struts2执行流程源码分析

一:tomcat刚启动时

 

 

tomcat服务器启动时,加载web.xml配置文件,执行init()方法,可以看到StrutsPrepareAndExecuteFiter这个类只是实现filter的过滤器。

 

 

 

下面是整个init方法。

   FilterHostConfig config = new FilterHostConfig(filterConfig);//servlet容器与核心过滤器做了一个整合

Dispatcher dispatcher = init.initDispatcher(config);// 初始化所有的资源文件

 

public Dispatcher initDispatcher( HostConfig filterConfig ) {

        Dispatcher dispatcher = createDispatcher(filterConfig);

        dispatcher.init();

        return dispatcher;

    }

 

 

public void init() {

   init_DefaultProperties(); // 加载default.properties文件

   init_TraditionalXmlConfigurations(); 

      //加载struts-default.xml,strut-plugin.xml,struts.xml

}

 

 

 

 

 

 

 

二.当客户端请求url时  

具体流程:

当客户端发出一个请求时。如请求url:personAction_savePerson.action,那么会这么执行:

1.会执行StrutsPrepareAndExecuteFilter类的doFilter(ServletRequest req, ServletResponse res, FilterChain chain)方法,

 以下是doFilter()方法的代码:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) 

throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) req;  //转换请求对象

        HttpServletResponse response = (HttpServletResponse) res; //转换响应对象

 

        try {

            prepare.setEncodingAndLocale(request, response);//设置编码

            prepare.createActionContext(request, response);  //创建ActionContext

            prepare.assignDispatcherToThread();

            if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {

                chain.doFilter(request, response);

            } else {

                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);

        }

    }

 首先会将会将requestrespone对象转换为httt相关的对象,接着设置编码,然后创建ActionContext对象:

 public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) {

        ActionContext ctx;

        Integer counter = 1;

        Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER);

        if (oldCounter != null) {

            counter = oldCounter + 1;

        }

        

        ActionContext oldContext = ActionContext.getContext();  

        if (oldContext != null) {

            ctx = new ActionContext(new HashMap<String, Object>(oldContext.getContextMap()));

        } else {

            ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();  //利用容器创建值栈

            stack.getContext().putAll(dispatcher.createContextMap(request, response, nullservletContext));  //构建值栈的结构

 

            ctx = new ActionContext(stack.getContext());

        }

        request.setAttribute(CLEANUP_RECURSION_COUNTER, counter);

        ActionContext.setContext(ctx);

       //把整个actionContext放入到了当前线程中,因为actionContext中有valueStack,所以valueStack也在当前线程中,这样就保证了数据的安全性

        return ctx;

    }

 我们来看看怎么创建的ActionContext对象,上面部分看到当oldContext不存在时,利用类容器创建值栈。曾今我在Struts2+技术内幕这本书中看到,描述ActionContextvalueStack的关系是这么说:两者相互依存,形影不离。现在可以理解为什么会这么说了,因为ValueStack是伴随这ActionContext的创建而创建的,看这行代码: ctx = new ActionContext(stack.getContext());也就是说它把值栈放在了ActionContext里了。 然后在上面的代码中还有一个关键的地方,就是它把整个ActionContext放在了当前线程中了。这是Struts2的一大亮点,其实是Xwork的一大亮点(因为这是Xwork的思想)。传统的web开发都是基于Servlet的开发,Servlet是单利的,在web环境并发访问中会出现线程安全问题,而Servlet解决的方案其实是绕过去的思想。解决Servlet的线程安全是不定义全局变量(或者仅仅只能存在只读的全局变量),采用局部变量的机制,这样做可以绕过线程安全问题。我们看看Struts2ActionContext放入当前线程中的具体实现:

public static void setContext(ActionContext context) {

        actionContext.set(context);

    }

我们进一步跟踪源代码:

public void set(T value) {

        Thread t = Thread.currentThread();

        ThreadLocalMap map = getMap(t);

        if (map != null)

            map.set(this, value);

        else

            createMap(t, value);

    }

从createMap(t, value);这句代码我们可以知道,实际上是把ActionContext以当前线程作为键放在ThreadLocalMap中,所以要取ActionContext是以当前线程取得,因此不会出现线程安全问题。这是创建ActionContext的过程,我们回到doFilter()方法中,看看还做了些什么。

 

2.我们看到这句代码: prepare.assignDispatcherToThread(); 这段代码的作用其实是让整个过程都在当前线程中执行。我们跟源码会发现还会出现上面放入线程的那段代码。

3.继续回到doFilter方法往下看:request = prepare.wrapRequest(request);这段代码是对request的一个封装,最终变成 StrutsRequestWrapper 类型的request,所以才会出现神奇的request。 

 

4.再接着往下看:  execute.executeAction(request, response, mapping); 我们跟踪这段代码,会出现这段代码:

 dispatcher.serviceAction(request, response, servletContext, mapping);

接着跟踪:以下那段源码比较长,我截取一部分:

serviceAction(){

    ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);

    /**

      *  因为在struts2容器中有太多的参数

             request,response,valueStack,session,application,paramters

         所以struts2容器对当前的请求中用到所有的数据封装在了ActionContext中的map

      */

extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.createValueStack(stack));

    String namespace = mapping.getNamespace();

    String name = mapping.getName();

    String method = mapping.getMethod();

    //创建actionProxy

    ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(

                    namespace, name, method, extraContext, true, false);

    proxy.execute()方法

    prepare.cleanupRequest(request);//struts2过程中的数据全部清空了

}

我们关注以下这个方法createActionProxy 方法,这个方法从名字上可以猜出是创建Action的代理类,跟过源码后会发现(这个源码比较散,所以没有粘贴过来)执行了DefaultActionInvocation中的init方法,而在这个inint方法中:

public void init(ActionProxy proxy) {

        this.proxy = proxy;

        Map<String, Object> contextMap = createContextMap();

        ActionContext actionContext = ActionContext.getContext();

        if (actionContext != null) {

            actionContext.setActionInvocation(this);

        }

        createAction(contextMap); 

        if (pushAction) {

            stack.push(action);

            contextMap.put("action"action);

        }

        invocationContext = new ActionContext(contextMap);

        invocationContext.setName(proxy.getActionName());

        List<InterceptorMapping> interceptorList = new ArrayList<InterceptorMapping>(proxy.getConfig().getInterceptors());

        interceptors = interceptorList.iterator();

    }

 

我们来分析以下这段代码:createAction(contextMap); 这句代码是创建了一个Action,所以Action的创建时机就是当创建ActionProxy的时候会创建Action。对于它如何创建Action的,我并不怎么关心,我看了以下源码,创建Action的过程相对来说比较复杂,也不是我讨论的范围,不过跟过源码发现里面有这么一段代码:

 action = objectFactory.buildAction(proxy.getActionName(), proxy.getNamespace(), proxy.getConfig(), contextMap);

从上面可以看到它是利用工厂来创建Action的。

 接着我们看下面这两句代码: stack.push(action); contextMap.put("action"action); 从这两句代码我们可以发现它把action放入到了对象栈的栈顶,并且还把Action放了一份到Map栈中。

接着往下看有这两句代码:

        List<InterceptorMapping> interceptorList = new ArrayList<InterceptorMapping>(proxy.getConfig().getInterceptors());

        interceptors = interceptorList.iterator();

从这两句代码可以看出:是获取所有的拦截器,并且返回了迭代器的形式。

 

5.接下来我们回到上面的serviceAction()这个方法中,在这个方法中还有一个重要的方法:  proxy.execute()  ,而这个方法会执行DefaultActionInvocation中的invoke方法,最关键的就是这个invoke方法。我们看看这个invoke方法到底发生了什么?下面摘取一段invoke方法里面的内容:

if (interceptors.hasNext()) {

                final InterceptorMapping interceptor = interceptors.next();

                String interceptorMsg = "interceptor: " + interceptor.getName();

                UtilTimerStack.push(interceptorMsg);

                try {

                          resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);

                            }

                finally {

                    UtilTimerStack.pop(interceptorMsg);

                }

            } else {

                resultCode = invokeActionOnly();

            }

            if (!executed) {

                if (preResultListeners != null) {

                    for (Object preResultListener : preResultListeners) {

                        PreResultListener listener = (PreResultListener) preResultListener;

 

                        String _profileKey = "preResultListener: ";

                        try {

                            UtilTimerStack.push(_profileKey);

                            listener.beforeResult(thisresultCode);

                        }

                        finally {

                            UtilTimerStack.pop(_profileKey);

                        }

                    }

                }

                if (proxy.getExecuteResult()) {

                    executeResult();

                }

                executed = true;

            }

 

6.我们可以看到interceptors.hasNext()这句代码是调用了所有的拦截器,然后这段代码:interceptor.getInterceptor().intercept(DefaultActionInvocation.this);这段代码是执行每一个拦截器,返回一个字符串。然后我们看这行代码:invokeActionOnly(); 这行代码是说明等所有的拦截器执行之后执行Action。接下来是这段代码: executeResult(); 即执行结果集。

 

7.回到最初的doFilter()方法中,还有最后一个收尾的方法:prepare.cleanupRequest(request);  这个方法是把struts2过程中的数据全部清空了,以免数据膨胀,我们可以看到这个方法是放在finally里面的,也就是说不管是否发生异常都会执行,Struts2的清除数据还有一个destroy() 方法,也就是说为了防止数据膨胀,Struts2做了非常好的清理过程。

 

 

最后总结:

   1、在struts2的配置文件中

       两大类型的配置

         一种类型  独立于包外的

          bean

          constant

          include

        一种类型   package  

           action,interceptor,result等都是在package中的,可以利用extend机制继承

   2、在执行过滤器的过程中

       1、创建ActionContext

              1、创建actionContext

              2、创建值栈

              3、把actionContext加入到当前线程中

       2、创建actionProxy

              执行DefaultActionInvocationinit方法

                  1、创建action

                  2、把action放入到栈顶

                  3、创建所有的拦截器,并且产生拦截器的迭代器

       3、执行Proxyexecute方法

            proxy.execute()---->invocaction.invoke方法

                   1、执行所有的拦截器

                   2、执行当前请求的action

                   3、执行RreResultListener

                   4、执行结果集

       4、清除数据  在finally中,这样即便出错,也能清除

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值