深入理解Struts2原理之自己实现Struts2框架

来源:互联网 发布:知乎 招聘 编辑:程序博客网 时间:2024/05/28 09:32

作为struts2的初学者,有时候理解struts框架的原理,网上看教程会一头雾水,所以要是能自己体会实现struts框架的整个过程,对理解struts思路会更加清晰一些,下面就来尝试自己实现这个过程。

先看一下struts2的官方框架结构图
这里写图片描述

上面那个图好复杂,看不懂没关系,我们先简化一下过程方便理解,因为作为初学者不能一下子就实现了整个框架的所有功能,只能实现他的核心功能,如下图:
这里写图片描述

这样图就好看很多了,大概意思是我们发送的request请求先经过过滤器筛选,然后到达ActionInvocation,通过这个类加载拦截器,再去找到对应的action,再找到action要返回的jsp界面,然后响应(response)返回显示数据。
官方的图里有Actionproxy,这是个action代理对象,通过他找到ConfigManager读取配置文件然后发送需要的数据到ActionInvocation中,本文是自己实现struts框架,没必要弄的这么复杂,就把这部分融合到ActionInvocation中去了,其他部分就不介绍了,因为我也不懂是啥


下面实现过程,每一个类希望大家参考这个图边看边实现,这样才好理解

接下来要说的就是需要具备的技术:

  1. XML解析,Xpath表达式.(dom4j)
  2. Servlet技术
  3. java内省(BeanUtils)(参数拦截器)
  4. ThreadLocal线程本地化类
  5. 递归调用

需要用到的jar包:
(jar其他版本的自己找了)
commons-beanutils-1.8.3.jar
commons-logging-1.1.1.jar
dom4j-1.6.1.jar
jaxen-1.1-beta-6.jar

步骤1:
创建web项目,然后导入jar包
这里写图片描述

步骤2:
创建struts.xml文件,放在src目录下

<?xml version="1.0" encoding="UTF-8" ?><struts>        <interceptor  class="com.test.interceptor.ParamInterceptor" />         <constant name="struts.action.extension" value="action" />        <action name="HelloAction" method="regist"                  class="com.test.action.HelloAction" >            <result name="success"  >/index.jsp</result>        </action>        <action name="registAction" method="regist"                     class="com.test.action.RegistAction" >            <result name="regist"  >/home.jsp</result>        </action></struts>

步骤3:
创建读取XML配置文件的解析类(ConfigurationManager)

public class ConfigurationManager {    /**     * 读取Interceptor     * @return     */    public static List<String> getInterceptors(){        List<String> interceptors = null;        //1创建解析器        SAXReader reader = new SAXReader();        //2.加载配置文件=>document        //获得配置文件流        InputStream is = ConfigurationManager.class.getResourceAsStream("/struts.xml");        Document doc = null;        try{            doc = reader.read(is);        }catch(DocumentException e){            e.printStackTrace();            throw new RuntimeException("配置文件加载失败!");        }        //3.书写xpath        String xpath = "//interceptor";        //4.根据xpath获得拦截器配置        List<Element> list = doc.selectNodes(xpath);        //5.将配置信息封装到list集合中        if(list != null && list.size()>0){            interceptors = new ArrayList<String>();            for(Element ele : list){                String className = ele.attributeValue("class");                interceptors.add(className);            }        }        //返回        return interceptors;    }    /**     * 读取action     * @return     */    public static Map<String,ActionConfig> getActionConfig() {        Map<String, ActionConfig> actionMap;        Document doc = getDocument();        String xpath = "//action";        List<Element> list = doc.selectNodes(xpath);        if(list == null || list.size() ==0){            return null;        }        actionMap = new HashMap<String,ActionConfig>();        for(Element e : list){            ActionConfig action = new ActionConfig();            action.setName(e.attributeValue("name"));            action.setClassName(e.attributeValue("class"));            String method = e.attributeValue("method");            action.setMethod(method==null||method.trim().equals("")?"execute":method);            List<Element> results = e.elements("result");            for(Element result : results){                action.getResult().put(result.attributeValue("name"), result.getText());            }            actionMap.put(action.getName(),action);        }        return actionMap;    }    private static Document getDocument()  {        Map<String,ActionConfig> actionMap =null;        //1创建解析器        SAXReader reader = new SAXReader();        //2.加载配置文件=>document        //获得配置文件流        InputStream is = ConfigurationManager.class.getResourceAsStream("/struts.xml");        Document doc;        try {            doc = reader.read(is);            return doc;        } catch (DocumentException e) {            e.printStackTrace();            throw new RuntimeException("加载配置文件失败!");        }    }    /**     * 读取constant     * @param key     * @return     */    public static String getConstant(String key) {        Document doc = getDocument();        String path = "//constant[@name='"+key+"']";        Element constant = (Element) doc.selectSingleNode(path);        if(constant!=null){            return constant.attributeValue("value");        }else{            return null;        }    }}

创建需要的ActionConfig类:

public class ActionConfig {    /**     * <action name="" method=""                         class="" >            <result name="success"  >/index.jsp</result>        </action>     */    private String name;//对应的是action中的name    private String method;//对应的是action中的method    private String className;//对应的是action中的class    private Map<String,String> result = new HashMap<String, String>();//对应的是action中的result    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public String getMethod() {        return method;    }    public void setMethod(String method) {        this.method = method;    }    public String getClassName() {        return className;    }    public void setClassName(String className) {        this.className = className;    }    public Map<String, String> getResult() {        return result;    }    public void setResult(Map<String, String> result) {        this.result = result;    }}

步骤4:
创建Struts2的数据中心(ActionContext)

public class ActionContext implements Serializable{    private static final long serialVersionUID = 1294441883362417229L;    //为了保证能获得ActionContext    public static ThreadLocal<ActionContext> tl = new ThreadLocal<ActionContext>();    ///用于存放各个域    private Map<String, Object> context;    public ActionContext(Map<String, Object> context) {        this.context = context;    }    public ActionContext(HttpServletRequest request,            HttpServletResponse response, Object action) {        // 准备域        context = new HashMap<String, Object>();        // 1.request        context.put(Constant.REQUEST, request);        // 2. response        context.put(Constant.RESPONSE, response);        // 3. param        context.put(Constant.PARAM, request.getParameterMap());        // 4.session        context.put(Constant.SESSION, request.getSession());        // 5.application        context.put(Constant.APPLICATION, request.getSession()                .getServletContext());        // ---------------------------------------------------        // 6.valuestack 值栈        ValueStack vs = new ValueStack();        // 将action压入栈顶        vs.push(action);        // 将值栈放入request域        request.setAttribute(Constant.VALUE_STACK, vs);        // 将值栈放入数据中心        context.put(Constant.VALUE_STACK, vs);        // -----------------------------------------------------------------        tl.set(this);    }    //下面提供域的获取方法    public HttpServletRequest getRequest() {        return (HttpServletRequest) context.get(Constant.REQUEST);    }    public HttpServletResponse getResponse() {        return (HttpServletResponse) context.get(Constant.RESPONSE);    }    public HttpSession getSession() {        return (HttpSession) context.get(Constant.SESSION);    }    public ServletContext getApplication() {        return (ServletContext) context.get(Constant.APPLICATION);    }    public Map<String, String[]> getParam() {        return (Map<String, String[]>) context.get(Constant.PARAM);    }    public ValueStack getStack() {        return (ValueStack) context.get(Constant.VALUE_STACK);    }    public static ActionContext getActionContext(){        return tl.get();    }    }

需要用到的类(Constant ):

public class Constant {    /**     * request域     */    public static final String REQUEST = "com.test.request";    /**     * response域     */    public static final String RESPONSE = "com.test.response";    /**     * session域     */    public static final String SESSION = "com.test.session";    /**     * application域     */    public static final String APPLICATION = "com.test.application";    /**     * param域     */    public static final String PARAM = "com.test.param";    /**     * value_stack域     */    public static final String VALUE_STACK = "com.test.stack";}

需要用到的类(值栈):

public class ValueStack {    private List<Object> list = new ArrayList<Object>();    //弹栈    public Object pop(){        return list.remove(0);    }    //压栈    public void push(Object o){        list.add(0, o);    }    //取出顶部对象    public Object seek(){        return list.get(0);    }}

需要用到的接口(Interceptor):

public interface Interceptor extends Serializable{    /**     * 初始化     */    public void init();    /**     * 拦截     * @param invocation     * @return     */    public String interceptor(ActionInvocation invocation);    /**     * 销毁     */    public void destory();}

步骤5:
创建ActionInvocation
ActionInvocation负责完成拦截器链状调用以及action调用,以及数据中心(ActionContext)的提供

public class ActionInvocation {    //过滤器链    private Iterator<Interceptor> interceptors;    //即将调用的action实例    private Object action;    //action配置信息    private ActionConfig config;    //数据中心    private ActionContext ac;    public ActionInvocation(List<String> InterceptorClassNames,ActionConfig config,HttpServletRequest request,HttpServletResponse response) {        //1 准备Interceptor链        List<Interceptor> interceptorList = null;        if(InterceptorClassNames!=null && InterceptorClassNames.size()>0){             interceptorList = new ArrayList<Interceptor>();            for(String className : InterceptorClassNames){                Interceptor interceptor;                try {                    //获取实例                    interceptor = (Interceptor) Class.forName(className).newInstance();                    interceptor.init();//初始化                } catch (Exception e) {                    e.printStackTrace();                    throw new RuntimeException("创建Interceptor失败!!"+className);                }                 interceptorList.add(interceptor);            }            this.interceptors = interceptorList.iterator();        }        //2 准备action实例        this.config = config;        try {            action = Class.forName(config.getClassName()).newInstance();//获取action对象        } catch (Exception e) {            e.printStackTrace();            throw new RuntimeException("action创建失败!"+config.getClassName());        }         //3 准备数据中心actionContext         ac = new ActionContext(request, response, action);    }    public ActionContext getActionContext() {        return ac;    }    /**     * 递归调用拦截器链     * @param invocation     * @return     */    public String invoke(ActionInvocation invocation){        //1 准备一个变量接受action运行结果的路由串        String result = null;        //2 判断拦截器链中是否有下一个拦截器&&变量是否被赋值        if(interceptors!= null  && interceptors.hasNext() && result==null ){            //有=>调用下一个拦截的拦截方法            Interceptor it = interceptors.next();            result = it.interceptor(invocation);        }else{            //没有=> 调用action实例的处理方法            //获得将要调用的action方法名称            String methodName = config.getMethod(); // execute            //根据action对象和方法名称获得方法对应的Method对象            try {                Method executeMethod = action.getClass().getMethod(methodName);                //调用目标方法                result = (String) executeMethod.invoke(action);            } catch (Exception e) {                e.printStackTrace();                throw new RuntimeException("您配置的action方法不存在!");            }         }        //3将action的结果路由串返回        return result;    }

步骤6:
创建StrutsPrepareAndExecuteFilter 过滤器类:

public class StrutsPrepareAndExcuteFilter implements Filter{    //配置文件中的过滤器配置信息    private List<String> InterceptorList;    //struts处理的action后缀    private String extension;    // 配置文件中action配置信息    private Map<String, ActionConfig> actionConfigs;    /**     * 初始化     */    public void init(FilterConfig arg0) throws ServletException {        //1> 准备过滤器链配置        InterceptorList = ConfigurationManager.getInterceptors();        //2> 准备constant配置=> 访问后缀的配置信息        extension = ConfigurationManager.getConstant("struts.action.extension");        //3> 加载action配置        actionConfigs = ConfigurationManager.getActionConfig();    }    /**     * 过滤器处理     */    public void doFilter(ServletRequest request, ServletResponse response,            FilterChain chain) throws IOException, ServletException {        //0  强转request和response为 HttpServletRequest 和 HttpServletResponse        HttpServletRequest req = (HttpServletRequest) request;        HttpServletResponse  resp = (HttpServletResponse) response;        //1 获得请求路径        // http://localhost:8080/MyStruts/HellAction.action        String path = req.getServletPath(); //  /HellAction.action        //2 判断请求是否需要访问action            if(!path.endsWith(extension)){                // 后缀不以".action"结尾 => 不需要访问action资源 => chain.doFIlter()放行                chain.doFilter(request, response);                return;            }else{                // 后缀以".action"结尾 => 需要访问action                 //3 获得需要访问的action名称=>提取需要访问的action名称                path = path.substring(1);// HellAction.action                path = path.replace("."+extension, "");// HellAction                //4 查找action对应的配置信息                ActionConfig config = actionConfigs.get(path);                if(config == null){                    //未找到配置信息 => 抛出异常提示访问的action不存在                    throw new RuntimeException("访问的action不存在!");                }                //找到配置信息  => 获得到配置信息=>继续                //5 创建actionInvocation实例,完成对拦截器器链以及action的方法                ActionInvocation invocation = new ActionInvocation(InterceptorList,config,req,resp);                //6 获得结果串                 String result = invocation.invoke(invocation); //success                //7 从配置信息找到结果串对应的路径                String dispatcherPath = config.getResult().get(result);                //找不到结果路径=> 抛出异常提示返回的路径找不到对应页面                if(dispatcherPath ==null || "".equals(dispatcherPath)){                    throw new RuntimeException("您要访问的结果没有找到配置!");                }                //8 将请求转发到配置的路径                req.getRequestDispatcher(dispatcherPath).forward(req, resp);                //释放资源                ActionContext.tl.remove();            }    }    /**     * 销毁     */    public void destroy() {        // TODO Auto-generated method stub    }}

步骤7:
将Filter配置到web.xml中

这里写图片描述

步骤8:
struts经典之参数封装拦截器创建:
以后自己写的每一个拦截器操作步骤都是这样的

public class ParamInterceptor implements Interceptor{       public void init() {        }    public String interceptor(ActionInvocation invocation) {        //1 获得参数        //2 获得action对象        //ActionContext ac = ActionContext.getActionContext().getStack();//第一种获得ActionContext对象        ActionContext ac =  invocation.getActionContext();//第二种获得ActionContext对象        ValueStack vs = ac.getStack();        Object action = vs.seek();        //3 封装        try {            BeanUtils.populate(action, ac.getRequest().getParameterMap());        } catch (IllegalAccessException e) {            e.printStackTrace();        } catch (InvocationTargetException e) {            e.printStackTrace();        }        //4 放行        return invocation.invoke(invocation);    }    public void destory() {    }}

然后在struts.xml里加上:

<interceptor  class="com.test.interceptor.ParamInterceptor" /> 

测试一:
新建一个action:
地址栏测试get请求

public class HelloAction {    private String name;    private String password;    public String getPassword() {        return password;    }    public void setPassword(String password) {        this.password = password;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public String execute(){                System.out.println("hello world!"+name+"==>"+password);             return "success";    }}

然后在struts.xml里配置对应的action:

<action name="HelloAction" method="execute"                         class="com.test.action.HelloAction" >            <result name="success"  >/index.jsp</result>        </action>

把项目部署到tomcat运行起来,然后在浏览器地址栏输入
http://localhost:8080/MyStruts2/HelloAction.action?name=marry&password=123456

会看到控制台输出
这里写图片描述

测试二:
表单(post)提交数据返回界面
新建一个RegistAction:

public class RegistAction implements Serializable{    private static final long serialVersionUID = -793621223468025885L;      private String name;    private String password;    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public String getPassword() {        return password;    }    public void setPassword(String password) {        this.password = password;    }    public String regist(){        System.out.println("注册的用户名:" + name+" ==> 密码:"+password);    ActionContext.getActionContext().getRequest().setAttribute("name", name);        return "regist";    } }

然后在struts.xml里配置对于的action:

<action name="registAction" method="regist"                         class="com.test.action.RegistAction" >            <result name="regist"  >/home.jsp</result>        </action>

再创建一个regist.jsp,下面是body的代码:

<body>    <form action="registAction.action" method="post">    <table>    <tr>    <td>用户名:</td>    <td><input type="text" id="name" name="name"></td>    </tr>    <tr>    <td>密码:</td>    <td><input type="password" id="password" name="password"></td>    </tr>     <tr>      <td colspan="2"><input type="submit" value="提交"></td>    </tr>    </table>       </form>  </body>

然后创建一个home.jsp,下面是body的代码:

<body>    欢迎您:${requestScope.name}  </body>

最后部署到tomcat运行起来
在浏览器访问地址:
这里写图片描述

输入参数后,运行结果如下:
这里写图片描述

到此说明已经成功实现了
下面是整个项目的结构图:
这里写图片描述

本文的源码地址:
http://download.csdn.net/detail/u014204541/9842176

写在最后,本文可能存在不足之处,但愿大神看到后有不足之处可以提出,方便后期学习,如果实现过程看不懂,建议多看图,边看边实现这个过程。

1 0
原创粉丝点击