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

2 篇文章 0 订阅

作为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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值