在第一篇中,花了主要篇幅探讨了MVC框架中对于编写Model层的约定,约定的目的是为了在Action类中进行自动装配,那么,struts2是如何自动装配Model Bean呢?在阅读struts2的源代码之前,不烦先做几个假设,带着这几个假设去看代码,效率高很多。
至于struts2是不是采用上述三种假设中的一个,符合不符合假设并不重要,重要的是找到了正确的结果。本文探讨的源码基于struts 2.3.4这个最新的发行版本。在探讨的过程中,仍然聚焦在Model层的处理,不详细描述每个细节(其实,以我对struts2框架的了解水平,显然是不可能的 )。
struts 2.3.4以StrutsPrepareAndExecuteFilter作为整个框架处理Request的接入点,从名字可以看出来,它是一个Filter(它真是一个Filter),在init方法中,读取了struts的配置文件,并且创建了Dispatcher、PrepareOperations和ExecuteOperations这三个重要的对象,其中Dispatcher是派发request给代理的工具类,很多实际的功能由这个类完成。
public void init(FilterConfig filterConfig) throws ServletException { InitOperations init = new InitOperations(); try { // FilterHostConfig其实是FilterConfig类的Wrapper,具有功能性的方法都是通过调用FilterConfig的相应方法实现的,主要的关注方法是 //getServletContext()和getInitParameter() FilterHostConfig config = new FilterHostConfig(filterConfig); init.initLogging(config); Dispatcher dispatcher = init.initDispatcher(config); //通过调用此方法,载入struts2框架需要用到配置文件,并创建Dispatcher对象, //Dispatcher是struts2框架中调度的最上层工具类 init.initStaticContentLoader(config, dispatcher); //创建prepare和execute这两个顶层对象 prepare = new PrepareOperations(filterConfig.getServletContext(), dispatcher); execute = new ExecuteOperations(filterConfig.getServletContext(), dispatcher); //被排除执行的action配置 this.excludedPatterns = init.buildExcludedPatternsList(dispatcher); postInit(dispatcher, filterConfig); } finally { init.cleanup(); } }
如果深入的说这个init方法,非要结合strut的配置文件和xwork具有迷你型的DI功能的容器来讨论,上面的只是惊鸿一瞥的把这个复杂载入配置文件的过程带过。
介绍完init方法,再来看一下doFilter方法,这个方法只有20几行代码,这20几行代码就把一个Action对象给执行了(执行前的N个步骤省略....),简洁至极。回到这里所关心的问题,什么时候Action的Model Bean被装配了,仔细的看看这20几行代码,实在是很高深,根本看不出跟装配Model Bean有什么直接关系。猜猜吧,执行装配工作的地方可能存在与这第3、8、16这三行代码之中。
try { prepare.setEncodingAndLocale(request, response); //设置字符集编码和本地化 prepare.createActionContext(request, response); //创建ActionContext prepare.assignDispatcherToThread(); if ( excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) { //如果excludes的,直接交给WEB容器处理 chain.doFilter(request, response); } else { request = prepare.wrapRequest(request); //包装HttpServletRequest类,根据content-type来决定是MultiPartRequestWrapper类,还是StrutsRequestWrapper, ActionMapping mapping = prepare.findActionMapping(request, response, true); //根据request和配置文件,找到一个对应的Action类的ActionMapping对象 if (mapping == null) { boolean handled = execute.executeStaticResourceRequest(request, response); if (!handled) { chain.doFilter(request, response); //只执行静态资源请求后,仍然交给WEB容器 } } else { execute.executeAction(request, response, mapping); //执行action } } } finally { prepare.cleanupRequest(request); //清除在执行Action过程之前向request写入的内容,设置actionContext, invocationContext为null,如果是multipart/form-data请求,清除生产的临时文件。 }
先看第3行,创建ActionContext对象。所谓ActionContext代表了框架处理Action时的上下文,包含了很多数据,主要包括action名,默认的值栈,具有抽象意义的SessionMap,ParameterMap,ApplicationMap, Local,ActionInvocation,Container。ActionContext实例被包装在ThreadLocal对象内,是线程安全的,很多框架也采用ThreadLocal解决多线程并发问题。
下面关键的代码显示,struts框架内,先通过x-work容器创建初始的OnglValueStack对象,此时,OnglValueStack对象是不包含任何和Model层所用的数据,prepare.createActionContext通过调用Dispatcher.createContextMap(...)方法创建的上面列出的map大对象,并且把这些map对象再放到extraContext这个“大”Map对象中去,注意,放的过程中,用到了strut框架里默认的值栈名(key),这些名字取数据的时候会再次用到。
Dispatcher.createContextMap(...)方法其实是struts2框架中Model层预处理的核心部分,有兴趣的读者可以推敲推敲其中的代码。
ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack(); stack.getContext().putAll(dispatcher.createContextMap(request, response, null, servletContext)); ctx = new ActionContext(stack.getContext());
Map<String,Object> extraContext = createContextMap(requestMap, params, session, application, request, response, context);
是不是struts框架非常偏爱Map这个类?这是框架使用了Ongl处理request参数的情形下合理选择。
通过阅读这些代码,我们发现,这个时候Action实例还没有被创建,因此不可能会出现装配的Model Bean的情形。但是,这个时候ActionContext已经具备了和Reqeust脱离的条件,也即在框架下一层的处理中,对于数据上的需要,操作的重点对象不再是Request对象,而是ActionContext对象。
[本文系作者原创,如若转载,请注明出处,新浪微博:@仪山湖]