当前位置: 移动技术网 > IT编程>开发语言>Java > 手写Struts,带你深入源码中心解析

手写Struts,带你深入源码中心解析

2019年08月14日  | 移动技术网IT编程  | 我要评论

个人剖析,不喜勿喷

扫码关注公众号,不定期更新干活
这里写图片描述

在此申明本博文并非原创,原文:http://blog.csdn.net/lenotang/article/details/3336623,本文章是在此文章基础上进行优化。也谈不上优化,只是加上了点自己的想法

jar包准备

  • 这里写图片描述

  • 为什么会用到这两个jar包呢,因为我需要通过这个jar来解析xml配置文件。

新建项目

  • 这里写图片描述

流程梳理

  • struts配置文件
```
<?xml version="1.0" encoding="utf-8"?>




/index.jsp
/web-inf/login.jsp



```

  • 熟悉struts的朋友都清楚struts.xml配置文件的重要性,这个配置文件名字是可以更改的,这里简单解释下这个配置文件的作用,首先我们找到action这个节点这个action的name是login,就是说前台中请求这个login经过这个配置文件解析就会把这个请求交给action中的class属性,也就是上面的
```
org.zxh.action.loginaction
```

具体的是交由这个类的login(method)这个方法。这个方法会方法一个string类型的字符串,如果返回的是success就将页面重定向到index.jsp如果是login就重定向到login.jsp。这个配置文件就是这样的作用。因为是自己写的,所以这里并不会想struts框架那样封装了很多东西,这里只是为了让读者更加深入的理解struts的运行机制。


如何将我们写的struts.xml文件在程序中启动呢?

  • 刚入门的同志可能会疑问,写一个配置文件就能处理前后台交互了?答案当然是不能。这里给大家普及一下web基础接触filter的,每次交互需要filter(jsp就是特殊的servlet),所以想实现交互我们就得新建一个servlet,在这个servlet里我们去读我们写的struts.xml文件,通过读到的信息决定下一步的操作。那么如何启动一个filter呢?这个不多说,直接在web项目中的web.xml配置拦截器就会执行filter。

新建filter(filterdispatcher)

  • 这个servlet就是struts的核心过滤器,需要先继承过滤器。
```

public class filterdispatcher implements filter{

@override
public void destroy() {
    // todo auto-generated method stub
    
}

@override
public void dofilter(servletrequest arg0, servletresponse arg1,
        filterchain arg2) throws ioexception, servletexception {
    // todo auto-generated method stub
    
}

@override
public void init(filterconfig arg0) throws servletexception {
    // todo auto-generated method stub
    
}

}

```
  • filter中我们要在初始化函数(init)中对一些参数进行初始化,对那些数据初始化呢,对!当然是拿配置文件的信息啦。配置文件是.xml这里我用dom4j读取.xml配置文件。 把struts.xml配置文件放在src下,(可以放在其他地方,这里的地址填的对应就行了)
// 获得xml配置文件
        string webrootpath = getclass().getclassloader()
                .getresource("struts.xml").getpath();
  • 拿到配置文件路径之后开始读取,这里我讲读到的数据封装到一个map里面。在封装在map中我们仔细观察一下配置文件

这里写图片描述

  • 其实我们放在map里面就是这四个属性的值,有了这四个值我们就可以完成一次前后台交互的映射了。所以为了方便这里封装成javabean。
package org.zxh.util;

import java.util.hashmap;
import java.util.map;

/**
 * 将action属性封装成类
 * @author 87077
 *
 */
public class actionconfig {
    
    //action 给别人调用的名字
    private string name;
    //action对应程序中的action类
    private string clazzname;
    //action中的方法
    private string method;
    //返回结果不知一条 所以用map
    private map<string, string> resultmap = new hashmap<string, string>();
    
    public actionconfig(){
        
    }
    
    public actionconfig(string name , string clazzname , string method , map<string, string> resultmap){
        this.name=name;
        this.clazzname=clazzname;
        this.method=method;
        this.resultmap=resultmap;
    }

    public string getname() {
        return name;
    }

    public string getclazzname() {
        return clazzname;
    }

    public string getmethod() {
        return method;
    }

    public map<string, string> getresultmap() {
        return resultmap;
    }

    public void setname(string name) {
        this.name = name;
    }

    public void setclazzname(string clazzname) {
        this.clazzname = clazzname;
    }

    public void setmethod(string method) {
        this.method = method;
    }

    public void setresultmap(map<string, string> resultmap) {
        this.resultmap = resultmap;
    }
    
}
  • 有了javabean 我们开始解析xml文件
package org.zxh.util;

import java.io.file;
import java.util.list;
import java.util.map;

import org.dom4j.document;
import org.dom4j.documentexception;
import org.dom4j.element;
import org.dom4j.io.saxreader;

/**
 * 采用dom4j解析xml配置文件
 * 
 * @author 87077
 * 
 */
public class configutil {

    /**
     * @param filename
     *            待解析的文件
     * @param map
     *            存放解析的数据
     */
    public static void parseconfigfile(string filename,
            map<string, actionconfig> map) {
        saxreader reader = new saxreader();
        try {
            document doc = reader.read(new file("d:\\zxh\\soft\\apache-tomcat-7.0.70\\apache-tomcat-7.0.70\\webapps\\mystruts\\web-inf\\classes\\struts.xml"));
            element root = doc.getrootelement();
            list<element> list = root.selectnodes("package/action");
            for (element element : list) {
                // 封装成actionconfig对象,保存在map中
                actionconfig config = new actionconfig();
                // 获取action中的值
                string name = element.attributevalue("name");
                string clazzname = element.attributevalue("class");
                string method = element.attributevalue("method");
                // 将值传入javabean中
                config.setname(name);
                config.setclazzname(clazzname);
                // 如果没有设置执行method 执行默认的
                if (method == null || "".equals(method)) {
                    method = "execute";
                }
                config.setmethod(method);
                // 继续向下获取action中的返回方法
                list<element> resultlist = element.selectnodes("result");
                for (element resultelement : resultlist) {
                    string resultname = resultelement.attributevalue("name");
                    string urlpath = resultelement.gettexttrim();
                    if (resultname == null || "".equals(resultname)) {
                        resultname = "success";
                    }
                    config.getresultmap().put(resultname, urlpath);
                }
                map.put(name, config);
            }
        } catch (documentexception e) {
            // todo auto-generated catch block
            e.printstacktrace();
        }
    }
}
  • 现在我们在回到过滤器上,上面两个类就是为了解析xml的。所以在filter中的init方法里我们就可以将解析的数据放到我们的全局map中
@override
    public void init(filterconfig arg0) throws servletexception {
        // todo auto-generated method stub 过滤器的初始化过程
        // 获得xml配置文件
        string webrootpath = getclass().getclassloader()
                .getresource("struts.xml").getpath();
        // 将xml配置文件解析装在到map中
        configutil.parseconfigfile(webrootpath, map);
    }

过滤器的执行

  • 过滤器真正执行是在dofilter方法开始时。
public void dofilter(servletrequest arg0, servletresponse arg1,
            filterchain arg2)

dofilter()方法类似于servlet接口的service()方法。当客户端请求目标资源的时候,容器就会调用与这个目标资源相关联的过滤器的 dofilter()方法。其中参数 request, response 为 web 容器或 filter 链的上一个 filter 传递过来的请求和相应对象;参数 chain 为代表当前 filter 链的对象,在特定的操作完成后,可以调用 filterchain 对象的 chain.dofilter(request,response)方法将请求交付给 filter 链中的下一个 filter 或者目标 servlet 程序去处理,也可以直接向客户端返回响应信息,或者利用requestdispatcher的forward()和include()方法,以及 httpservletresponse的sendredirect()方法将请求转向到其他资源。这个方法的请求和响应参数的类型是 servletrequest和servletresponse,也就是说,过滤器的使用并不依赖于具体的协议。

  • 获取请求域和响应域还有filter链,并设置编码防止乱码
//针对http请求,将请求和响应的类型还原为http类型
        httpservletrequest request = (httpservletrequest) arg0;
        httpservletresponse response = (httpservletresponse) arg1;
        //设置请求和响应的编码问题
        request.setcharacterencoding("utf-8");
        response.setcharacterencoding("utf-8");
  • 获取请求地址
//获取请求路径
string url = request.getservletpath();
  • 通过请求去判断知否拦截过滤这个地址的请求,本文默认过滤所有以.action结尾的请求
//请求地址过滤,如果不是以.action结尾的
        if(!url.endswith(".action")){
            //不是.action的放行
            arg2.dofilter(request, response);
            return ;
        }
  • 看我之前将xml文件中数据放入到map的格式可以看出我是讲整个javabean放入map中名字是action的name。所以下面我就要去那个name(就是请求中的login)
//解析request路径
        int  start = url.indexof("/");
        int end = url.lastindexof(".");
        string path=url.substring(start+1,end);
        //通过path去匹配到对应的actionconfig类。在这里已经解析到了所有的action的信息
        actionconfig config = map.get(path);
        //匹配不成功就返回找不到页面错误信息
        if(config==null){
            response.setstatus(response.sc_not_found);
            return ;
        }
  • 获取了actionconfig类了,action的所有信息都存储在这个javabean类中了,下面的事情就好办了。下面的只是会用到反射的知识。我们拿到真正action类的名称后就需要根据名字获取到这个action的实体类。
//通过actionconfig获取完成的类名字
        string clazzname=config.getclazzname();
        //实例化action对象,不存在的话就提示错误信息 
        object action = getaction(clazzname);
        if(action==null){
            //说明这个action是错误的,在配置文件中没有占到对应的action类
            response.setstatus(response.sc_not_found);
            return ;
        }

request参数获取并赋值给action

  • 执行action的方法前很定需要先将request中的参数获取到,进行赋值,这部才是真正的意义上的交互。
public static void requesttoaction(httpservletrequest request , object action )
  • 将传进来的action对象进行class话并获取action实体下的属性
class<? extends object> clazzaction = action.getclass();
        //获取aciton中所有属性,从前台获取的值很多,只有action属性中有的才会进行反射赋值
        field[] fields = action.getclass().getdeclaredfields();
  • 拿到request传过来的值并进行遍历
//获取请求中的名字属性值
        enumeration<string> names=request.getparameternames();
string name=names.nextelement();
            boolean flag=false;
            //需要判断action属性中没有的而请求中有的我们不需要进行反射处理
            for (field field : fields) {
                if(name.equals(field.getname())){
                    flag=true;
                }
            }
            if(!flag){
                return;
            }
            string[] value=request.getparametervalues(name);
  • 通过request中的name并且在action中有这个属性之后我们就需要获取action这个字段的属性。
class<object> fieldtype=(class<object>) clazzaction.getdeclaredfield(name).gettype();
  • 获取action的改name字段属性的set方法
//通过反射调用该属性的set方法
                    string setname="set"+name.substring(0,1).touppercase()+name.substring(1);
                    method method=clazzaction.getmethod(setname, new class[]{fieldtype});
  • 下面我们就需要将获取的value按类型
private static object[] transfer(class<object> fieldtype , string[] value){
        object[] os = null;
        //fieldtype 是[]这种类型的,需要将[]去掉
        string type=fieldtype.getsimplename().replace("[]", "");
        if("string".equals(type)){
            os=value;
        }else if("int".equals(type)||"integer".equals(type)){
            os = new integer[value.length];
            for (int i = 0; i < os.length; i++) {
                os[i] = integer.parseint(value[i]);
            }
        }else if("float".equals(type)||"float".equals(type)){
            os=new float[value.length];
            for (int i = 0; i < os.length; i++) {
                os[i]=float.parsefloat(value[i]);
            }
        }else if("double".equals(type)||"double".equals(type)){
            os=new double[value.length];
            for (int i = 0; i < os.length; i++) {
                os[i]=double.parsedouble(value[i]);
            }
        }
        return os;
    }
  • 获取object数据之后就是讲这个object数据通过反射付给action对应的属性
//判断是否是数组属性
                    if(fieldtype.isarray()){
                        method.invoke(action, new object[]{object});
                    }else {
                        method.invoke(action, new object[]{object[0]});
                    }

这说一下 method.invoke是将action类中method方法这个方法需要的参数就是object<a href="http://www.oschina.net/code/snippet_216465_36771”>详解

  • 有了这个方法我们在回到filter就可以了
//前置拦截,获取request里面的参数,调用action的set方法给属性设置值
        beanutil.requesttoaction(request, action);
  • 属性赋值完成就开始执行action中的method了
private string executeaction(actionconfig config, object action) {
        string method = config.getmethod();
        string result = null;
        try {
            method callmethod = action.getclass().getmethod(method,string.class);
            result = (string) callmethod.invoke(action, new object[] {});
        } catch (exception e) {
            // todo auto-generated catch block
            e.printstacktrace();
        }
        return config.getresultmap().get(result);
    }
  • 到这里你已经获取了配置文件中前台映射后应该的result了,那么就简单了,直接重定向就可以了,到这里就实现了struts的前后台交互。
request.getrequestdispatcher(result).forward(request, response);

验证正确性

  • 下面就在前台jsp中form表单将数据传递给我们的login action看看会不会去执行指定的方法
<form method="post" action="login.action" name="loginform">
        <table width="422" border="1" bgcolor="#0080c0" height="184">
            <caption>
                <h1>用户登陆</h1>
            </caption>
            <tbody>
                <tr>
                    <td>&nbsp;姓名:</td>
                    <td>&nbsp; <input type="text" name="username">
                    </td>
                </tr>
                <tr>
                    <td>&nbsp;密码:</td>
                    <td>&nbsp; <input type="password" name="password">
                    </td>
                </tr>
                <tr align="center">
                    <td colspan="2">&nbsp; <input type="submit" value="登陆"
                        name="submit"> <input type="reset" value="重置" name="reset">
                    </td>
                </tr>
            </tbody>
        </table>
    </form>
  • 效果读者自行展示吧,到这里struts的运行机制就讲完了,注意知识运行机制里面还有很多值得我们学习的东西,就好比说这里有很多过滤器,不同过滤器过滤数据程度不同执行效果不同。希望有机会再和大家分享一些其他关于struts的知识!

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网