从Struts2源码学习Struts2的工作原理

来源:互联网 发布:怕出头知乎 编辑:程序博客网 时间:2024/06/06 00:18

今天我和我好基友啊斌通过探讨struts2的源码,总结了一下它的原理,代码是不会骗人的。
总的来说:struts的工作原理有7步:
1 客户端初始化一个指向Servlet容器的请求;
web应用程序启动时就会加载并初始化ActionServler。
用户提交表单时,一个配置好的ActionForm对象被创建,并被填入表单相应的数据,
ActionServler根据Struts-config.xml文件配置好的设置决定是否需要表单验证,
如果需要就调用ActionForm的Validate()验证后选择将请求发送到哪个Action

2 这个请求经过一系列的过滤器
在项目部署的时候,由tomcat容器读取项目的web.xml文件,测试的web.xml文件如下:

       <?xml version="1.0" encoding="UTF-8"?><web-app version="2.5"     xmlns="http://java.sun.com/xml/ns/javaee"     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"     xsi:schemaLocation="http://java.sun.com/xml/ns/javaee     http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">  <display-name></display-name>   <filter>        <filter-name>struts2</filter-name>        <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>    </filter>    <filter-mapping>        <filter-name>struts2</filter-name>        <url-pattern>*.action</url-pattern>    </filter-mapping>  <welcome-file-list>    <welcome-file>index.jsp</welcome-file>  </welcome-file-list></web-app>

然后被org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter这个过滤器拦截,执行其的init方法

public class StrutsPrepareAndExecuteFilter implements StrutsStatics, Filter {    //PrepareOperations 包含在执行前请求的准备操作    protected PrepareOperations prepare;    protected ExecuteOperations execute;    protected List<Pattern> excludedPatterns = null;    public void init(FilterConfig filterConfig) throws ServletException {        InitOperations init = new InitOperations();        Dispatcher dispatcher = null;        try {            //主机配置包裹filterconfig            FilterHostConfig config = new FilterHostConfig(filterConfig);            //初始化日志            init.initLogging(config);            dispatcher = init.initDispatcher(config);            init.initStaticContentLoader(config, dispatcher);            prepare = new PrepareOperations(dispatcher);            execute = new ExecuteOperations(dispatcher);            this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);            postInit(dispatcher, filterConfig);        } finally {            if (dispatcher != null) {            //清理任何资源用于初始化调度                dispatcher.cleanUpAfterInit();            }            init.cleanup();        }    }

我们可以看到,传入的参数是一些配置信息,包括框架的版本,过滤器的名字,发布的项目等等
这里写图片描述

然后执行一系列初始化的的方法用于初始化环境:eg:
这里面通过执行此方法:创建和初始化调度
这里写图片描述
得到了很多有用的信息,例如:默认编码集,默认的defalutFrameworkBeanName,还有通过FreeMarker技术动态生成的一个错误页面
这里写图片描述
以前不知到报错的页面什么时候生成的,现在知道啦。
最后就是执行一些收尾工作:eg:
dispatcher.cleanUpAfterInit()清理任何资源用于初始化调度 –> init.cleanup()–> ActionContext.setContext(null); init–>FilterHostConfig(主机配置包裹filterconfig),

3: 接着FilterDispatcher被调用,FilterDispatcher询问ActionMapper来决定这个请求是否需要调用某个Action
好,初始化之后一个请求冲了过来,过滤器直接拦截,并执行里面的doFilter()方法。
我们接着看源码:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {        HttpServletRequest request = (HttpServletRequest) req; //request,response的转换        HttpServletResponse response = (HttpServletResponse) res;        try {            if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {                chain.doFilter(request, response);            } else {            //prepare包含在执行前请求的准备操作                prepare.setEncodingAndLocale(request, response);                //设置好Action的上下文,这个非常的重要                prepare.createActionContext(request, response);                prepare.assignDispatcherToThread();                //用Struts的包装处理多部分请求的请求                request = prepare.wrapRequest(request);                ActionMapping mapping = prepare.findActionMapping(request, response, true);                if (mapping == null) {                    boolean handled = execute.executeStaticResourceRequest(request, response);                    if (!handled) {                        chain.doFilter(request, response);                    }                } else {                    execute.executeAction(request, response, mapping);                }            }        } finally {            prepare.cleanupRequest(request);        }    }

其中在执行prepare.createActionContext(request, response)方法中设置很多有用的信息,eg:请求的类,属性,栈工厂等等;
这里写图片描述
然后就是通过返回的map判断是否有action需要调用某个Action.
这里写图片描述
我们这里的话返回的mapping不为空,具体值如下:
这里写图片描述
,所以直接执行executeAction方法.

4 如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求的处理交给ActionProxy(action的代理)
继续看我们的代码:executeAction方法中只有一句代码,就是
dispatcher.serviceAction(request, response, mapping);
然后我们进入serviceAction方法.

public void serviceAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping)            throws ServletException {    //创建上下文的map        Map<String, Object> extraContext = createContextMap(request, response, mapping);        // 如果前面有一个值栈,然后创建一个新的副本,并将它传递给新的操作        ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);        //根据stack的状态判断nullStack的状态        boolean nullStack = stack == null;        if (nullStack) {        //返回特定于当前线程的Action上下文。            ActionContext ctx = ActionContext.getContext();            if (ctx != null) {                //获取OGNL值栈。                stack = ctx.getValueStack();            }        }        if (stack != null) {            extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.createValueStack(stack));        }        String timerKey = "Handling request from Dispatcher";        try {            UtilTimerStack.push(timerKey);            //取得对应的值 eg:namespace=/ name=reg_student            String namespace = mapping.getNamespace();            String name = mapping.getName();            String method = mapping.getMethod();        //创建Action的动态代理,这个非常的重要            ActionProxy proxy = getContainer().getInstance(ActionProxyFactory.class).createActionProxy(                    namespace, name, method, extraContext, true, false);            request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());            // 如果ActionMapping说直冲向结果,则实现它            if (mapping.getResult() != null) {                Result result = mapping.getResult();                result.execute(proxy.getInvocation());            } else {                //执行动态代理(非常重要)                proxy.execute();            }    }

执行动态代理的过程我在之前的博客有写到,这里就不过多写了。

5ActionProxy通过Configuration Manager询问框架的配置文件,找到需要调用的Action类

6 ActionProxy创建一个ActionInvocation的实例。
7 ActionInvocation实例使用命名模式来调用,在调用Action的过程前后,涉及到相关拦截器(Intercepter)的调用。
8 一旦Action执行完毕,ActionInvocation负责根据struts.xml中的配置找到对应的返回结果
返回结果通常是(但不总是,也可 能是另外的一个Action链)一个需要被表示的JSP或者FreeMarker的模版。
在表示的过程中可以使用Struts2 框架中继承的标签。在这个过程中需要涉及到ActionMapper

总的来说看了源码之后对struts的工作原理加深了印象,看优秀的代码,能进步。感觉在看源码的过程中千万要坐的住,遇到看不懂的不要心烦意乱,要稳得住,坚持慢慢看,总会有所收获的。

2 1