当前访客身份:游客 [ 登录  | 注册加入尚学堂]
直播

zuzhijie

拥有积分:335
这家伙太懒,还没有签名!

博客分类

笔记中心

课题中心

提问中心

答题中心

解答题中心

struts2源码分析

zuzhijie 发表于 2月前 (2017-01-10 17:31:26)  |  评论(0)  |  阅读次数(91)| 0 人收藏此文章,   我要收藏   
摘要 struts2源码分析
本文通过分析祥细的分析strut2.31的源代码来加深对struts2的认识。使我们在使用struts2框架的时候更有把握更得心应手。(独到的分析,非常祥细,原创)
struts2的入口就是一个StrutsPrepareAndExecuteFilter 过滤器,网上的也有很多分析struts2的文章了,但在filter入口这里却一笔略过,
其实我觉得先让大家都深入了解filter的原理这后,再一条线索往下,也许这样更能使你的思路更清淅,
所以我借助tomcat中filter的源代码从源头上开始分析,正所谓技术是日新月异的,也许哪天他的实现方式改变了,但是原理这东西是沉甸下来的,原理就是原理。
请耐心地看,也许我有哪些地方分析得不是很好,但我期望指正,分享学习也是一种快乐,让我们一起前进。


     Servlet2.3中引入Filter其实它就是使用了职责链模式(Chain Of Responsibility)的抽象设计了。责链设计模式的意图就是使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
     其实tomcat中管道和过滤器模式就是职责链模式的抽象,把它应用到软件体系架构中。

     用一个通俗例子来说,就好比警察抓犯人,在案件发生后经侦察已经确认此案由一团伙共5个犯人所作,数量已经确定了,打个比方这个犯罪团伙是这样组成的,这5个犯人中只有一个主犯 E,其余四个从犯中只有D这个人认识并能找到主犯,D找了个只有他才认识的C犯,C又找了只有他才认识的B犯,B也是找了只有他才认识的A犯, 所以他们组成了一个犯罪团伙了。正好警察是从抓到 A开始的,接着A招供了B,B招供了C,C招供了D,D最后招供了主犯E了。(A->B->C->D->>E),呵呵,可能比喻得有点勉强了,但你能清楚了解就好,其实为什么突出有个主犯E呢,就好类比tomcat中的Filter 和servlet的执行顺序。(A->B->C->D)都好比如是Filter.doFilter(),链式的执行后,而主犯E就是最后用户定义的Servlet.service()了。

     上面的都是比喻性地说明了一下,从下面开始就结合源代码来分析说明了。

    init(FilterConfig):
     当容器如tomcat启动的时候就会调用的filter初始化方法。在容器启动的时候。它加载应用程序中的配置描述符 web.xml 文件,解析过滤器配置信息。 并获取文件中指定的filter初始化参数。filter顺序是按<filter-mapping>的顺序依次排列保存下来,所以此时filter的数量 n 已经能被计算确定下来了。


在Tomcat处理请求的Servlet实例之前先要处理与之相关联的所有Filter的,在StandardWrapperValve类的#invoke()方法中调用filterChain.doFilter(request.getRequest(), response.getResponse()); 从这开始调用Filter了,

   以下是在ApplicationFilterChain类两个方法的源代码:
Java代码   收藏代码
  1. public void doFilter(ServletRequest request, ServletResponse response) throws IOException,    
  2.             ServletException {    
  3.         
  4.         if (Globals.IS_SECURITY_ENABLED) {    
  5.             final ServletRequest req = request;    
  6.             final ServletResponse res = response;    
  7.             try {    
  8.                 java.security.AccessController    
  9.                         .doPrivileged(new java.security.PrivilegedExceptionAction() {    
  10.                             public Object run() throws ServletException, IOException {    
  11.                                 internalDoFilter(req, res);    
  12.                                 return null;    
  13.                             }    
  14.                         });    
  15.             } catch (PrivilegedActionException pe) {    
  16.               ......  
  17.             }    
  18.         } else {    
  19.             internalDoFilter(request, response);  //这个方法无论怎么样都会被调用  
  20.         }    
  21.     }    
  22.   
  23.   
  24.         private void internalDoFilter(ServletRequest request, ServletResponse response)    
  25.             throws IOException, ServletException {    
  26.         
  27.   
  28.         if (pos < n) {    
  29.   
  30.               //n 就是web.xml定义的Filter的数量,init时已经计算出来,filter的排列顺序按<filter-mapping>的顺序已经定下来,  
  31.               而且保存在filterConfig中了。  
  32.   
  33.             ApplicationFilterConfig filterConfig = filters[pos++];    
  34.             Filter filter = null;    
  35.             try {    
  36.                 // 得到当前Filter    
  37.                 filter = filterConfig.getFilter();    
  38.                 support.fireInstanceEvent(InstanceEvent.BEFORE_FILTER_EVENT, filter, request,    
  39.                         response);    
  40.                 
  41.                 if (Globals.IS_SECURITY_ENABLED) {    
  42.                     final ServletRequest req = request;    
  43.                     final ServletResponse res = response;    
  44.                     Principal principal = ((HttpServletRequest) req).getUserPrincipal();    
  45.         
  46.                     Object[] args = new Object[] { req, res, this };    
  47.                     SecurityUtil.doAsPrivilege("doFilter", filter, classType, args);    
  48.         
  49.                     args = null;    
  50.                 } else {    
  51.   
  52.                     // 这里调用Filter的#doFilter()方法。  注意看这个filter.doFilter(request, response, this); 中  
  53.                     //的this引用指向的就是ApplicationFilterChain类的对象,所以传递给Filter的chain实例就是ApplicationFilterChain类的对象。  
  54.                     filter.doFilter(request, response, this);    
  55.                                         //还记得前面比喻的例子吗, 犯人A记住B,B->C,C->D吗,  
  56.                                        //在Filter的#doFilter()方法里都会调用#chain.doFilter(request,response); 这个方法才能往下继续执行下去,  
  57.                                        //就好比只要有一犯人记不住下一个犯人,这条线索就中断的不会继续往下了。  
  58.                                       // 再来看这个chain的定义,它是ApplicationFilterChain类的的引用,而这个internalDoFilter(..)方法所在的类就是ApplicationFilterChain类中  
  59.                                      //于是程序执行到Filter 中的chain.doFilter(request,response)相当于程序又再次调用本方法,也可以说是一种变相的递归了。  
  60.                 }    
  61.         
  62.                 support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT, filter, request,    
  63.                         response);    
  64.             } catch (Exception e) {    
  65.                ...............  
  66.             } ........省略  
  67.   
  68.             return;  //说明只要符合if(pos < n)这条件,这方法只执行到这了。  
  69.         }    
  70.         
  71.         try {    
  72.             if (Globals.STRICT_SERVLET_COMPLIANCE) {    
  73.                 lastServicedRequest.set(request);    
  74.                 lastServicedResponse.set(response);    
  75.             }    
  76.   
  77.   
  78.   
  79.             // 当Filter全部调用完毕后,而且在最后的一个Filter中有调用#chain.doFilter(request,response);这一行,  
  80.             //才会把请求真正的传递给Servlet了,调用它的#service()方法。    
  81.    
  82.             support.fireInstanceEvent(InstanceEvent.BEFORE_SERVICE_EVENT, servlet, request,    
  83.                     response);    
  84.             if ((request instanceof HttpServletRequest)    
  85.                     && (response instanceof HttpServletResponse)) {    
  86.         
  87.                 if (Globals.IS_SECURITY_ENABLED) {    
  88.                     final ServletRequest req = request;    
  89.                     final ServletResponse res = response;    
  90.                     Principal principal = ((HttpServletRequest) req).getUserPrincipal();    
  91.                     Object[] args = new Object[] { req, res };    
  92.                     SecurityUtil.doAsPrivilege("service", servlet, classTypeUsedInService, args,    
  93.                             principal);    
  94.                     args = null;    
  95.                 } else {    
  96.                     servlet.service((HttpServletRequest) request, (HttpServletResponse) response);    
  97.                 }    
  98.             } else {    
  99.                 servlet.service(request, response);    
  100.             }    
  101.             support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT, servlet, request,    
  102.                             response);    
  103.         } catch (Exception e) {    
  104.           ............  
  105.         }............  
  106.         finally {    
  107.             if (Globals.STRICT_SERVLET_COMPLIANCE) {    
  108.                 lastServicedRequest.set(null);    
  109.                 lastServicedResponse.set(null);    
  110.             }    
  111.         }    
  112.         
  113.     }    

 在Tomcat怎么处理Filter的流程说完了,接下来开始讨论Struts2的了,
Struts2就是由一个 StrutsPrepareAndExecuteFilter也就是一个filter作为控制器入口。所以它要在web.xml中配置。当浏览器发来的请求到达这个 StrutsPrepareAndExecuteFilter了,
就会调用 它的#doFilter()方法:

Java代码   收藏代码
  1. public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {  
  2.   
  3.         HttpServletRequest request = (HttpServletRequest) req;  
  4.         HttpServletResponse response = (HttpServletResponse) res;  
  5.   
  6.         try {  
  7.             prepare.setEncodingAndLocale(request, response);//设置编码和Locale  
  8.             prepare.createActionContext(request, response);  
  9.             prepare.assignDispatcherToThread();//获取Dispatcher的实例然后设置ThreadLocal<Dispatcher> instance中,可见Struts2框架为每一个线程都提供了一个Dispatcher对象,所以在编写Action的时候不需要考虑多线程的问题了。  
  10.   
  11.                if ( excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {  
  12.                     chain.doFilter(request, response);  
  13.                } else {  
  14.                     /* (1) 对请求进行包装 */    
  15.                     request = prepare.wrapRequest(request);  
  16.   
  17.                      /* (2) 获得Action Mapping ,其中包含namespace处理*/    
  18.                     ActionMapping mapping = prepare.findActionMapping(request, response, true);  
  19.                    /* (3)当Mapping为空时,检查是否访问的为静态资源 */  
  20.                     if (mapping == null) {  
  21.                          boolean handled = execute.executeStaticResourceRequest(request, response);  
  22.                          if (!handled) {  
  23.                               chain.doFilter(request, response);  
  24.                          }  
  25.                     } else {  
  26.   
  27.                   /* (4) 如果请求的是action资源,这会执行此方法,在这方法中会调用很多相关的方法做了很多工作(例如execute)和拦截器执行等 */    
  28.                          execute.executeAction(request, response, mapping);  
  29.                     //注意到这里了吗,没有chain.doFilter(request, response);也就是说如果执行的请求的资源是Action,就不会再往下执行到Servlet.service()了。  
  30.                     }  
  31.                }  
  32.         } finally {  
  33.             prepare.cleanupRequest(request);  
  34.         }  
  35.     }  

 

 从上面(1)(2)(3)(4)点分别分开重点分明;
    (1) 对请求进行包装
    (2) 获得Action Mapping 以及怎么处理namespace的。
   (3)如何检查是否访问的为静态资源,还有如果是正常的请求如jsp,js等,则让它真接通过chain.doFilter(..)链下去。
   (4)调用被请求的Action的执行方法。

   下面形如依次分析说明了。
   (1)对请求进行包装:request = prepare.wrapRequest(request);
   public HttpServletRequest wrapRequest(HttpServletRequest oldRequest) throws ServletException {
        HttpServletRequest request = oldRequest;
        try {
            request = dispatcher.wrapRequest(request, servletContext);
       }catch{..}
     }

     以下是Dispatcher中#wrapRequest()源代码:

 

Java代码   收藏代码
  1. public HttpServletRequest wrapRequest(HttpServletRequest request, ServletContext servletContext) throws IOException {  
  2.        // don't wrap more than once  
  3.        if (request instanceof StrutsRequestWrapper) {// 判断request是否是StrutsRequestWrapper的对象,保证对request只包装一次。   
  4.            return request;  
  5.        }  
  6.   
  7.        String content_type = request.getContentType();  
  8.   
  9.        //判断Content-Type是否是multipart/form-data,如果是的话返回一个MultiPartRequestWrapper的对象处理文件上传,  
  10.        //否则返回StrutsRequestWrapper的对象处理普通请求。   
  11.   
  12.        if (content_type != null && content_type.contains("multipart/form-data")) {    
  13.            MultiPartRequest mpr = null;  
  14.            //check for alternate implementations of MultiPartRequest  
  15.            Set<String> multiNames = getContainer().getInstanceNames(MultiPartRequest.class);  
  16.            if (multiNames != null) {  
  17.                for (String multiName : multiNames) {  
  18.                    if (multiName.equals(multipartHandlerName)) {  
  19.                        mpr = getContainer().getInstance(MultiPartRequest.class, multiName);  
  20.                    }  
  21.                }  
  22.            }  
  23.            if (mpr == null ) {  
  24.                mpr = getContainer().getInstance(MultiPartRequest.class);  
  25.            }  
  26.            request = new MultiPartRequestWrapper(mpr, request, getSaveDir(servletContext));  
  27.        } else {  
  28.            request = new StrutsRequestWrapper(request);  
  29.        }  
  30.   
  31.        return request;  
  32.    }  

 

  (2) 获得Action Mapping 以及怎么处理namespace的。
    ActionMapping mapping = prepare.findActionMapping(request, response, true);
  //ActionMapping类,它内部封装了如下6个字段:

 

Java代码   收藏代码
  1. //ActionMapping类,它内部封装了如下6个字段:   
  2.     public class ActionMapping {  
  3.   
  4.     private String name;// Action名  
  5.     private String namespace;// Action所在的名称空间    
  6.     private String method;// 执行方法   
  7.     private String extension;//扩展名如(.action)  
  8.     private Map<String, Object> params;// 可以通过set方法设置的参数    
  9.     private Result result;// 返回的结果类型  
  10.          }  
  11.   
  12.     public ActionMapping findActionMapping(HttpServletRequest request, HttpServletResponse response, boolean forceLookup) {  
  13.         ActionMapping mapping = (ActionMapping) request.getAttribute(STRUTS_ACTION_MAPPING_KEY);  
  14.         if (mapping == null || forceLookup) {  
  15.             try {  
  16.                 mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager());  
  17.                 if (mapping != null) {  
  18.                     request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping);  
  19.                 }  
  20.             } catch (Exception ex) {  
  21.               .....  
  22.             }  
  23.         }  
  24.   
  25.         return mapping;  
  26.     }  

 实质调用的是DefaultActionMapper类的getMapping(..)

Java代码   收藏代码
  1.  public ActionMapping getMapping(HttpServletRequest request,  
  2.                                     ConfigurationManager configManager) {  
  3.         ActionMapping mapping = new ActionMapping();  
  4.         String uri = getUri(request);//下面祥细介绍  
  5.   
  6.         int indexOfSemicolon = uri.indexOf(";");  
  7.         uri = (indexOfSemicolon > -1) ? uri.substring(0, indexOfSemicolon) : uri;  
  8.   
  9.         uri = dropExtension(uri, mapping);//下面祥细介绍  
  10.         if (uri == null) {  
  11.             return null;  
  12.         }  
  13.   
  14.         parseNameAndNamespace(uri, mapping, configManager);//下面祥细介绍  
  15.   
  16.         handleSpecialParameters(request, mapping);//此方法用于处理Struts框架定义的四种特殊的prefix:Method prefix,Action prefix,Redirect prefix,Redirect-action prefix    
  17.   
  18.         if (mapping.getName() == null) {  
  19.             return null;  
  20.         }  
  21.   
  22.         parseActionName(mapping);  
  23.   
  24.         return mapping;  
  25.     }  
  26.   
  27.   
  28.   
  29. protected String getUri(HttpServletRequest request) {  
  30.         // handle http dispatcher includes.  
  31.         String uri = (String) request  
  32.                 .getAttribute("javax.servlet.include.servlet_path");  
  33.         if (uri != null) {  
  34.             return uri;  
  35.         }  
  36.   
  37.         uri = RequestUtils.getServletPath(request);  
  38.         if (uri != null && !"".equals(uri)) {  
  39.             return uri;  
  40.         }  
  41.   
  42.         uri = request.getRequestURI();  
  43.         return uri.substring(request.getContextPath().length());  
  44.     }  

 

getUri()这个方法首先判断请求是否来自于一个jsp的include,
如果是,那么请求request.getAttribute("javax.servlet.include.servlet_path")属性可以获得include的页面uri,
否则通过一般的方法获得请求的uri,最后返回去掉ContextPath的请求路径,
比如http://127.0.0.1:8080/struts2/text/index.jsp?param=1,返回的为/text/index.jsp。去掉了ContextPath和查询字符串等。


 uri = dropExtension(uri);
   负责去掉Action的"扩展名"(默认为"action")并设置在mapping里,源代码如下:

Java代码   收藏代码
  1. protected String dropExtension(String name, ActionMapping mapping) {  
  2.         if (extensions == null) {  
  3.             return name;  
  4.         }  
  5.         for (String ext : extensions) {  
  6.             if ("".equals(ext)) {  
  7.                 int index = name.lastIndexOf('.');  
  8.                 if (index == -1 || name.indexOf('/', index) >= 0) {  
  9.                     return name;  
  10.                 }  
  11.             } else {  
  12.                 String extension = "." + ext;  
  13.                 if (name.endsWith(extension)) {  
  14.                     name = name.substring(0, name.length() - extension.length());  
  15.                     mapping.setExtension(ext);//设置扩展名  
  16.                     return name;  
  17.                 }  
  18.             }  
  19.         }  
  20.         return null;  
  21.     }  

 

此方法用于解析Action的名称和命名空间,并赋给ActionMapping对象。源代码如下:

Java代码   收藏代码
  1. protected void parseNameAndNamespace(String uri, ActionMapping mapping,  
  2.                                          ConfigurationManager configManager) {  
  3.         String namespace, name;  
  4.         int lastSlash = uri.lastIndexOf("/");  
  5.            //首先如果经过前面处理后的uri为空“” ,就如请求contextPath/后就没有“/”了,则设置 namespace = "";  
  6.         if (lastSlash == -1) {  
  7.             namespace = "";  
  8.             name = uri;  
  9.         } else if (lastSlash == 0) {  
  10.             //如果经过前面处理后的uri为/everything ,则设置 namespace = "/";  
  11.             namespace = "/";  
  12.             name = uri.substring(lastSlash + 1);  
  13.         } else if (alwaysSelectFullNamespace) {  
  14.             // alwaysSelectFullNamespace默认为false,代表是否将最后一个"/"前的字符串作为名称空间。    
  15.             namespace = uri.substring(0, lastSlash);// 获得字符串 namespace  
  16.             name = uri.substring(lastSlash + 1);// 获得字符串 name    
  17.         } else {  
  18.   
  19.           //尝试去找在.xml中已经定义好的namespace,默认为空""字符。  
  20.             Configuration config = configManager.getConfiguration();  
  21.             String prefix = uri.substring(0, lastSlash);  
  22.             namespace = "";  
  23.             boolean rootAvailable = false;  
  24.             // Find the longest matching namespace, defaulting to the default  
  25.             //按最长匹配原则,如果struts.xml中的配置的有几个package并且namespace分别为 (如namespace=/first 。namespace=/first/second)  
  26.             //请求的uri如果是/first/second/text.action 则先找到最长匹配namespace=/first/second的package  
  27.             for (Object cfg : config.getPackageConfigs().values()) {  
  28.                 String ns = ((PackageConfig) cfg).getNamespace();  
  29.                 if (ns != null && prefix.startsWith(ns) && (prefix.length() == ns.length() || prefix.charAt(ns.length()) == '/')) {  
  30.                     if (ns.length() > namespace.length()) {  
  31.                         namespace = ns;  
  32.                     }  
  33.                 }  
  34.                 if ("/".equals(ns)) {  
  35.                     rootAvailable = true;  
  36.                 }  
  37.             }  
  38.   
  39.             name = uri.substring(namespace.length() + 1);  
  40.   
  41.             //// allowSlashesInActionNames代表是否允许"/"出现在Action的名称中,默认为false    
  42.             if (rootAvailable && "".equals(namespace)) {  
  43.                 namespace = "/";  
  44.             }  
  45.         }  
  46.   
  47.         if (!allowSlashesInActionNames && name != null) {  
  48.             int pos = name.lastIndexOf('/');  
  49.             if (pos > -1 && pos < name.length() - 1) {  
  50.                 name = name.substring(pos + 1);  
  51.             }  
  52.         }  
  53.   
  54.         mapping.setNamespace(namespace);  
  55.         mapping.setName(name);  
  56.     }  

 

其实当这次namespace设置在mapping中后,在execute.executeAction(request, response, mapping)处理流程中会去相应的package中查找请求的action,
如果根据这次的namespace中对应的package中没有找到请求的action,会再次在DefaultConfiguration#getActionConfig(String namespace, String name)
 中去找这个namespace的config,如果还是没有在这个namespace中找到对应name的Action则最终去默认为""空字符串的namespace中找。

 

 

 

 

 

 

Java代码   收藏代码
  1. public synchronized ActionConfig getActionConfig(String namespace, String name) {  
  2.             ActionConfig config = findActionConfigInNamespace(namespace, name);  
  3.   
  4.             // try wildcarded namespaces  
  5.             if (config == null) {  
  6.                 NamespaceMatch match = namespaceMatcher.match(namespace);  
  7.                 if (match != null) {  
  8.                     config = findActionConfigInNamespace(match.getPattern(), name);  
  9.   
  10.                     // If config found, place all the matches found in the namespace processing in the action's parameters  
  11.                     if (config != null) {  
  12.                         config = new ActionConfig.Builder(config)  
  13.                                 .addParams(match.getVariables())  
  14.                                 .build();  
  15.                     }  
  16.                 }  
  17.             }  
  18.   
  19.               
  20.             //如果还是找不到在namespace的配置Actionconfig,则去namespace为""空的package中找Action为name为ActionConfig  
  21.             if ((config == null) && (namespace != null) && (!"".equals(namespace.trim()))) {  
  22.                 config = findActionConfigInNamespace("", name);  
  23.             }  
  24.   
  25.   
  26.             return config;  
  27.         }  
  28.   
  29.   
  30. //解析ActionName,并通过是否存在动态方法调用如:"name!method"形式的action调用  
  31. protected ActionMapping parseActionName(ActionMapping mapping) {  
  32.         if (mapping.getName() == null) {  
  33.             return mapping;  
  34.         }  
  35.         if (allowDynamicMethodCalls) {//如果允许动态方法调用,则去判断ActionName中是否为name!method形式,  
  36.                                       //如果有!分隔则解析出name和method并保存在mapping;  
  37.               
  38.             String name = mapping.getName();  
  39.             int exclamation = name.lastIndexOf("!");  
  40.             if (exclamation != -1) {  
  41.                 mapping.setName(name.substring(0, exclamation));  
  42.                 mapping.setMethod(name.substring(exclamation + 1));  
  43.             }  
  44.         }  
  45.         return mapping;//此时mapping的6个属性已经组装完成了,就返回;  
  46.     }  

 


(3)如何检查是否访问的为静态资源,还有如果是正常的请求如jsp等,
则让它真接通过chain.doFilter(..)链下去直到它会调用Servlet.service(),注:jsp最终也被解析为一个Servlet。
一就如的doFilter()中的源代码:

Java代码   收藏代码
  1. if (mapping == null) {  //当前面获取mapping后还是null时,则检查是否访问的为静态资源  
  2.           boolean handled = execute.executeStaticResourceRequest(request, response);  
  3.           if (!handled) {  
  4.                               //请求如.jsp .html .js,让它通过则让给容器如tomcat处理。  
  5.             chain.doFilter(request, response);  
  6.           }  
  7.         }   
  8.   
  9.   
  10.         public boolean executeStaticResourceRequest(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {  
  11.             
  12.           //该请求不是一个Action请求时,先查找是不是访问的为静态资源  
  13.         String resourcePath = RequestUtils.getServletPath(request);  
  14.   
  15.         if ("".equals(resourcePath) && null != request.getPathInfo()) {  
  16.             resourcePath = request.getPathInfo();  
  17.         }  
  18.   
  19.         StaticContentLoader staticResourceLoader = dispatcher.getContainer().getInstance(StaticContentLoader.class);  
  20.         if (staticResourceLoader.canHandle(resourcePath)) {  
  21.             staticResourceLoader.findStaticResource(resourcePath, request, response);  
  22.             //如果访问的为静态资源则The framework did its job here  
  23.             return true;  
  24.   
  25.         } else {  
  26.           //如果是一个正常的请求如.jsp .html .js,让它通过  
  27.             return false;  
  28.         }  
  29.     }  

 

 (4)调用被请求的Action的执行方法

(4)调用被请求的Action的执行方法。

          execute.executeAction(request, response, mapping);
             public void executeAction(HttpServletRequest request, HttpServletResponse response,

                        ActionMapping mapping) throws ServletException {
                    dispatcher.serviceAction(request, response, servletContext, mapping);//这个方法里具体的处

                                                                       //理Action调用,包括调用Action前后调用拦截器(Interceptor);
               }
  
       以下是调用Dispatcher类中serviceAction()的源代码:

 

Java代码   收藏代码
  1. public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context,  
  2.                              ActionMapping mapping) throws ServletException {  
  3.   
  4.        Map<String, Object> extraContext = createContextMap(request, response, mapping, context);//此方法首先创建了一个名称为extraContext的Map对象  
  5.                                                                                                 //它保存了request,session,application,mapping的信息,这些信息以后可以统一在此对象(extraContext)中查找。   
  6.   
  7.          
  8.        //如果request中已经有了一个ValueStack对象,将其保存下来,留待以后恢复,并把它进行一些封装后也存入extraContext中。   
  9.        ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);  
  10.        boolean nullStack = stack == null;  
  11.        if (nullStack) {  
  12.            ActionContext ctx = ActionContext.getContext();  
  13.            if (ctx != null) {  
  14.                stack = ctx.getValueStack();  
  15.            }  
  16.        }  
  17.        if (stack != null) {  
  18.            extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.createValueStack(stack));  
  19.        }  
  20.   
  21.        String timerKey = "Handling request from Dispatcher";  
  22.   
  23.        //根据前封装的mapping的信息去找到请求处理的Action  
  24.   
  25.        try {  
  26.            UtilTimerStack.push(timerKey);  
  27.            String namespace = mapping.getNamespace();  
  28.            String name = mapping.getName();  
  29.            String method = mapping.getMethod();  
  30.   
  31.            //获取配置包装Configuration类,此时的config所有的信息在容器如tomcat启动时已经加载完成了。  
  32.            Configuration config = configurationManager.getConfiguration();  
  33.   
  34.           //下面这一句是创建Action代理的核心,下面要祥细介绍 (1)  
  35.            ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(  
  36.                    namespace, name, method, extraContext, truefalse);  
  37.   
  38.   
  39.            request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());  
  40.   
  41.             
  42.            //如果mapping中的result属性已经有值了,就直接跳到Result处理类  
  43.            if (mapping.getResult() != null) {  
  44.                Result result = mapping.getResult();  
  45.                result.execute(proxy.getInvocation());  
  46.            } else {  
  47.   
  48.            //这个方法是调用Action处理方法的核心了,包括调用Action前后调用拦截器(Interceptor);后面要祥细介绍了。(2)  
  49.                proxy.execute();  
  50.            }  
  51.   
  52.            // If there was a previous value stack then set it back onto the request  
  53.            if (!nullStack) {  
  54.                request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);  
  55.            }  
  56.        } catch (ConfigurationException e) {  
  57.           ........  
  58.        } catch (Exception e) {  
  59.            sendError(request, response, context, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);  
  60.        } finally {  
  61.            UtilTimerStack.pop(timerKey);  
  62.        }  
  63.    }  

 

 

  先来了解一下 , 创建一个Action的代理对象——ActionProxy引用,实际上对Action的调用正是通过ActionProxy实现的,而ActionProxy又由ActionProxyFactory创建,ActionProxyFactory是创建ActionProxy的工厂。
        配置信息加载完成后(这里说的配置信息加载是在容器启动时已经加载完成了,现在只要取得config对象的引用也就能获得所有配置信息了),创建一个Action的代理对象——ActionProxy引用,实际上对Action的调用正是通过ActionProxy实现的,而ActionProxy又由ActionProxyFactory创建,ActionProxyFactory是创建ActionProxy的工厂。
      注:ActionProxy和ActionProxyFactory都是接口,他们的默认实现类分别是DefaultActionProxy和DefaultActionProxyFactory,位于com.opensymphony.xwork2包下。

   祥细介绍 (1)
    调用了DefaultActionProxyFactory类#.createActionProxy(namespace, name, method, extraContext, true, false);方法,源代码如下
 

Java代码   收藏代码
  1.  public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map<String, Object> extraContext, boolean executeResult, boolean cleanupContext) {  
  2.           
  3.         ActionInvocation inv = new DefaultActionInvocation(extraContext, true); //创建了ActionInvocation,并注入extraContext的Map对象,里面包含了request,session,application,mapping的信息  
  4.         container.inject(inv);  
  5.         return createActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);//创建ActionProxy对象  
  6.     }  
  7.   
  8.  //以下是创建ActionProxy对象的方法的实现  
  9. public ActionProxy createActionProxy(ActionInvocation inv, String namespace, String actionName, String methodName, boolean executeResult, boolean cleanupContext) {  
  10.   
  11.         DefaultActionProxy proxy = new DefaultActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);//创建ActionProxy实现类DefaultActionProxy  
  12.         container.inject(proxy);  
  13.         proxy.prepare();//这方法找到请求action的config并做了相关init工作  
  14.         return proxy;  
  15.     }  
  16.   
  17.   
  18.   
  19.      protected void prepare() {  
  20.         String profileKey = "create DefaultActionProxy: ";  
  21.         try {  
  22.             UtilTimerStack.push(profileKey);  
  23.   
  24.             //根据namespace, actionName去configuration中找到是否有对应的ActionConfig  
  25.             config = configuration.getRuntimeConfiguration().getActionConfig(namespace, actionName);  
  26.   
  27.             if (config == null && unknownHandlerManager.hasUnknownHandlers()) {  
  28.                 config = unknownHandlerManager.handleUnknownAction(namespace, actionName);  
  29.             }  
  30.             if (config == null) {  
  31.                 throw new ConfigurationException(getErrorMessage());  
  32.             }  
  33.   
  34.             resolveMethod();  
  35.   
  36.             if (!config.isAllowedMethod(method)) {  
  37.                 throw new ConfigurationException("Invalid method: " + method + " for action " + actionName);  
  38.             }  
  39.   
  40.             invocation.init(this);//下面祥细分析  
  41.   
  42.         } finally {  
  43.             UtilTimerStack.pop(profileKey);  
  44.         }  
  45.     }  

 
    需要要先说明 一下关于Configuration config = configurationManager.getConfiguration();
在容器启动时先加载Struts2的配置文件,如果没有人为配置,则默认加载struts-default.xml、struts-plugin.xml和struts.xml,并且将配置信息保存在形如com.opensymphony.xwork2.config.entities.XxxxConfig的类中。
类com.opensymphony.xwork2.config.providers.XmlConfigurationProvider负责配置文件的读取和解析, addAction()方法负责读取<action>标签,并将数据保存在ActionConfig中;
addResultTypes()方法负责将<result-type>标签转化为ResultTypeConfig对象;

loadInterceptors()方法负责将<interceptor>标签转化为InterceptorConfi对象;
loadInterceptorStack()方法负责将<interceptor-ref>标签转化为InterceptorStackConfig对象;

loadInterceptorStacks()方法负责将<interceptor-stack>标签转化成InterceptorStackConfig对象。
而上面的方法最终会被addPackage()方法调用,将所读取到的数据汇集到PackageConfig对象中,要清楚明白这是容器启动时这工作,现在说的是struts处理请求的情况了。
所以根据namespace, actionName去configuration中找到是否有对应的ActionConfig

getActionConfig(namespace, actionName);在前面说mapping中的namespace时已经分析过了,如不清楚可以回前面看看。
invocation.init(this);是调用了DefaultActionInvocation的方法,在这方法里根据前面的信息,创建了一个请求的Action处理o

 

 

Java代码   收藏代码
  1. public void init(ActionProxy proxy) {  
  2.         this.proxy = proxy;  
  3.         Map<String, Object> contextMap = createContextMap();  
  4.   
  5.         // Setting this so that other classes, like object factories, can use the ActionProxy and other  
  6.         // contextual information to operate  
  7.         ActionContext actionContext = ActionContext.getContext();  
  8.   
  9.         if (actionContext != null) {  
  10.             actionContext.setActionInvocation(this);  
  11.         }  
  12.   
  13.         createAction(contextMap);//创建Action了,如果有actionEventListener,则触发监听器的调用。  
  14.   
  15.         if (pushAction) {  
  16.             stack.push(action);  
  17.             contextMap.put("action", action);  
  18.         }  
  19.   
  20.         invocationContext = new ActionContext(contextMap);  
  21.         invocationContext.setName(proxy.getActionName());  
  22.   
  23.          
  24.         //把config加载的iterceptors放到一个新的ArrayList<InterceptorMapping>里  
  25.         List<InterceptorMapping> interceptorList = new ArrayList<InterceptorMapping>(proxy.getConfig().getInterceptors());  
  26.         interceptors = interceptorList.iterator();  
  27.     }  
  28.   
  29.   //createAction()方法的源代码  
  30.  protected void createAction(Map<String, Object> contextMap) {  
  31.         // load action  
  32.         String timerKey = "actionCreate: " + proxy.getActionName();  
  33.         try {  
  34.             UtilTimerStack.push(timerKey);  
  35.             //请看下面action的具体实现  
  36.             action = objectFactory.buildAction(proxy.getActionName(), proxy.getNamespace(), proxy.getConfig(), contextMap);  
  37.         } catch (Exception e) {  
  38.         ...................  
  39.         } finally {  
  40.             UtilTimerStack.pop(timerKey);  
  41.         }  
  42.            
  43.          //如果有actionEventListener,则触发监听器的调用#prepare(action, stack)。  
  44.         if (actionEventListener != null) {  
  45.             action = actionEventListener.prepare(action, stack);  
  46.         }  
  47.     }  
  48.   
  49.     //通过反射创建了一个新的Action了,看到这应该很清楚Action的出生了吧。  
  50.     public Object buildBean(String className, Map<String, Object> extraContext, boolean injectInternal) throws Exception {  
  51.         Class clazz = getClassInstance(className);  
  52.         Object obj = clazz.newInstance();  
  53.         if (injectInternal) {  
  54.             injectInternalBeans(obj);  
  55.         }  
  56.         return obj;  
  57.     }  

 

 

创建Action完成了,现再往下一步就是   proxy.execute();真正地去调用action的方法了。跟进去祥细介绍如下:

    执行的是StrutsActionProxy类的execute()方法,调用invocation.invoke();

 

Java代码   收藏代码
  1. public String execute() throws Exception {  
  2.        ActionContext previous = ActionContext.getContext();  
  3.        ActionContext.setContext(invocation.getInvocationContext());  
  4.        try {  
  5.   
  6.            return invocation.invoke();//核心方法调用  
  7.        } finally {  
  8.            if (cleanupContext)  
  9.                ActionContext.setContext(previous);  
  10.        }  
  11.    }  

  最终还是回到了DefaultActionInvocation类执行invoke()方法,在DefaultActionInvocation类中,
  定义了invoke()方法,该方法实现了截拦器的递归调用和执行Action的execute()方法。


    先来看一下Interceptor接口的定义:

 

 

Java代码   收藏代码
  1. Interceptor.java  
  2. publicinterface Interceptor extends Serializable {  
  3. void destroy();  
  4. void init();  
  5. String intercept(ActionInvocation invocation) throws Exception;  
  6. }  

 所有的截拦器必须实现intercept方法,而该方法的参数恰恰又是ActionInvocation,所以,如果在intercept方法中调用invocation.invoke(),
代码DefaultActionInvocation.invoke()方法的部分代码
if (interceptors.hasNext()) {  resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);}会再次执行,从Action的Intercepor列表中找到下一个截拦器,依此递归。
  DefaultActionInvocation.invoke()源代码如下:

 

 

Java代码   收藏代码
  1. public String invoke() throws Exception {  
  2.        String profileKey = "invoke: ";  
  3.        try {  
  4.            UtilTimerStack.push(profileKey);  
  5.   
  6.            if (executed) {  
  7.                throw new IllegalStateException("Action has already executed");  
  8.            }  
  9.   
  10.            /* 
  11.             下面代码说明。整个方法主要由2个if从句分割,在(1)处的if从句中,主要实现了拦截器的"递归"调用, 
  12.             说它是递归调用,其实是一种非传统的递归。传统的递归应该是函数调用自身,最后达成一定条件后退出, 
  13.             但是这里是将自身的引用作为参数传递给intercept(),然后在intercept()内部再调用DefaultActionInvocation的invoke(), 
  14.             实现了递归调用。 
  15.            */  
  16.   
  17.            if (interceptors.hasNext()) {  
  18.                final InterceptorMapping interceptor = (InterceptorMapping) interceptors.next();  
  19.                String interceptorMsg = "interceptor: " + interceptor.getName();  
  20.                UtilTimerStack.push(interceptorMsg);  
  21.                try {  
  22.                                resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);  
  23.                            }  
  24.                finally {  
  25.                    UtilTimerStack.pop(interceptorMsg);  
  26.                }  
  27.            } else {  
  28.   
  29.            //执行完所有的拦截器后,调用这个方法里最终通过反射 methodResult = method.invoke(action, EMPTY_OBJECT_ARRAY);调用了action中的方法了。  
  30.                resultCode = invokeActionOnly();  //最后resultCode=(String) methodResult;  
  31.            }  
  32.   
  33.             
  34.            //执行完action方法并返回结果后,此处调用了在PreResultListener中的定义的一些执行Result前的操作  
  35.            if (!executed) {  
  36.                if (preResultListeners != null) {  
  37.                    for (Object preResultListener : preResultListeners) {  
  38.                        PreResultListener listener = (PreResultListener) preResultListener;  
  39.   
  40.                        String _profileKey = "preResultListener: ";  
  41.                        try {  
  42.                            UtilTimerStack.push(_profileKey);  
  43.                            listener.beforeResult(this, resultCode);  
  44.                        }  
  45.                        finally {  
  46.                            UtilTimerStack.pop(_profileKey);  
  47.                        }  
  48.                    }  
  49.                }  
  50.   
  51.                // now execute the result, if we're supposed to  
  52.                if (proxy.getExecuteResult()) {  
  53.   
  54.                    executeResult();  //// 根据配置文件构建Result,现在有以下这几种Result的实现,如:ActionChainResult,ServletDispatcherResult,  
  55.                                       //FreemarkerResult,ServletRedirectResult,ServletActionRedirectResult,VelocityResult等。  
  56.                }  
  57.   
  58.                executed = true;  
  59.            }  
  60.   
  61.            return resultCode;  
  62.        }  
  63.        finally {  
  64.            UtilTimerStack.pop(profileKey);  
  65.        }  
  66.    }  
  67.   
  68.   
  69.    private void executeResult() throws Exception {  
  70.        result = createResult();  
  71.   
  72.        String timerKey = "executeResult: " + getResultCode();  
  73.        try {  
  74.            UtilTimerStack.push(timerKey);  
  75.            if (result != null) {  
  76.                result.execute(this);//执行了result实现类的方法,表示对于请求的资源继续往下派发给别的去处理,  
  77.                                    //还是用相关的视图呈现给浏览器用户,这要看你配置的result-type,  
  78.                                     //以后有机会再一一分析各种result的处理流程程了。  
  79.   
  80.            } else if(){。。。。。。。。}  
  81.            }  
  82.        } finally {  
  83.            UtilTimerStack.pop(timerKey);  
  84.        }  
  85.    }  

 

 拦截器的调用活动图:

 

 

最后再说下有些朋友很关注struts2是在什么时候给action的各个属性setAttribute()的呢?其实这个过程是在调用ParametersInterceptor拦截器时
在执行doIntercept()中完成set值的。源码如下

Java代码   收藏代码
  1. public String doIntercept(ActionInvocation invocation) throws Exception {  
  2.         Object action = invocation.getAction();  
  3.         if (!(action instanceof NoParameters)) {  
  4.             ActionContext ac = invocation.getInvocationContext();  
  5.             final Map<String, Object> parameters = retrieveParameters(ac);  
  6.   
  7.             if (LOG.isDebugEnabled()) {  
  8.                 LOG.debug("Setting params " + getParameterLogMap(parameters));  
  9.             }  
  10.   
  11.             if (parameters != null) {  
  12.                 Map<String, Object> contextMap = ac.getContextMap();  
  13.                 try {  
  14.                     ReflectionContextState.setCreatingNullObjects(contextMap, true);  
  15.                     ReflectionContextState.setDenyMethodExecution(contextMap, true);  
  16.                     ReflectionContextState.setReportingConversionErrors(contextMap, true);  
  17.   
  18.                     ValueStack stack = ac.getValueStack();  
  19.   
  20.                     setParameters(action, stack, parameters);//这是方法里就通过反射给Action  setAttribute的。此处不再一一说明了,知道原理就行了。  
  21.                 } finally {  
  22.                     ReflectionContextState.setCreatingNullObjects(contextMap, false);  
  23.                     ReflectionContextState.setDenyMethodExecution(contextMap, false);  
  24.                     ReflectionContextState.setReportingConversionErrors(contextMap, false);  
  25.                 }  
  26.             }  
  27.         }  
  28.         return invocation.invoke();  
  29.     }  

 

 呵呵,先分析到这里的,如果朋友有什么地方还不清楚的,可以指出,我会尽我最大努力为你解决。让我们共同学习共同进步。

分享到:0
关注微信,跟着我们扩展技术视野。每天推送IT新技术文章,每周聚焦一门新技术。微信二维码如下:
微信公众账号:尚学堂(微信号:bjsxt-java)
北京总部地址:北京市海淀区西三旗桥东建材城西路85号神州科技园B座三层尚学堂 咨询电话:400-009-1906 010-56233821
Copyright 2007-2015 北京尚学堂科技有限公司 京ICP备13018289号-1 京公网安备11010802015183