struts2工作原理

读者如果曾经学习 过Struts1.x或者有过Struts1.x的开发经验,那么千万不要想当然地以为这一章可以跳过。实际上Struts1.x与Struts2并无我 们想象的血缘关系。虽然Struts2的开发小组极力保留Struts1.x的习惯,但因为Struts2的核心设计 完全改变,从思想到设计到工作流程,都有了很大的不同。
Struts2是Struts社区和WebWork社区的共同成果,我们甚至可以 说,Struts2是WebWork的升级版,他采用的正是WebWork的核心,所以,Struts2并不是一个不成熟的产品,相反,构建在 WebWork基础之上的Struts2是一个运行稳定、性能优异、设计成熟的WEB 框架。
本章主要对Struts的源代码进行分析,因为Struts2与WebWork的关系如此密不可分,因此,读者需要下载xwork的源代码,访问http://www.opensymphony.com/xwork/download.action 即可自行下载。
下 载的Struts2源代码文件是一个名叫struts-2.1.0-src.zip的压缩包,里面的目录和文件非常多,读者可以定位到struts- 2.1.0-src"struts-2.0.10"src"core"src"main"java 目录下查看Struts2的源文件,如图14所示。

(图14)
struts2工作原理

主要的包和类
Struts2 框架的正常运行,除了占核心地位的xwork的支持以外,Struts2本身 也提供了许多类,这些类被分门别类组织到不同的包中。从源代码中发现,基本上每一个Struts2类都访问了WebWork提供的功能,从而也可以看出 Struts2与WebWork千丝万缕的联系。但无论如何,Struts2的核心功能比如将请求委托给哪个Action处理都是由xwork完成 的,Struts2只是在WebWork的基础上做了适当的简化、加强和封装,并少量保留Struts1.x中的习惯。
以下是对各包的简要说明:
包名
说明
org.apache.struts2. components
该包封装视图组件,Struts2在视图组件上有了很大加强,不仅增加了组件的属性个数,更新增了几个非常有用的组件,如updownselect、doubleselect、datetimepicker、token、tree等。
另外,Struts2可视化视图组件开始支持主题(theme),缺省情况下,使用自带的缺省主题,如果要自定义页面效果,需要将组件的theme属性设置为simple。
org.apache.struts2. config
该包定义与配置相关的接口和类。实际上,工程中的xml和properties文件的读取和解析都是由WebWork完成的,Struts只做了少量的工作。
org.apache.struts2.dispatcher
Struts2的核心包,最重要的类都放在该包中。
org.apache.struts2.impl
该包只定义了3个类,他们是StrutsActionProxy、StrutsActionProxyFactory、StrutsObjectFactory,这三个类都是对xwork的扩展。
org.apache.struts2.interceptor
定义内置的截拦器。
org.apache.struts2.util
实用包。
org.apache.struts2.validators
只定义了一个类:DWRValidator。
org.apache.struts2.views
提供freemarker、jsp、velocity等不同类型的页面呈现。
下表是对一些重要类的说明:
类名
说明
org.apache.struts2.dispatcher. Dispatcher
      该类有两个作用:
       1、初始化
       2、调用指定的Action的execute()方法。
org.apache.struts2.dispatcher. FilterDispatcher
      这是一个过滤器。文档中已明确说明,如果没有经验,配置时请将url-pattern的值设成/*。
      该类有四个作用:
       1、执行Action
       2、清理ActionContext,避免内存泄漏
       3、处理静态内容(Serving static content)
       4、为请求启动xwork’s的截拦器链。
com.opensymphony.xwork2. ActionProxy
       Action的代理接口。
com.opensymphony.xwork2. ctionProxyFactory
      生产ActionProxy的工厂。
com.opensymphony.xwork2.ActionInvocation
      负责调用Action和截拦器。
com.opensymphony.xwork2.config.providers. XmlConfigurationProvider
      负责Struts2的配置文件的解析。

Struts2的工作机制
3.1Struts2体系结构图
       Strut2的体系结构如图15所示:

      (图15)

3.2Struts2的工作机制
      从图15可以看出,一个请求在Struts2框架中的处理大概分为以下几个步骤:
1、客户端初始化一个指向Servlet容器(例如Tomcat)的请求;
2、这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个叫做ActionContextCleanUp的可选过滤器,这个过滤器对于Struts2和其他框架的集成很有帮助,例如:SiteMesh Plugin);
3、接着FilterDispatcher被调用,FilterDispatcher询问ActionMapper来决定这个请求是否需要调用某个Action;
4、如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求的处理交给ActionProxy;
5、ActionProxy通过Configuration Manager询问框架的配置文件,找到需要调用的Action类;
6、ActionProxy创建一个ActionInvocation的实例。
7、ActionInvocation实例使用命名模式来调用,在调用Action的过程前后,涉及到相关拦截器(Intercepter)的调用。
8、 一旦Action执行完毕,ActionInvocation负责根据 struts.xml中的配置找到对应的返回结果。返回结果通常是(但不总是,也可能是另外的一个Action链)一个需要被表示的JSP或者 FreeMarker的模版。在表示的过程中可以使用Struts2框架中继承的标签。在这个过程中需要涉及到ActionMapper。

注:以上步骤参考至网上,具体网址已忘记。在此表示感谢!

3.3Struts2源代码分析
      和Struts1.x不同,Struts2的启动是通过FilterDispatcher过滤器实现的。下面是该过滤器在web.xml文件中的配置:
代码清单6:web.xml(截取)

   <filter>
      <filter-name>struts2</filter-name>
      <filter-class>
           org.apache.struts2.dispatcher.FilterDispatcher
      </filter-class>
   </filter>
   <filter-mapping>
      <filter-name>struts2</filter-name>
      <url-pattern>/*</url-pattern>
   </filter-mapping>

       Struts2建议,在对Struts2的配置尚不熟悉的情况下,将url-pattern配置为/*,这样该过滤器将截拦所有请求。
      实际上,FilterDispatcher除了实现Filter接口以外,还实现了StrutsStatics接口,继承代码如下:
代码清单7:FilterDispatcher结构

publicclassFilterDispatcherimplementsStrutsStatics, Filter {
}

StrutsStatics 并没有定义业务方法,只定义了若干个常量。Struts2对常用 的接口进行了重新封装,比如HttpServletRequest、HttpServletResponse、HttpServletContext等。  以下是StrutsStatics的定义:
代码清单8:StrutsStatics.java

publicinterfaceStrutsStatics {
   /**
    *ConstantfortheHTTPrequestobject.
    */
   publicstaticfinalStringHTTP_REQUEST="com.opensymphony.xwork2.dispatcher.HttpServletRequest";
   /**
    *ConstantfortheHTTPresponseobject.
    */
   publicstaticfinalStringHTTP_RESPONSE="com.opensymphony.xwork2.dispatcher.HttpServletResponse";
   /**
    *ConstantforanHTTPrequest dispatcher}.
    */
   publicstaticfinalStringSERVLET_DISPATCHER="com.opensymphony.xwork2.dispatcher.ServletDispatcher";
   /**
    *Constantfortheservlet context}object.
    */
   publicstaticfinalStringSERVLET_CONTEXT="com.opensymphony.xwork2.dispatcher.ServletContext";
   /**
    *ConstantfortheJSPpage context}.
    */
publicstaticfinalStringPAGE_CONTEXT="com.opensymphony.xwork2.dispatcher.PageContext";
   /**ConstantforthePortletContextobject*/
   publicstaticfinalStringSTRUTS_PORTLET_CONTEXT="struts.portlet.context";
}

   容器启动后,FilterDispatcher被实例化,调用 init(FilterConfig filterConfig)方法。该方法创建Dispatcher类的对象,并且将FilterDispatcher配置的初始化参数传到对象中(详情请 参考代码清单10),并负责Action的执行。然后得到参数packages,值得注意的是,还有另外三个固定的包和该参数进行拼接,分别是 org.apache.struts2.static、template、和 org.apache.struts2.interceptor.debugging,中间用空格隔开,经过解析将包名变成路径后存储到一个名叫 pathPrefixes的数组中,这些目录中的文件会被自动搜寻。
代码清单9:FilterDispatcher.init()方法

   publicvoidinit(FilterConfig filterConfig)throwsServletException {
       this.filterConfig = filterConfig;     
       dispatcher = createDispatcher(filterConfig);
       dispatcher.init();     
       String param = filterConfig.getInitParameter("packages");
       String packages ="org.apache.struts2.static template org.apache.struts2.interceptor.debugging";
       if(param !=null) {
            packages = param +" "+ packages;
       }
       this.pathPrefixes= parse(packages);
}

代码清单10:FilterDispatcher.createDispatcher()方法

   protectedDispatcher createDispatcher(FilterConfig filterConfig) {
       Map<String,String> params =newHashMap<String,String>();
       for(Enumeration e = filterConfig.getInitParameterNames(); e.hasMoreElements(); ) {
           String name = (String) e.nextElement();
           String value = filterConfig.getInitParameter(name);
           params.put(name, value);
       }
       returnnewDispatcher(filterConfig.getServletContext(), params);
   }

   当用户向Struts2发送请求时,FilterDispatcher的 doFilter()方法自动调用,这个方法非常关键。首先,Struts2对请求对象进行重新包装,此次包装根据请求内容的类型不同,返回不同的对象, 如果为multipart/form-data类型,则返回MultiPartRequestWrapper类型的对象,该对象服务于文件上传,否则返回 StrutsRequestWrapper类型的对象,MultiPartRequestWrapper是StrutsRequestWrapper的子 类,而这两个类都是HttpServletRequest接口的实现。包装请求对象如代码清单11所示:
代码清单11:FilterDispatcher.prepareDispatcherAndWrapRequest()方法\

 

 

 

1. Struts2架构图  
这是Struts2官方站点提供的Struts 2 的整体结构。 

struts2工作原理  
2. Struts2部分类介绍 
这部分从Struts2参考文档中翻译就可以了。 
ActionMapper 
        ActionMapper其实是HttpServletRequest和Action调用请求的一个映射,它屏蔽了Action对于Request等 java Servlet类的依赖。Struts2中它的默认实现类是DefaultActionMapper,ActionMapper很大的用处可以根据自己的 需要来设计url格式,它自己也有Restful的实现,具体可以参考文档的docs\actionmapper.html。 
ActionProxy&ActionInvocation 
        Action的一个代理,由ActionProxyFactory创建,它本身不包括Action实例,默认实现DefaultActionProxy是 由ActionInvocation持有Action实例。ActionProxy作用是如何取得Action,无论是本地还是远程。而 ActionInvocation的作用是如何执行Action,拦截器的功能就是在ActionInvocation中实现的。 
ConfigurationProvider&Configuration 
        ConfigurationProvider就是Struts2中配置文件的解析器,Struts2中的配置文件主要是尤其实现类 XmlConfigurationProvider及其子类StrutsXmlConfigurationProvider来解析。 

3. Struts2请求流程 
1、客户端发送请求 
2、请求先通过ActionContextCleanUp-->FilterDispatcher 
3、FilterDispatcher通过ActionMapper来决定这个Request需要调用哪个Action 
4、如果ActionMapper决定调用某个Action,FilterDispatcher把请求的处理交给ActionProxy,这儿已经转到它的Delegate--Dispatcher来执行 
5、ActionProxy根据ActionMapping和ConfigurationManager找到需要调用的Action类 
6、ActionProxy创建一个ActionInvocation的实例 
7、ActionInvocation调用真正的Action,当然这涉及到相关拦截器的调用 
8、Action执行完毕,ActionInvocation创建Result并返回,当然,如果要在返回之前做些什么,可以实现PreResultListener。添加PreResultListener可以在Interceptor中实现。 


另一个版本:大同小异~ 
一个请求在Struts2框架中的处理大概分为以下几个步骤: 
1. 客户端提起一个(HttpServletRequest)请求,如上文在浏览器中输入”http://localhost:8080/TestMvc/add.action”就是提起一个(HttpServletRequest)请求。 
2. 请 求被提交到一系列(主要是三层)的过滤器(Filter),如(ActionContextCleanUp、其他过滤器(SiteMesh等)、 FilterDispatcher)。注意这里是有顺序的,先ActionContextCleanUp,再其他过滤器(SiteMesh等)、最后到 FilterDispatcher。 
3. FilterDispatcher是控制器的核心,就是mvc中c控制层的核心。下面粗略的分析下我理解的FilterDispatcher工作流程和原理:FilterDispatcher进行初始化并启用核心doFilter 

其代码如下: 

Java代码   收藏代码
  1. public   void  doFilter(ServletRequest req, ServletResponse res, FilterChain chain)  throws  IOException, ServletException ...{   
  2.         HttpServletRequest request = (HttpServletRequest) req;   
  3.         HttpServletResponse response = (HttpServletResponse) res;   
  4.         ServletContext servletContext = filterConfig.getServletContext();   
  5.         // 在这里处理了HttpServletRequest和HttpServletResponse。    
  6.         DispatcherUtils du = DispatcherUtils.getInstance();   
  7.         du.prepare(request, response);//正如这个方法名字一样进行locale、encoding以及特殊request parameters设置    
  8.         try  ...{   
  9.             request = du.wrapRequest(request, servletContext);//对request进行包装    
  10.         } catch  (IOException e) ...{   
  11.             String message = "Could not wrap servlet request with MultipartRequestWrapper!" ;   
  12.             LOG.error(message, e);   
  13.             throw   new  ServletException(message, e);   
  14.         }   
  15.                 ActionMapperIF mapper = ActionMapperFactory.getMapper();//得到action的mapper    
  16.         ActionMapping mapping = mapper.getMapping(request);// 得到action 的 mapping    
  17.         if  (mapping ==  null ) ...{   
  18.             // there is no action in this request, should we look for a static resource?    
  19.             String resourcePath = RequestUtils.getServletPath(request);   
  20.             if  ( "" .equals(resourcePath) &&  null  != request.getPathInfo()) ...{   
  21.                 resourcePath = request.getPathInfo();   
  22.             }   
  23.             if  ( "true" .equals(Configuration.get(WebWorkConstants.WEBWORK_SERVE_STATIC_CONTENT))    
  24.                     && resourcePath.startsWith("/webwork" )) ...{   
  25.                 String name = resourcePath.substring("/webwork" .length());   
  26.                 findStaticResource(name, response);   
  27.             } else  ...{   
  28.                 // this is a normal request, let it pass through    
  29.                 chain.doFilter(request, response);   
  30.             }   
  31.             // WW did its job here    
  32.             return ;   
  33.         }   
  34.         Object o = null ;   
  35.         try  ...{   
  36.             //setupContainer(request);    
  37.             o = beforeActionInvocation(request, servletContext);   
  38.            //整个框架最最核心的方法,下面分析    
  39.             du.serviceAction(request, response, servletContext, mapping);   
  40.         } finally  ...{   
  41.             afterActionInvocation(request, servletContext, o);   
  42.             ActionContext.setContext(null );   
  43.         }   
  44.     }   
  45. du.serviceAction(request, response, servletContext, mapping);   
  46. //这个方法询问ActionMapper是否需要调用某个Action来处理这个(request)请求,如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求的处理交给ActionProxy    
  47. public   void  serviceAction(HttpServletRequest request, HttpServletResponse response, String namespace, String actionName, Map requestMap, Map parameterMap, Map sessionMap, Map applicationMap) ...{    
  48.         HashMap extraContext = createContextMap(requestMap, parameterMap, sessionMap, applicationMap, request, response, getServletConfig());  //实例化Map请求 ,询问ActionMapper是否需要调用某个Action来处理这个(request)请求    
  49.         extraContext.put(SERVLET_DISPATCHER, this );    
  50.         OgnlValueStack stack = (OgnlValueStack) request.getAttribute(ServletActionContext.WEBWORK_VALUESTACK_KEY);    
  51.         if  (stack !=  null ) ...{    
  52.             extraContext.put(ActionContext.VALUE_STACK,new  OgnlValueStack(stack));    
  53.         }    
  54.         try  ...{    
  55.             ActionProxy proxy = ActionProxyFactory.getFactory().createActionProxy(namespace, actionName, extraContext);    
  56. //这里actionName是通过两道getActionName解析出来的, FilterDispatcher把请求的处理交给ActionProxy,下面是ServletDispatcher的 TODO:     
  57.             request.setAttribute(ServletActionContext.WEBWORK_VALUESTACK_KEY, proxy.getInvocation().getStack());    
  58.             proxy.execute();    
  59.            //通过代理模式执行ActionProxy    
  60.             if  (stack !=  null )...{    
  61.                 request.setAttribute(ServletActionContext.WEBWORK_VALUESTACK_KEY,stack);    
  62.             }    
  63.         } catch  (ConfigurationException e) ...{    
  64.             log.error("Could not find action" , e);    
  65.             sendError(request, response, HttpServletResponse.SC_NOT_FOUND, e);    
  66.         } catch  (Exception e) ...{    
  67.             log.error("Could not execute action" , e);    
  68.             sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);    
  69.         }    
  70. }    




4. FilterDispatcher询问ActionMapper是否需要调用某个Action来处理这个(request)请求,如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求的处理交给ActionProxy。 
5. ActionProxy通过Configuration Manager(struts.xml)询问框架的配置文件,找到需要调用的Action类. 
如上文的struts.xml配置 

Java代码   收藏代码
  1. <?xml version= "1.0"  encoding= "GBK" ?>   
  2.  <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"   "http://struts.apache.org/dtds/struts-2.0.dtd" >   
  3.  <struts>   
  4.      <include file="struts-default.xml" />   
  5.      <package  name= "struts2"   extends = "struts-default" >   
  6.          <action name="add"     
  7.              class = "edisundong.AddAction"  >   
  8.              <result>add.jsp</result>   
  9.          </action>       
  10.      </package >   
  11.  </struts>   


如果提交请求的是add.action,那么找到的Action类就是edisundong.AddAction。 
6. ActionProxy 创建一个ActionInvocation的实例,同时ActionInvocation通过代理模式调用Action。但在调用之前 ActionInvocation会根据配置加载Action相关的所有Interceptor。(Interceptor是struts2另一个核心级 的概念) 

下面我们来看看ActionInvocation是如何工作的: 

ActionInvocation 是Xworks 中Action 调度的核心。而对Interceptor 的调度,也正是由ActionInvocation负责。ActionInvocation 是一个接口, 而DefaultActionInvocation 则是Webwork 对ActionInvocation的默认实现。 

Interceptor 的调度流程大致如下: 
1. ActionInvocation初始化时,根据配置,加载Action相关的所有Interceptor。 
2. 通过ActionInvocation.invoke方法调用Action实现时,执行Interceptor。 

Interceptor 将很多功能从我们的Action中独立出来,大量减少了我们Action的代码,独立出来的行为具有很好的重用性。XWork、WebWork的许多功能 都是有Interceptor实现,可以在配置文件中组装Action用到的Interceptor,它会按照你指定的顺序,在Action执行前后运 行。 
那么什么是拦截器。 
拦截器就是AOP(Aspect-Oriented Programming)的一种实现。(AOP是指用于在某个方法或字段被访问之前,进行拦截然后在之前或之后加入某些操作。) 
拦截器的例子这里就不展开了。 
struts-default.xml文件摘取的内容:

Java代码   收藏代码
  1. < interceptor name = "alias"   class  = "com.opensymphony.xwork2.interceptor.AliasInterceptor"  />    
  2. < interceptor name ="autowiring"   class  = "com.opensymphony.xwork2.spring.interceptor.ActionAutowiringInterceptor"  />    
  3. < interceptor name ="chain"   class  = "com.opensymphony.xwork2.interceptor.ChainingInterceptor"  />    
  4. < interceptor name ="conversionError"   class  = "org.apache.struts2.interceptor.StrutsConversionErrorInterceptor"  />    
  5. < interceptor name ="createSession"   class  = "org.apache.struts2.interceptor.CreateSessionInterceptor"  />    
  6. < interceptor name ="debugging"   class  = "org.apache.struts2.interceptor.debugging.DebuggingInterceptor"  />    
  7. < interceptor name ="external-ref"   class  = "com.opensymphony.xwork2.interceptor.ExternalReferencesInterceptor"  />    
  8. < interceptor name ="execAndWait"   class  = "org.apache.struts2.interceptor.ExecuteAndWaitInterceptor"  />    
  9. < interceptor name ="exception"   class  = "com.opensymphony.xwork2.interceptor.ExceptionMappingInterceptor"  />    
  10. < interceptor name ="fileUpload"   class  = "org.apache.struts2.interceptor.FileUploadInterceptor"  />    
  11. < interceptor name ="i18n"   class  = "com.opensymphony.xwork2.interceptor.I18nInterceptor"  />    
  12. < interceptor name ="logger"   class  = "com.opensymphony.xwork2.interceptor.LoggingInterceptor"  />    
  13. < interceptor name ="model-driven"   class  = "com.opensymphony.xwork2.interceptor.ModelDrivenInterceptor"  />    
  14. < interceptor name ="scoped-model-driven"   class  = "com.opensymphony.xwork2.interceptor.ScopedModelDrivenInterceptor"  />    
  15. < interceptor name ="params"   class  = "com.opensymphony.xwork2.interceptor.ParametersInterceptor"  />    
  16. < interceptor name ="prepare"   class  = "com.opensymphony.xwork2.interceptor.PrepareInterceptor"  />    
  17. < interceptor name ="static-params"   class  = "com.opensymphony.xwork2.interceptor.StaticParametersInterceptor"  />    
  18. < interceptor name ="scope"   class  = "org.apache.struts2.interceptor.ScopeInterceptor"  />    
  19. < interceptor name ="servlet-config"   class  = "org.apache.struts2.interceptor.ServletConfigInterceptor"  />    
  20. < interceptor name ="sessionAutowiring"   class  = "org.apache.struts2.spring.interceptor.SessionContextAutowiringInterceptor"  />    
  21. < interceptor name ="timer"   class  = "com.opensymphony.xwork2.interceptor.TimerInterceptor"  />    
  22. < interceptor name ="token"   class  = "org.apache.struts2.interceptor.TokenInterceptor"  />    
  23. < interceptor name ="token-session"   class  = "org.apache.struts2.interceptor.TokenSessionStoreInterceptor"  />    
  24. < interceptor name ="validation"   class  = "com.opensymphony.xwork2.validator.ValidationInterceptor"  />    
  25. < interceptor name ="workflow"   class  = "com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor"  />    
  26. < interceptor name ="store"   class  = "org.apache.struts2.interceptor.MessageStoreInterceptor"  />    
  27. < interceptor name ="checkbox"   class  = "org.apache.struts2.interceptor.CheckboxInterceptor"  />    
  28. < interceptor name ="profiling"   class  = "org.apache.struts2.interceptor.ProfilingActivationInterceptor"  />   




7. 一旦Action执行完毕,ActionInvocation负责根据struts.xml中的配置找到对应的返回结果。如上文中将结构返回“add.jsp”,但大部分时候都是返回另外一个action,那么流程又得走一遍………

你可能感兴趣的:(struts2)