struts2流程介绍

1、struts2流程介绍

首先要知道struts2是在webwork的技术基础上开发的,采用拦截器的机制来处理用户请求的全新MVC框架。而webwork是建立在xwork的command模式框架之上的基于web的MVC框架。所以总而言之,无论是struts2还是webwork底层都是xwork

从其官方网站的介绍来看,XWork不仅提供了一系列基础构件,其中包括:一个IoC的容器、强大的表达式语言(OGNL)支持、数据类型转化、数据校验框架、可插拔的功能模块(插件模式)及其配置,并且在这一系列的基础构件之上,实现了一套基于Command设计模式的“事件请求执行框架”。 
那么,XWork作为Struts2所依赖的底层核心,使得Struts2只需要关注与Web容器打交道的部分,而把其余的工作交给XWork即可。当Struts2收到一个Http请求时,Struts2只需要接收请求参数,交给XWork完成执行序列,当XWork执行完毕后,将结果交还Struts2返回相应的视图。

我这里的struts2源码是从官网下载的一个最新的struts-2.3.15.1-src.zip,将其解压即可。里面的目录页文件非常的多,我们只需要定位到struts-2.3.15.1\src\core\src\main\java\org\apache\struts2查看源文件。目录结构如下图:

Struts2框架的正常运行,除了占核心地位的xwork的支持以外,Struts2本身也提供了许多类,这些类被分门别类组织到不同的包中。从源代码中发现,基本上每一个Struts2类都访问了WebWork提供的功能,从而也可以看出Struts2与WebWork千丝万缕的联系。但无论如何,Struts2的核心功能比如将请求委托给哪个Action处理都是由xwork完成的,Struts2只是在WebWork的基础上做了适当的简化、加强和封装,并少量保留Struts1.x中的习惯。 
以下是包说明:

根目录下的5个文件说明: 
这里写图片描述

struts2 架构图如下图所示: 

依照上图,我们可以看出一个请求在struts的处理大概有如下步骤:

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

2、struts2源码分析

首先我们使用struts2框架都会在web.xml中注册和映射struts2,配置内容如下:

<span style="color:#000000"><code> <span style="color:#006666"><<span style="color:#4f4f4f">filter</span>></span> 
     <span style="color:#006666"><<span style="color:#4f4f4f">filter-name</span>></span>struts2<span style="color:#006666"></<span style="color:#4f4f4f">filter-name</span>></span>  
     <span style="color:#006666"><<span style="color:#4f4f4f">filterclass</span>></span>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
     <span style="color:#006666"></<span style="color:#4f4f4f">filter-class</span>></span> 
 <span style="color:#006666"></<span style="color:#4f4f4f">filter</span>></span>  
 <span style="color:#006666"><<span style="color:#4f4f4f">filter-mapping</span>></span> 
     <span style="color:#006666"><<span style="color:#4f4f4f">filter-name</span>></span>struts2<span style="color:#006666"></<span style="color:#4f4f4f">filter-name</span>></span>  
     <span style="color:#006666"><<span style="color:#4f4f4f">url-pattern</span>></span>/*<span style="color:#006666"></<span style="color:#4f4f4f">url-pattern</span>></span> 
 <span style="color:#006666"></<span style="color:#4f4f4f">filter-mapping</span>></span></code></span>

注:在早期的struts2中,都是使用FilterDispathcer,从Struts 2.1.3开始,它已不推荐使用。升级到StrutsPrepareAndExecuteFilter。

StrutsPrepareAndExecuteFilter中的方法: 
这里写图片描述

web容器一启动,就会初始化核心过滤器StrutsPrepareAndExecuteFilter,并执行初始化init()方法,初始化方法如下:

<span style="color:#000000"><code> <span style="color:#000088">public</span> <span style="color:#000088">void</span> <span style="color:#009900">init</span>(FilterConfig filterConfig) <span style="color:#000088">throws</span> ServletException {
          InitOperations init = <span style="color:#000088">new</span> InitOperations();
          Dispatcher dispatcher = <span style="color:#000088">null</span>;
          <span style="color:#000088">try</span> {
              <span style="color:#880000">//封装filterConfig,其中有个主要方法getInitParameterNames将参数名字以String格式存储在List中</span>
              FilterHostConfig config = <span style="color:#000088">new</span> FilterHostConfig(filterConfig);
              <span style="color:#880000">//初始化struts内部日志</span>
              init.initLogging(config);
              <span style="color:#880000">//创建dispatcher ,并初始化</span>
              dispatcher = init.initDispatcher(config);
              init.initStaticContentLoader(config, dispatcher);
             <span style="color:#880000">//初始化类属性:prepare 、execute</span>
              prepare = <span style="color:#000088">new</span> PrepareOperations(filterConfig.getServletContext(), dispatcher);
              execute = <span style="color:#000088">new</span> ExecuteOperations(filterConfig.getServletContext(), dispatcher);
              <span style="color:#000088">this</span>.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
             <span style="color:#880000">//回调空的postInit方法</span>
              postInit(dispatcher, filterConfig);
         } <span style="color:#000088">finally</span> {
              <span style="color:#000088">if</span> (dispatcher != <span style="color:#000088">null</span>) {
                 dispatcher.cleanUpAfterInit();
             }
             init.cleanup();
         }
     }</code></span>

关于封装的参数filterConfig,首先看下FilterHostConfig ,源码如下:

<span style="color:#000000"><code>  <span style="color:#000088">public</span> <span style="color:#000088">class</span> <span style="color:#4f4f4f">FilterHostConfig</span> <span style="color:#000088">implements</span> <span style="color:#4f4f4f">HostConfig</span> {

      <span style="color:#000088">private</span> FilterConfig config;
      <span style="color:#880000">//构造方法</span>
      <span style="color:#000088">public</span> <span style="color:#009900">FilterHostConfig</span>(FilterConfig config) {
          <span style="color:#000088">this</span>.config = config;
      }
      <span style="color:#880000">//根据init-param配置的param-name获取param-value的值  </span>
      <span style="color:#000088">public</span> String <span style="color:#009900">getInitParameter</span>(String key) {
         <span style="color:#000088">return</span> config.getInitParameter(key);
     }
     <span style="color:#880000">//返回初始化参数名的迭代器 </span>
     <span style="color:#000088">public</span> Iterator<String> <span style="color:#009900">getInitParameterNames</span>() {
         <span style="color:#000088">return</span> MakeIterator.convert(config.getInitParameterNames());
     }
     <span style="color:#880000">//返回Servlet上下文</span>
     <span style="color:#000088">public</span> ServletContext <span style="color:#009900">getServletContext</span>() {
         <span style="color:#000088">return</span> config.getServletContext();
     }
 }</code></span>

只有短短的几行代码,getInitParameterNames是这个类的核心,将Filter初始化参数名称有枚举类型转为Iterator。此类的主要作为是对filterConfig 封装。

接下来,看下StrutsPrepareAndExecuteFilter中init方法中dispatcher = init.initDispatcher(config)。这是初始化dispatcher的,是通过init对象的initDispatcher方法来初始化的。init是InitOperations类的对象,我们看看InitOperations中initDispatcher方法:

<span style="color:#000000"><code><span style="color:#000088">public</span> Dispatcher <span style="color:#009900">initDispatcher</span>( HostConfig filterConfig ) {
         Dispatcher dispatcher = createDispatcher(filterConfig);
         dispatcher.init();
         <span style="color:#000088">return</span> dispatcher;
     }</code></span>

创建Dispatcher,会读取 filterConfig 中的配置信息,将配置信息解析出来,封装成为一个Map,然后根绝servlet上下文和参数Map构造Dispatcher :

<span style="color:#000000"><code><span style="color:#000088">private</span> Dispatcher createDispatcher( HostConfig filterConfig ) {
          <span style="color:#880000">//存放参数的Map</span>
          <span style="color:#4f4f4f">Map</span><span style="color:#4f4f4f"><</span><span style="color:#4f4f4f">String</span>, <span style="color:#4f4f4f">String</span><span style="color:#4f4f4f">></span> <span style="color:#000088">params</span> <span style="color:#4f4f4f">=</span> <span style="color:#006666">new</span> HashMap<span style="color:#4f4f4f"><</span><span style="color:#4f4f4f">String</span>, <span style="color:#4f4f4f">String</span><span style="color:#4f4f4f">></span>();
          <span style="color:#880000">//将参数存放到Map</span>
          for ( Iterator e <span style="color:#4f4f4f">=</span> filterConfig<span style="color:#4f4f4f">.</span>getInitParameterNames(); e<span style="color:#4f4f4f">.</span>hasNext(); ) {
              <span style="color:#4f4f4f">String</span> name <span style="color:#4f4f4f">=</span> (<span style="color:#4f4f4f">String</span>) e<span style="color:#4f4f4f">.</span>next();
              <span style="color:#4f4f4f">String</span> value <span style="color:#4f4f4f">=</span> filterConfig<span style="color:#4f4f4f">.</span>getInitParameter(name);
              <span style="color:#000088">params</span><span style="color:#4f4f4f">.</span>put(name, value);
          }
         <span style="color:#880000">//根据servlet上下文和参数Map构造Dispatcher </span>
         <span style="color:#000088">return</span> <span style="color:#006666">new</span> Dispatcher(filterConfig<span style="color:#4f4f4f">.</span>getServletContext(), <span style="color:#000088">params</span>);
     }</code></span>

这样dispatcher对象创建完成,接着就是dispatcher对象的初始化,打开Dispatcher类,看到它的init方法如下:

<span style="color:#000000"><code> <span style="color:#000088">public</span> <span style="color:#000088">void</span> init() {

         <span style="color:#000088">if</span> (configurationManager == <span style="color:#000088">null</span>) {
             configurationManager = createConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME);
         }

         <span style="color:#000088">try</span> {
             init_FileManager();
             <span style="color:#880000">//加载org/apache/struts2/default.properties</span>
            init_DefaultProperties(); <span style="color:#880000">// [1]</span>
            <span style="color:#880000">//加载struts-default.xml,struts-plugin.xml,struts.xml</span>
            init_TraditionalXmlConfigurations(); <span style="color:#880000">// [2]</span>
            init_LegacyStrutsProperties(); <span style="color:#880000">// [3]</span>
            <span style="color:#880000">//用户自己实现的ConfigurationProviders类 </span>
            init_CustomConfigurationProviders(); <span style="color:#880000">// [5]</span>
            <span style="color:#880000">//Filter的初始化参数 </span>
            init_FilterInitParameters() ; <span style="color:#880000">// [6]</span>
            init_AliasStandardObjects() ; <span style="color:#880000">// [7]</span>

            Container <span style="color:#000088">container</span> = init_PreloadConfiguration();
            <span style="color:#000088">container</span>.inject(<span style="color:#000088">this</span>);
            init_CheckWebLogicWorkaround(<span style="color:#000088">container</span>);

            <span style="color:#000088">if</span> (!dispatcherListeners.isEmpty()) {
                <span style="color:#000088">for</span> (DispatcherListener l : dispatcherListeners) {
                    l.dispatcherInitialized(<span style="color:#000088">this</span>);
                }
            }
        } <span style="color:#000088">catch</span> (Exception ex) {
            <span style="color:#000088">if</span> (LOG.isErrorEnabled())
                LOG.error(<span style="color:#009900">"Dispatcher initialization failed"</span>, ex);
            <span style="color:#000088">throw</span> <span style="color:#000088">new</span> StrutsException(ex);
        }
    }</code></span>

这里主要是加载一些配置文件的,将按照顺序逐一加载:default.properties,struts-default.xml,struts-plugin.xml,struts.xml,……关于文件是如何加载的,大家可以自己取看源文件,主要是由xwork核心类加载的,代码在xwork-core\src\main\java\com\opensymphony\xwork2\config\providers包里面。

现在,我们回到StrutsPrepareAndExecuteFilter类中,刚才我们分析了StrutsPrepareAndExecuteFilter类的init方法,该方法在web容器一启动就会调用的。当用户访问某个action的时候,首先调用核心过滤器StrutsPrepareAndExecuteFilter的doFilter方法,该方法内容如下:

<span style="color:#000000"><code><span style="color:#000088">public</span> <span style="color:#000088">void</span> <span style="color:#009900">doFilter</span>(ServletRequest req, ServletResponse res, FilterChain chain) <span style="color:#000088">throws</span> IOException, ServletException {

          HttpServletRequest request = (HttpServletRequest) req;
          HttpServletResponse response = (HttpServletResponse) res;

          <span style="color:#000088">try</span> {
              <span style="color:#880000">//设置编码和国际化</span>
              prepare.setEncodingAndLocale(request, response);
              <span style="color:#880000">//创建action上下文</span>
             prepare.createActionContext(request, response);
             prepare.assignDispatcherToThread();
             <span style="color:#000088">if</span> (excludedPatterns != <span style="color:#000088">null</span> && prepare.isUrlExcluded(request, excludedPatterns)) {
                 chain.doFilter(request, response);
             } <span style="color:#000088">else</span> {
                 request = prepare.wrapRequest(request);
                 ActionMapping mapping = prepare.findActionMapping(request, response, <span style="color:#000088">true</span>);
                 <span style="color:#880000">//如果mapping为空,则认为不是调用action,会调用下一个过滤器链,直到获取到mapping才调用action</span>
                 <span style="color:#000088">if</span> (mapping == <span style="color:#000088">null</span>) {
                     <span style="color:#000088">boolean</span> handled = execute.executeStaticResourceRequest(request, response);
                     <span style="color:#000088">if</span> (!handled) {
                         chain.doFilter(request, response);
                     }
                 } <span style="color:#000088">else</span> {
                     <span style="color:#880000">//执行action</span>
                     execute.executeAction(request, response, mapping);
                 }
             }
         } <span style="color:#000088">finally</span> {
             prepare.cleanupRequest(request);
         }
     }</code></span>

下面对doFilter方法中的重点部分一一讲解:

  1. prepare.setEncodingAndLocale(request, response) 
    第8行是调用prepare对象的setEncodingAndLocale方法,prepare是PrepareOperations类的对象,PrepareOperations类是用来做请求准备工作的。我们看下PrepareOperations类中的setEncodingAndLocale方法:
<span style="color:#000000"><code><span style="color:#000088">public</span> void setEncodingAndLocale(HttpServletRequest <span style="color:#4f4f4f">request</span>, HttpServletResponse <span style="color:#4f4f4f">response</span>) {
         dispatcher.prepare(<span style="color:#4f4f4f">request</span>, <span style="color:#4f4f4f">response</span>);
     }</code></span>

在这方法里面我们可以看到它只是调用了dispatcher的prepare方法而已,下面我们看看dispatcher的prepare方法:

<span style="color:#000000"><code>public void prepare(HttpServletRequest request, HttpServletResponse response) {
          String encoding = <span style="color:#006666">null</span>;
          <span style="color:#000088">if</span> (defaultEncoding != <span style="color:#006666">null</span>) {
              encoding = defaultEncoding;
          }
          <span style="color:#008800">//</span> check <span style="color:#000088">for</span> Ajax request to use UTF-<span style="color:#006666">8</span> encoding strictly <span style="color:#009900">http</span>:<span style="color:#008800">//</span>www.w3.org<span style="color:#008800">/TR/XMLHttpRequest/</span><span style="color:#880000">#the-send-method</span>
          <span style="color:#000088">if</span> (<span style="color:#009900">"XMLHttpRequest"</span>.equals(request.getHeader(<span style="color:#009900">"X-Requested-With"</span>))) {
              encoding = <span style="color:#009900">"UTF-8"</span>;
          }

         Locale locale = <span style="color:#006666">null</span>;
         <span style="color:#000088">if</span> (defaultLocale != <span style="color:#006666">null</span>) {
             locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());
         }

         <span style="color:#000088">if</span> (encoding != <span style="color:#006666">null</span>) {
             applyEncoding(request, encoding);
         }

         <span style="color:#000088">if</span> (locale != <span style="color:#006666">null</span>) {
             response.setLocale(locale);
         }

         <span style="color:#000088">if</span> (paramsWorkaroundEnabled) {
             request.getParameter(<span style="color:#009900">"foo"</span>); <span style="color:#008800">//</span> simply read any parameter (existing <span style="color:#000088">or</span> <span style="color:#000088">not</span>) to <span style="color:#009900">"prime"</span> the request
         }
     }</code></span>

我们可以看到该方法只是简单的设置了encoding 和locale ,做的只是一些辅助的工作。

  1. prepare.createActionContext(request, response)

我们回到StrutsPrepareAndExecuteFilter的doFilter方法,看到第10行代码:prepare.createActionContext(request, response);这是action上下文的创建。ActionContext是一个容器,这个容器主要存储request、session、application、parameters等相关信 息。ActionContext是一个线程的本地变量,这意味着不同的action之间不会共享ActionContext,所以也不用考虑线程安全问 题。其实质是一个Map,key是标示request、session、……的字符串,值是其对应的对象,我们可以看到com.opensymphony.xwork2.ActionContext类中时如下定义的:

<span style="color:#000000"><code> <span style="color:#000088">static</span> ThreadLocal<ActionContext> actionContext = <span style="color:#000088">new</span> ThreadLocal<ActionContext>();</code></span>
  •  

我们看下PrepareOperations类的createActionContext方法:

<span style="color:#000000"><code> <span style="color:#880000">/**
       * Creates the action context and initializes the thread local
       */</span>
      <span style="color:#000088">public</span> ActionContext <span style="color:#009900">createActionContext</span>(HttpServletRequest request, HttpServletResponse response) {
          ActionContext ctx;
          Integer counter = <span style="color:#006666">1</span>;
          Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER);
          <span style="color:#000088">if</span> (oldCounter != <span style="color:#000088">null</span>) {
              counter = oldCounter + <span style="color:#006666">1</span>;
         }
         <span style="color:#880000">//此处是从ThreadLocal中获取此ActionContext变量</span>
         ActionContext oldContext = ActionContext.getContext();
         <span style="color:#000088">if</span> (oldContext != <span style="color:#000088">null</span>) {
             <span style="color:#880000">// detected existing context, so we are probably in a forward</span>
             ctx = <span style="color:#000088">new</span> ActionContext(<span style="color:#000088">new</span> HashMap<String, Object>(oldContext.getContextMap()));
         } <span style="color:#000088">else</span> {
             ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();
             stack.getContext().putAll(dispatcher.createContextMap(request, response, <span style="color:#000088">null</span>, servletContext));
             <span style="color:#880000">//stack.getContext()返回的是一个Map<String,Object>,根据此Map构造一个ActionContext</span>
             ctx = <span style="color:#000088">new</span> ActionContext(stack.getContext());
         }
         request.setAttribute(CLEANUP_RECURSION_COUNTER, counter);
         <span style="color:#880000">//将ActionContext存到ThreadLocal </span>
         ActionContext.setContext(ctx);
         <span style="color:#000088">return</span> ctx;
     }</code></span>

上面第18行代码中dispatcher.createContextMap,如何封装相关参数:

<span style="color:#000000"><code>  <span style="color:#000088">public</span> <span style="color:#4f4f4f">Map</span><span style="color:#4f4f4f"><</span><span style="color:#4f4f4f">String</span>,Object<span style="color:#4f4f4f">></span> createContextMap(HttpServletRequest request, HttpServletResponse response,
              ActionMapping mapping, ServletContext context) {

          <span style="color:#880000">// request map wrapping the http request objects</span>
          <span style="color:#4f4f4f">Map</span> requestMap <span style="color:#4f4f4f">=</span> <span style="color:#006666">new</span> RequestMap(request);

          <span style="color:#880000">// parameters map wrapping the http parameters.  ActionMapping parameters are now handled and applied separately</span>
          <span style="color:#4f4f4f">Map</span> <span style="color:#000088">params</span> <span style="color:#4f4f4f">=</span> <span style="color:#006666">new</span> HashMap(request<span style="color:#4f4f4f">.</span>getParameterMap());

         <span style="color:#880000">// session map wrapping the http session</span>
         <span style="color:#4f4f4f">Map</span> session <span style="color:#4f4f4f">=</span> <span style="color:#006666">new</span> SessionMap(request);

         <span style="color:#880000">// application map wrapping the ServletContext</span>
         <span style="color:#4f4f4f">Map</span> application <span style="color:#4f4f4f">=</span> <span style="color:#006666">new</span> ApplicationMap(context);
         <span style="color:#880000">//requestMap、params、session等Map封装成为一个上下文Map </span>
         <span style="color:#4f4f4f">Map</span><span style="color:#4f4f4f"><</span><span style="color:#4f4f4f">String</span>,Object<span style="color:#4f4f4f">></span> extraContext <span style="color:#4f4f4f">=</span> createContextMap(requestMap, <span style="color:#000088">params</span>, session, application, request, response, context);

         <span style="color:#000088">if</span> (mapping <span style="color:#4f4f4f">!=</span> <span style="color:#4f4f4f">null</span>) {
             extraContext<span style="color:#4f4f4f">.</span>put(ServletActionContext<span style="color:#4f4f4f">.</span>ACTION_MAPPING, mapping);
         }
         <span style="color:#000088">return</span> extraContext;
     }</code></span>
  1. request = prepare.wrapRequest(request) 
    我们再次回到StrutsPrepareAndExecuteFilter的doFilter方法中,看到第15行:request = prepare.wrapRequest(request);这一句是对request进行包装的,我们看下prepare的wrapRequest方法: 
      
<span style="color:#000000"><code><span style="color:#000088">public</span> HttpServletRequest <span style="color:#009900">wrapRequest</span>(HttpServletRequest oldRequest) <span style="color:#000088">throws</span> ServletException {
          HttpServletRequest request = oldRequest;
          <span style="color:#000088">try</span> {
              <span style="color:#880000">// Wrap request first, just in case it is multipart/form-data</span>
              <span style="color:#880000">// parameters might not be accessible through before encoding (ww-1278)</span>
              request = dispatcher.wrapRequest(request, servletContext);
          } <span style="color:#000088">catch</span> (IOException e) {
              <span style="color:#000088">throw</span> <span style="color:#000088">new</span> ServletException(<span style="color:#009900">"Could not wrap servlet request with MultipartRequestWrapper!"</span>, e);
          }
         <span style="color:#000088">return</span> request;
     }</code></span>

由第6行我们可以看到它里面调用的是dispatcher的wrapRequest方法,并且将servletContext对象也传进去了,我们看下dispatcher的wrapRequest:

 此次包装根据请求内容的类型不同,返回不同的对象,如果为multipart/form-data类型,则返回MultiPartRequestWrapper类型的对象,该对象服务于文件上传,否则返回StrutsRequestWrapper类型的对象,MultiPartRequestWrapper是StrutsRequestWrapper的子类,而这两个类都是HttpServletRequest接口的实现。

  1. ActionMapping mapping = prepare.findActionMapping(request, response, true)

包装request后,通过ActionMapper的getMapping()方法得到请求的Action,Action的配置信息存储在ActionMapping对象中,如StrutsPrepareAndExecuteFilter的doFilter方法中第16行:ActionMapping mapping = prepare.findActionMapping(request, response, true);我们找到prepare对象的findActionMapping方法:

<span style="color:#000000"><code> <span style="color:#000088">public</span> ActionMapping <span style="color:#009900">findActionMapping</span>(HttpServletRequest request, HttpServletResponse response, <span style="color:#000088">boolean</span> forceLookup) {
          <span style="color:#880000">//首先从request对象中取mapping对象,看是否存在</span>
          ActionMapping mapping = (ActionMapping) request.getAttribute(STRUTS_ACTION_MAPPING_KEY);
          <span style="color:#880000">//不存在就创建一个</span>
          <span style="color:#000088">if</span> (mapping == <span style="color:#000088">null</span> || forceLookup) {
              <span style="color:#000088">try</span> {
                  <span style="color:#880000">//首先创建ActionMapper对象,通过ActionMapper对象创建mapping对象</span>
                  mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager());
                  <span style="color:#000088">if</span> (mapping != <span style="color:#000088">null</span>) {
                     request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping);
                 }
             } <span style="color:#000088">catch</span> (Exception ex) {
                 dispatcher.sendError(request, response, servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);
             }
         }

         <span style="color:#000088">return</span> mapping;
     }</code></span>

下面是ActionMapper接口的实现类DefaultActionMapper的getMapping()方法的源代码:

<span style="color:#000000"><code>  <span style="color:#000088">public</span> ActionMapping <span style="color:#009900">getMapping</span>(HttpServletRequest request, ConfigurationManager configManager) {
          ActionMapping mapping = <span style="color:#000088">new</span> ActionMapping();
          <span style="color:#880000">//获得请求的uri,即请求路径URL中工程名以后的部分,如/userAction.action</span>
          String uri = getUri(request);
          <span style="color:#880000">//修正url的带;jsessionid 时找不到的bug</span>
          <span style="color:#000088">int</span> indexOfSemicolon = uri.indexOf(<span style="color:#009900">";"</span>);
          uri = (indexOfSemicolon > -<span style="color:#006666">1</span>) ? uri.substring(<span style="color:#006666">0</span>, indexOfSemicolon) : uri;
          <span style="color:#880000">//删除扩展名,如.action或者.do</span>
          uri = dropExtension(uri, mapping);
         <span style="color:#000088">if</span> (uri == <span style="color:#000088">null</span>) {
             <span style="color:#000088">return</span> <span style="color:#000088">null</span>;
         }
         <span style="color:#880000">//从uri中分离得到请求的action名、命名空间。</span>
         parseNameAndNamespace(uri, mapping, configManager);
         <span style="color:#880000">//处理特殊的请求参数,将这些参数存储在一个HashSet中</span>
         handleSpecialParameters(request, mapping);
         <span style="color:#880000">//如果允许动态方法调用,即形如/userAction!getAll.action的请求,分离action名和方法名</span>
         <span style="color:#000088">return</span> parseActionName(mapping);
     }</code></span>

可以看到,该方法从请求的uri中分离出name和namespace并存放在mapping中。并对请求action名解析,分离出action和方法名。 
5. execute.executeAction(request, response, mapping) 
上面我们分析完了mapping的获取,继续看doFilter方法:

<span style="color:#000000"><code><span style="color:#880000">//如果mapping为空,则认为不是调用action,会调用下一个过滤器链,直到获取到mapping才调用action</span>
        <span style="color:#000088">if</span> (mapping == <span style="color:#000088">null</span>) {
            <span style="color:#000088">boolean</span> handled = execute.executeStaticResourceRequest(request, response);
            <span style="color:#000088">if</span> (!handled) {
                chain.doFilter(request, response);
            }
        } <span style="color:#000088">else</span> {
            <span style="color:#880000">//执行action</span>
            execute.executeAction(request, response, mapping);
        }</code></span>

如果mapping对象不为空,则会执行action,我们看到上面代码第9行:execute是ExecuteOperations类的对象,ExecuteOperations类在包org.apache.struts2.dispatcher.ng下面,我们找到它里面的executeAction方法:

<span style="color:#000000"><code> <span style="color:#000088">public</span> <span style="color:#000088">void</span> <span style="color:#009900">executeAction</span>(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) <span style="color:#000088">throws</span> ServletException {
         dispatcher.serviceAction(request, response, servletContext, mapping);
     }</code></span>

我们可以看到它里面只是简单的调用了dispatcher的serviceAction方法:我们找到dispatcher的serviceAction方法:

<span style="color:#000000"><code><span style="color:#000088">public</span> <span style="color:#006666">void</span> serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context,
                              ActionMapping mapping) throws ServletException {
        <span style="color:#880000">//封转上下文环境,主要将requestMap、params、session等Map封装成为一个上下文Map</span>
        <span style="color:#4f4f4f">Map</span><span style="color:#4f4f4f"><</span><span style="color:#4f4f4f">String</span>, Object<span style="color:#4f4f4f">></span> extraContext <span style="color:#4f4f4f">=</span> createContextMap(request, response, mapping, context);

        <span style="color:#880000">// If there was a previous value stack, then create a new copy and pass it in to be used by the new Action</span>
        ValueStack <span style="color:#4f4f4f">stack</span> <span style="color:#4f4f4f">=</span> (ValueStack) request<span style="color:#4f4f4f">.</span>getAttribute(ServletActionContext<span style="color:#4f4f4f">.</span>STRUTS_VALUESTACK_KEY);
        boolean nullStack <span style="color:#4f4f4f">=</span> <span style="color:#4f4f4f">stack</span> <span style="color:#4f4f4f">==</span> <span style="color:#4f4f4f">null</span>;
        <span style="color:#000088">if</span> (nullStack) {
            ActionContext ctx <span style="color:#4f4f4f">=</span> ActionContext<span style="color:#4f4f4f">.</span>getContext();
            <span style="color:#000088">if</span> (ctx <span style="color:#4f4f4f">!=</span> <span style="color:#4f4f4f">null</span>) {
                <span style="color:#4f4f4f">stack</span> <span style="color:#4f4f4f">=</span> ctx<span style="color:#4f4f4f">.</span>getValueStack();
            }
        }
        <span style="color:#000088">if</span> (<span style="color:#4f4f4f">stack</span> <span style="color:#4f4f4f">!=</span> <span style="color:#4f4f4f">null</span>) {
            extraContext<span style="color:#4f4f4f">.</span>put(ActionContext<span style="color:#4f4f4f">.</span>VALUE_STACK, valueStackFactory<span style="color:#4f4f4f">.</span>createValueStack(<span style="color:#4f4f4f">stack</span>));
        }

        <span style="color:#4f4f4f">String</span> timerKey <span style="color:#4f4f4f">=</span> <span style="color:#009900">"Handling request from Dispatcher"</span>;
        try {
            UtilTimerStack<span style="color:#4f4f4f">.</span>push(timerKey);
            <span style="color:#4f4f4f">String</span> namespace <span style="color:#4f4f4f">=</span> mapping<span style="color:#4f4f4f">.</span>getNamespace();<span style="color:#880000">//从mapping对象获取命名空间</span>
            <span style="color:#4f4f4f">String</span> name <span style="color:#4f4f4f">=</span> mapping<span style="color:#4f4f4f">.</span>getName();          <span style="color:#880000">//获取请求的action名</span>
            <span style="color:#4f4f4f">String</span> method <span style="color:#4f4f4f">=</span> mapping<span style="color:#4f4f4f">.</span>getMethod();      <span style="color:#880000">//获取请求方法</span>
            <span style="color:#880000">//得到配置对象</span>
            Configuration config <span style="color:#4f4f4f">=</span> configurationManager<span style="color:#4f4f4f">.</span>getConfiguration();
            <span style="color:#880000">//根据执行上下文参数,命名空间,名称等创建用户自定义Action的代理对象  </span>
            ActionProxy proxy <span style="color:#4f4f4f">=</span> config<span style="color:#4f4f4f">.</span>getContainer()<span style="color:#4f4f4f">.</span>getInstance(ActionProxyFactory<span style="color:#4f4f4f">.</span>class)<span style="color:#4f4f4f">.</span>createActionProxy(
                    namespace, name, method, extraContext, <span style="color:#006666">true</span>, <span style="color:#006666">false</span>);

            request<span style="color:#4f4f4f">.</span>setAttribute(ServletActionContext<span style="color:#4f4f4f">.</span>STRUTS_VALUESTACK_KEY, proxy<span style="color:#4f4f4f">.</span>getInvocation()<span style="color:#4f4f4f">.</span>getStack());

            <span style="color:#880000">// if the ActionMapping says to go straight to a result, do it!</span>
            <span style="color:#880000">//如果配置文件中执行的这个action配置了result,就直接转到result</span>
            <span style="color:#000088">if</span> (mapping<span style="color:#4f4f4f">.</span>getResult() <span style="color:#4f4f4f">!=</span> <span style="color:#4f4f4f">null</span>) {
                Result result <span style="color:#4f4f4f">=</span> mapping<span style="color:#4f4f4f">.</span>getResult();
                result<span style="color:#4f4f4f">.</span>execute(proxy<span style="color:#4f4f4f">.</span>getInvocation());
            } <span style="color:#000088">else</span> {
                proxy<span style="color:#4f4f4f">.</span>execute();
            }

            <span style="color:#880000">// If there was a previous value stack then set it back onto the request</span>
            <span style="color:#000088">if</span> (<span style="color:#4f4f4f">!</span>nullStack) {
                request<span style="color:#4f4f4f">.</span>setAttribute(ServletActionContext<span style="color:#4f4f4f">.</span>STRUTS_VALUESTACK_KEY, <span style="color:#4f4f4f">stack</span>);
            }
        } catch (ConfigurationException e) {
            <span style="color:#880000">// WW-2874 Only log error if in devMode</span>
            <span style="color:#000088">if</span> (devMode) {
                <span style="color:#4f4f4f">String</span> reqStr <span style="color:#4f4f4f">=</span> request<span style="color:#4f4f4f">.</span>getRequestURI();
                <span style="color:#000088">if</span> (request<span style="color:#4f4f4f">.</span>getQueryString() <span style="color:#4f4f4f">!=</span> <span style="color:#4f4f4f">null</span>) {
                    reqStr <span style="color:#4f4f4f">=</span> reqStr <span style="color:#4f4f4f">+</span> <span style="color:#009900">"?"</span> <span style="color:#4f4f4f">+</span> request<span style="color:#4f4f4f">.</span>getQueryString();
                }
                <span style="color:#000088">LOG</span><span style="color:#4f4f4f">.</span>error(<span style="color:#009900">"Could not find action or result\n"</span> <span style="color:#4f4f4f">+</span> reqStr, e);
            } <span style="color:#000088">else</span> {
                <span style="color:#000088">if</span> (<span style="color:#000088">LOG</span><span style="color:#4f4f4f">.</span>isWarnEnabled()) {
                    <span style="color:#000088">LOG</span><span style="color:#4f4f4f">.</span>warn(<span style="color:#009900">"Could not find action or result"</span>, e);
                }
            }
            sendError(request, response, context, HttpServletResponse<span style="color:#4f4f4f">.</span>SC_NOT_FOUND, e);
        } catch (Exception e) {
            <span style="color:#000088">if</span> (handleException <span style="color:#4f4f4f">||</span> devMode) {
                sendError(request, response, context, HttpServletResponse<span style="color:#4f4f4f">.</span>SC_INTERNAL_SERVER_ERROR, e);
            } <span style="color:#000088">else</span> {
                throw <span style="color:#006666">new</span> ServletException(e);
            }
        } finally {
            UtilTimerStack<span style="color:#4f4f4f">.</span>pop(timerKey);
        }
    }</code></span>

核心代码就是根据前面的请求准备工作,为用户创建ActionProxy对象,执行相应的action,若配置有执行结果,那么返回执行结果。

猜你喜欢

转载自blog.csdn.net/weixin_40657079/article/details/83017991