Struts2工作流程个人解析

来源:互联网 发布:linux dns配置 redhat 编辑:程序博客网 时间:2024/05/22 06:08

前面稍微学习了下Strust2基本使用,对Struts2的工作流程以及底层源码完全不懂,今天打算把Struts2的工作流程好好的摸索一遍。

 

1.这是一张网上download的struts2工作流程图,

对上图稍做解释:

1.首先客户端/浏览器发送一个请求到服务器,即HttpServletRequest会经过一系列(Fliter)过滤器(ActionContextCleanUp该过滤器是可选过滤器主要作用就是对ActionContext进行CleanUp操作,不让后续的Fliter清除,延长action中属性的生命周期,以便在jsp中访问。

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {        HttpServletRequest request = (HttpServletRequest) req;        HttpServletResponse response = (HttpServletResponse) res;        try {            if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {                chain.doFilter(request, response);            } else {                prepare.setEncodingAndLocale(request, response);//对locale、encoding进行设置                prepare.createActionContext(request, response);//创建AcionContext,即action上下文                prepare.assignDispatcherToThread();                request = prepare.wrapRequest(request);//对request进行包装                ActionMapping mapping = prepare.findActionMapping(request, response, true);//得到action mapping//如果mapping为空,则不会调用action,会调用下一个过滤器链,直到获取到mapping才调用action                if (mapping == null) {                    boolean handled = execute.executeStaticResourceRequest(request, response);                    if (!handled) {                        chain.doFilter(request, response);                    }//不为空时,则执行action                } else {                    execute.executeAction(request, response, mapping);                }            }        } finally {            prepare.cleanupRequest(request);        }    }    public void destroy() {        prepare.cleanupDispatcher();    }}

在调用完所有的doFilter方法后,核心过滤器StrutsPrepareAndExecuteFilter会清空ActionContext,避免内存泄漏。如果其他过滤器还想使用ValueStack中的sturts属性,如果不使用ActionContextCleanUp便无法得到,说白了就是说清理ActionContext的工作就交给ActionContextCleanUp,其他过滤器不用去管,这样action属性的生命周期就延长了。  ActionContextCleanUp工作原理:

在doFilter中设置一个counter计数器,当请求来的时候回被赋1,然后放到请求中。有了这个计数器,后续的Fliter就不会清空ActionContext了,而是由ActionContextCleanUp这个过滤器负责清除。

private static final String COUNTER = "__cleanup_recursion_counter";try {            UtilTimerStack.push(timerKey);            try {                Integer count = (Integer)request.getAttribute(COUNTER); //从request中取出counter值,当请求来的时候,request作用域中没有counter值,所以被赋null                if (count == null) {                    count = Integer.valueOf(1);//counter值为null,请求刚来,因为赋1                }                else {                    count = Integer.valueOf(count.intValue()+1);//不为1,+1                }                request.setAttribute(COUNTER, count);//把标记的count值放到request作用域当中                //LOG.debug("filtering counter="+count);                chain.doFilter(request, response);//执行doFliter,把请求传给下一个Fliter            } finally {                int counterVal = ((Integer)request.getAttribute(COUNTER)).intValue();//取出counter值                counterVal -= 1;//-1,为清除做准备                request.setAttribute(COUNTER, Integer.valueOf(counterVal));//更新request作用域中的counter值                cleanUp(request);//调用cleanup方法清除数据            }    }//COUNTER>0或非空则不进行清除 protected static void cleanUp(ServletRequest req) {// should we clean up yet?Integer count = (Integer) req.getAttribute(COUNTER);if (count != null && count > 0 ) {if (LOG.isDebugEnabled()) {LOG.debug("skipping cleanup counter="+count);}return;}// always dontClean up the thread request, even if an action hasn't been executedActionContext.setContext(null);Dispatcher.setInstance(null);}
不过在struts2的2.1.3版本该方法已经被摈弃了,而是直接在doFilter最后调用cleanUp这个方法(原理一样):public void cleanupRequest(HttpServletRequest request) {        Integer counterVal = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER);        if (counterVal != null) {            counterVal -= 1;            request.setAttribute(CLEANUP_RECURSION_COUNTER, counterVal);            if (counterVal > 0 ) {                if (log.isDebugEnabled()) {                    log.debug("skipping cleanup counter="+counterVal);                }                return;            }        }        // always clean up the thread request, even if an action hasn't been executed        try {            dispatcher.cleanUpRequest(request);        } finally {            ActionContext.setContext(null);            Dispatcher.setInstance(null);        }    }

这里解释下ActionContext是什么,里面放的是什么:

ActionContext是Struts2的上下文,负责存储action运行产生的数据(主要存储requestsessionapplicationparameters等相关信息),结构是key-value的map集合,可以像map一样进行操作,ActionContext生命周期都是一次Http请求。

Strus2会根据每个执行Http请求的线程来创建对应的ActionContext,即一个线程有一个唯一的ActionContex。可以使用ActionContext.getContext()获取当前线程的ActionContext(actioncontext是threadloacl线程绑定的),可能是这个原因不需要担心Action的线程安全。

static ThreadLocal<ActionContext> actionContext = new ThreadLocal<ActionContext>();private Map<String, Object> context;public ActionContext(Map<String, Object> context) {        this.context = context;}

2.就是核心过滤器StrutsPrepareAndExecuteFilter被调用,它会询问ActionMapper是否要调用Action,调用哪个Action,如果ActionMapper决定需要调用某个Action,StrutsPrepareAndExecuteFilter把请求的处理交给ActionProxy来处理

3.ActionProxy会通过configurationManager询问配置文件,找到相应的Action类。

 4. ActionProxy创建一个ActionInvocation的实例,ActionInvocation实例使用命名模式来调用,在调用Action的过程前后,涉及到相关拦截器(Intercepter)的调用。

<strong>这里把2-4的源码局部源码一起呈现上来:</strong>
//Action的配置信息存储在ActionMapping对象中public class ActionMapping {private String name;private String namespace;private String method;private String extension;private Map<String, Object> params;private Result result;...}ActionInvocation getInvocation(); try {            UtilTimerStack.push(timerKey);            String namespace = mapping.getNamespace(); //从mapping对象获取命名空间            String name = mapping.getName();           //获取请求的action名            String method = mapping.getMethod();       //获取请求方法//得到配置对象            Configuration config = configurationManager.getConfiguration();//根据执行上下文参数,命名空间,名称等创建用户自定义Action的代理对象             ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(                    namespace, name, method, extraContext, true, false);            request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());            // if the ActionMapping says to go straight to a result, do it!//如果配置文件中执行的这个action配置了result,就直接转到resul            if (mapping.getResult() != null) {                Result result = mapping.getResult();                result.execute(proxy.getInvocation());            } else {                proxy.execute();            }            // If there was a previous value stack then set it back onto the request            if (!nullStack) {                request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);            }        } 

5. 一旦Action执行完毕,ActionInvocation负责根据struts.xml中的配置找到对应的返回结果。返回结果通常是一个JSP页面。


Struts 2设计的精巧之处就是使用了Action代理Action代理可以根据系统的配置,加载一系列的拦截器,由拦截器将HttpServletRequest参数解析出来,传入Action

同样,Action处理的结果也是通过拦截器传入HttpServletResponse,然后由HttpServletRequest传给用户。

拦截器是Struts 2框架的核心,通过拦截器,实现了AOP(面向切面编程)。但建议在编写Action的时候,尽量避免将业务逻辑放到其中,尽量减少Action与业务逻辑模块或者组件的耦合程度


Struts2的源码很多,值得深究,可能上述的理解有误或不全面,欢迎各位指出,加油!(其中参考了这篇博文:http://www.cnblogs.com/liuling/p/2013-8-10-01.html)







0 0
原创粉丝点击