struts2原理及请求处理核心代码

StrutsXMLWeb 
1. struts-default.xml 
2. struts-plugin.xml 
3. struts.xml 
4. struts.properties 
5. web.xml 
多个文件定义了同一个常量值,则后一个会覆盖前面的定义!


 上图来源于Struts2官方站点,是Struts 2 的整体结构。

 

Struts2框架由3个部分组成:核心控制器FilterDispatcher、业务控制器和用户实现的业务逻辑组件。在这3个部分里,Struts 2框架提供了核心控制器FilterDispatcher,而用户需要实现业务控制器和业务逻辑组件。 
(1)核心控制器:FilterDispatcher 
      FilterDispatcher是Struts2框架的核心控制器,该控制器作为一个Filter运行在Web应用中,它负责拦截所有的用户请求,当用户请求到达时,该Filter会过滤用户请求。如果用户请求以action结尾,该请求将被转入Struts2框架处理。 
     Struts2框架获得了*.action请求后,将根据*.action请求的前面部分决定调用哪个业务逻辑组件,例如,对于login.action请求,Struts2调用名为login的Action来处理该请求。 
    Struts2应用中的Action都被定义在struts.xml文件中,在该文件中定义Action时,定义了该Action的name属性和class属性,其中name属性决定了该Action处理哪个用户请求,而class属性决定了该Action的实现类。 
    Struts2用于处理用户请求的Action实例,并不是用户实现的业务控制器,而是Action代理——因为用户实现的业务控制器并没有与Servlet API耦合,显然无法处理用户请求。而Struts2框架提供了系列拦截器,该系列拦截器负责将HttpServletRequest请求中的请求参数解析出来,传入到Action中,并回调Action 的execute方法来处理用户请求。 

(2)一个请求在Struts2框架中的处理大概分为以下几个步骤 
1 .客户端初始化一个指向Servlet容器(例如Tomcat)的请求 ,即HttpServletRequest请求。
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 

在上述过程中所有的对象(Action,Results,Interceptors,等)都是通过ObjectFactory来创建的。


下面开始浏览struts2请求处理部分源码,最核心的方法doFilter 


  1. /** 
  2.  *  Dispatcher 
  3.  */  
  4. public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {  
  5.         /** 
  6.          * 实例化HttpServletRequest和HttpServletResponse 
  7.          */  
  8.         HttpServletRequest request = (HttpServletRequest) req;  
  9.         HttpServletResponse response = (HttpServletResponse) res;  
  10.   
  11.         try {  
  12.             /** 
  13.              * 处理编码与本地化 
  14.              */  
  15.             prepare.setEncodingAndLocale(request, response);  
  16.             /** 
  17.              * 创建action上下文 
  18.              */  
  19.             prepare.createActionContext(request, response);  
  20.             /** 
  21.              * 关联当前的dispatcher与当前线程 
  22.              *  
  23.              */  
  24.             prepare.assignDispatcherToThread();  
  25.             /** 
  26.              * 判断请求的URL模式是不是规定的模式 
  27.              */  
  28.             if ( excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {  
  29.                 chain.doFilter(request, response);  
  30.             } else {  
  31.                 /** 
  32.                  * 封装request对象 
  33.                  */  
  34.                 request = prepare.wrapRequest(request);  
  35.                 /** 
  36.                  * xwork的ConfigurationManager读取struts.xml 
  37.                  * 返回值来自Dispatcher的Container 
  38.                  */  
  39.                 ActionMapping mapping = prepare.findActionMapping(request, response, true);  
  40.                 /** 
  41.                  * 根据请求的URL,如果ActionMapping找不到,执行静态资源请求 
  42.                  * 如果找到就转发给action处理 
  43.                  */  
  44.                 if (mapping == null) {  
  45.                     boolean handled = execute.executeStaticResourceRequest(request, response);  
  46.                     if (!handled) {  
  47.                         chain.doFilter(request, response);  
  48.                     }  
  49.                 } else {  
  50.                     /** 
  51.                      * 在执行这个方法之前对http请求做预处理,为业务处理准备数据和运行环境 
  52.                      * 执行这个方法后把控制权交给xwork 
  53.                      * struts核心设计就是解耦合,消除核心程序对外部运行环境的依赖 
  54.                      * Web容器和MVC分离 
  55.                      */  
  56.                     execute.executeAction(request, response, mapping);  
  57.                 }  
  58.             }  
  59.         } finally {  
  60.             /** 
  61.              * 清理本次请求相关内容 
  62.              */  
  63.             prepare.cleanupRequest(request);  
  64.         }  
  65.     }  

每一个请求过来就会调用一次doFilter方法,下面来看一下doFilter方法中都做了哪些操作。在实例化HttpServletRequest和HttpServletResponse后,设置请求的编码和国际化 

  1. /** 
  2.      * PrepareOperations 
  3.      */  
  4.     public void setEncodingAndLocale(HttpServletRequest request, HttpServletResponse response) {  
  5.         dispatcher.prepare(request, response);  
  6.     }  
  7.       
  8.     /** 
  9.      * Dispacher 
  10.      */  
  11.     public void prepare(HttpServletRequest request, HttpServletResponse response) {  
  12.         String encoding = null;  
  13.         if (defaultEncoding != null) {  
  14.             encoding = defaultEncoding;  
  15.         }  
  16.   
  17.         Locale locale = null;  
  18.         if (defaultLocale != null) {  
  19.             locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());  
  20.         }  
  21.   
  22.         if (encoding != null) {  
  23.             try {  
  24.                 request.setCharacterEncoding(encoding);  
  25.             } catch (Exception e) {  
  26.                 LOG.error("Error setting character encoding to '" + encoding + "' - ignoring.", e);  
  27.             }  
  28.         }  
  29.   
  30.         if (locale != null) {  
  31.             response.setLocale(locale);  
  32.         }  
  33.   
  34.         if (paramsWorkaroundEnabled) {  
  35.             request.getParameter("foo"); // simply read any parameter (existing or not) to "prime" the request  
  36.         }  
  37.     }  


struts2用注解的方式在初始化的时候对属性赋值,类似与spring的依赖注入 

在StrutsConstants中 
  1. public static final String STRUTS_LOCALE = "struts.locale";  
  2. public static final String STRUTS_I18N_ENCODING = "struts.i18n.encoding";  


在default.properties中 
  1. # struts.locale=en_US  
  2. struts.i18n.encoding=UTF-8  


  1. @Inject(StrutsConstants.STRUTS_I18N_ENCODING)  
  2.     public void setDefaultEncoding(String val) {  
  3.         defaultEncoding = val;  
  4.     }  
  5.       
  6. @Inject(value=StrutsConstants.STRUTS_LOCALE, required=false)  
  7.     public void setDefaultLocale(String val) {  
  8.         defaultLocale = val;  
  9.     }  


所以在初始化的时候defaultEncoding赋值为UTF-8,stauts.locale被注掉了,同时注解设置了required=false,对不存在的属性,初始化注入的时候被忽略了,故defaultLocale为null 

下面来看一下是如何创建action上下文,又做了哪些操作。我们知道每一个请求都会创建一个action上下文,不同的action不会共享action上下文,这是通过本地线程变量实现的。 
  1. public static ActionContext getContext() {  
  2.         return (ActionContext) actionContext.get();  
  3.     }  
  4. static ThreadLocal actionContext = new ThreadLocal();  


下面来看看ActionContext中有哪些属性,除了一些静态常量外就只有Map<String, Object> context一个属性。 
  1. /** 
  2.      * PrepareOperations 
  3.      */  
  4.     public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) {  
  5.         ActionContext ctx;  
  6.         Integer counter = 1;  
  7.         Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER);  
  8.         if (oldCounter != null) {  
  9.             counter = oldCounter + 1;  
  10.         }  
  11.         /** 
  12.          *  从ThreadLocal中获取ActionContext 
  13.          */  
  14.         ActionContext oldContext = ActionContext.getContext();  
  15.         if (oldContext != null) {  
  16.             // detected existing context, so we are probably in a forward  
  17.             ctx = new ActionContext(new HashMap<String, Object>(oldContext.getContextMap()));  
  18.         } else {  
  19.             ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();  
  20.             stack.getContext().putAll(dispatcher.createContextMap(request, response, null, servletContext));  
  21.             ctx = new ActionContext(stack.getContext());  
  22.         }  
  23.         request.setAttribute(CLEANUP_RECURSION_COUNTER, counter);  
  24.         ActionContext.setContext(ctx);  
  25.         return ctx;  
  26.     }  


createActionContext中最重要的是dispatcher.createContextMap(request, response, null, servletContext),该方法就是将容器对象封装成普通java对象.这个方法是struts2核心设计,前面说过struts的核心设计就是解耦合,消除核心程序对外部运行环境的依赖,即Web容器和MVC分离,在创建action上下文的时候把web容器相关的数据Request,Session, 
Applicatioin...封装成普通的java对象Map使得xwork不依赖与web容器,从而解耦和。 
  1. /** 
  2.      * Dispatcher 
  3.      */  
  4.     public Map<String,Object> createContextMap(HttpServletRequest request, HttpServletResponse response,  
  5.             ActionMapping mapping, ServletContext context) {  
  6.   
  7.         // request map wrapping the http request objects  
  8.         Map requestMap = new RequestMap(request);  
  9.   
  10.         // parameters map wrapping the http parameters.  ActionMapping parameters are now handled and applied separately  
  11.         Map params = new HashMap(request.getParameterMap());  
  12.   
  13.         // session map wrapping the http session  
  14.         Map session = new SessionMap(request);  
  15.   
  16.         // application map wrapping the ServletContext  
  17.         Map application = new ApplicationMap(context);  
  18.   
  19.         Map<String,Object> extraContext = createContextMap(requestMap, params, session, application, request, response, context);  
  20.   
  21.         if (mapping != null) {  
  22.             extraContext.put(ServletActionContext.ACTION_MAPPING, mapping);  
  23.         }  
  24.         return extraContext;  
  25.     }  


createContextMap代码很清晰,先把容器对象封装成Map,在把这些Map作为value添加到contextMap中,创建之后再通过ActionContext.setContext(ctx)把context加到ThreadLocal中。 

包装request的方法wrapRequest,在wrapRequest方法中又调用 
  1. /** 
  2.      * Dispatcher 
  3.      */  
  4.     public HttpServletRequest wrapRequest(HttpServletRequest request, ServletContext servletContext) throws IOException {  
  5.         /** 
  6.          *  判断有没有做过封装,确保只做一次封装 
  7.          */  
  8.         if (request instanceof StrutsRequestWrapper) {  
  9.             return request;  
  10.         }  
  11.   
  12.         String content_type = request.getContentType();  
  13.         /** 
  14.          *  封装Request对象,判断content_type是不是multipart/form-data 
  15.          *  如果是返回MultiPartRequestWrapper对象处理文件上传 
  16.          *  如果不是返回StrutsRequestWrapper对象处理普通请求 
  17.          */  
  18.         if (content_type != null && content_type.indexOf("multipart/form-data") != -1) {  
  19.             MultiPartRequest mpr = null;  
  20.             //check for alternate implementations of MultiPartRequest  
  21.             Set<String> multiNames = getContainer().getInstanceNames(MultiPartRequest.class);  
  22.             if (multiNames != null) {  
  23.                 for (String multiName : multiNames) {  
  24.                     if (multiName.equals(multipartHandlerName)) {  
  25.                         mpr = getContainer().getInstance(MultiPartRequest.class, multiName);  
  26.                     }  
  27.                 }  
  28.             }  
  29.             if (mpr == null ) {  
  30.                 mpr = getContainer().getInstance(MultiPartRequest.class);  
  31.             }  
  32.             request = new MultiPartRequestWrapper(mpr, request, getSaveDir(servletContext));  
  33.         } else {  
  34.             request = new StrutsRequestWrapper(request);  
  35.         }  
  36.   
  37.         return request;  
  38.     }  


接下来看一看prepare.findActionMapping(request, response, true)方法,在PrepareOperations的findActionMapping方法中又调用了dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager()) 
这里是调用ActionMapper的实现类DefaultActionMapper的getMapping方法,分析getMapping之前先看一下ActionMapping类的属性 
  1. /** 
  2.      *  action名 
  3.      */  
  4.     private String name;  
  5.     /** 
  6.      * action的命名空间 
  7.      */  
  8.     private String namespace;  
  9.     /** 
  10.      * action的执行方法 
  11.      */  
  12.     private String method;  
  13.     /** 
  14.      * url后缀名 
  15.      */  
  16.     private String extension;  
  17.     /** 
  18.      * 参数 
  19.      */  
  20.     private Map<String, Object> params;  
  21.     /** 
  22.      * 返回的结果 
  23.      */  
  24.     private Result result;  


  1. /** 
  2.      * DefaultActionMapper 
  3.      */  
  4.     public ActionMapping getMapping(HttpServletRequest request,  
  5.             ConfigurationManager configManager) {  
  6.         ActionMapping mapping = new ActionMapping();  
  7.         /** 
  8.          * 获取请求中的url 
  9.          */  
  10.         String uri = getUri(request);  
  11.         /** 
  12.          * 去掉url中的参数 
  13.          * eg:test/test.cation;id=1-->test/test.cation 
  14.          */  
  15.         int indexOfSemicolon = uri.indexOf(";");  
  16.         uri = (indexOfSemicolon > -1) ? uri.substring(0, indexOfSemicolon) : uri;  
  17.         /** 
  18.          * 去掉url中的后缀名test/test.cation-->test/test 
  19.          */  
  20.         uri = dropExtension(uri, mapping);  
  21.         if (uri == null) {  
  22.             return null;  
  23.         }  
  24.         /** 
  25.          * 解析Action的名称和命名空间 
  26.          */  
  27.         parseNameAndNamespace(uri, mapping, configManager);  
  28.         /** 
  29.          * 去掉请求中的重复项 
  30.          */  
  31.         handleSpecialParameters(request, mapping);  
  32.   
  33.         if (mapping.getName() == null) {  
  34.             return null;  
  35.         }  
  36.         /** 
  37.          * 处理test!mehtod格式的请求 
  38.          */  
  39.         parseActionName(mapping);  
  40.   
  41.         return mapping;  
  42.     }  


  1. /** 
  2.      * DefaultActionMapper 
  3.      */  
  4.     protected String getUri(HttpServletRequest request) {  
  5.         /** 
  6.          *  判断请求是否来自于一个jsp的include 
  7.          *  如果通过属性"javax.servlet.include.servlet_path"取得url 
  8.          */  
  9.         String uri = (String) request  
  10.                 .getAttribute("javax.servlet.include.servlet_path");  
  11.         if (uri != null) {  
  12.             return uri;  
  13.         }  
  14.   
  15.         uri = RequestUtils.getServletPath(request);  
  16.         if (uri != null && !"".equals(uri)) {  
  17.             return uri;  
  18.         }  
  19.   
  20.         uri = request.getRequestURI();  
  21.         /** 
  22.          *  去掉contextPath的路径 
  23.          */  
  24.         return uri.substring(request.getContextPath().length());  
  25.     }  


下面看一下去除url后缀名的方法,先说一下extensions的赋值 
在StrutsConstants中 
  1. String STRUTS_ACTION_EXTENSION = "struts.action.extension";  


在default.properties中 
  1. struts.action.extension=action,,  


  1. @Inject(StrutsConstants.STRUTS_ACTION_EXTENSION)  
  2.     public void setExtensions(String extensions) {  
  3.         if (extensions != null && !"".equals(extensions)) {  
  4.             List<String> list = new ArrayList<String>();  
  5.             String[] tokens = extensions.split(",");  
  6.             for (String token : tokens) {  
  7.                 list.add(token);  
  8.             }  
  9.             if (extensions.endsWith(",")) {  
  10.                 list.add("");  
  11.             }  
  12.             this.extensions = Collections.unmodifiableList(list);  
  13.         } else {  
  14.             this.extensions = null;  
  15.         }  
  16.     }  

通过注解在初始化的时候赋值,所以extensions值为[action, ] 

  1. /** 
  2.      * DefaultActionMapper 
  3.      */  
  4.     protected String dropExtension(String name, ActionMapping mapping) {  
  5.         if (extensions == null) {  
  6.             return name;  
  7.         }  
  8.         for (String ext : extensions) {  
  9.             if ("".equals(ext)) {  
  10.                 /** 
  11.                  *  如果name中不包含. 
  12.                  *  或name中最后一个点后还有/直接返回name 
  13.                  */  
  14.                 int index = name.lastIndexOf('.');  
  15.                 if (index == -1 || name.indexOf('/', index) >= 0) {  
  16.                     return name;  
  17.                 }  
  18.             } else {  
  19.                 String extension = "." + ext;  
  20.                 /** 
  21.                  *  如果name结尾匹配定义后缀,则去掉name的匹配部分 
  22.                  */  
  23.                 if (name.endsWith(extension)) {  
  24.                     name = name.substring(0, name.length() - extension.length());  
  25.                     mapping.setExtension(ext);  
  26.                     return name;  
  27.                 }  
  28.             }  
  29.         }  
  30.         return null;  
  31.     }  


parseNameAndNamespace解析action的名称和命名空间 
  1. /** 
  2.      * DefaultActionMapper 
  3.      */  
  4.     protected void parseNameAndNamespace(String uri, ActionMapping mapping,  
  5.             ConfigurationManager configManager) {  
  6.         String namespace, name;  
  7.         int lastSlash = uri.lastIndexOf("/");  
  8.         /** 
  9.          *  如果处理过的url中不包含/或/在url的开头,那么namespace为"" 
  10.          */  
  11.         if (lastSlash == -1) {  
  12.             namespace = "";  
  13.             name = uri;  
  14.         } else if (lastSlash == 0) {  
  15.             namespace = "/";  
  16.             name = uri.substring(lastSlash + 1);  
  17.         } else if (alwaysSelectFullNamespace) {  
  18.             /** 
  19.              *  alwaysSelectFullNamespace默认是false 
  20.              *  判断是否把最后一个/前的字符全作为命名空间 
  21.              */  
  22.             namespace = uri.substring(0, lastSlash);  
  23.             name = uri.substring(lastSlash + 1);  
  24.         } else {  
  25.             // Try to find the namespace in those defined, defaulting to ""  
  26.             Configuration config = configManager.getConfiguration();  
  27.             String prefix = uri.substring(0, lastSlash);  
  28.             namespace = "";  
  29.             boolean rootAvailable = false;  
  30.             /** 
  31.              * 匹配最长的命名空间 
  32.              * eg:url test1/test2/test 
  33.              * 如果配置文件有两个namespace test1/test2和test1 
  34.              * 会匹配最长的那个test1/test2即贪婪匹配 
  35.              */  
  36.             for (Object cfg : config.getPackageConfigs().values()) {  
  37.                 String ns = ((PackageConfig) cfg).getNamespace();  
  38.                 if (ns != null && prefix.startsWith(ns) && (prefix.length() == ns.length() || prefix.charAt(ns.length()) == '/')) {  
  39.                     if (ns.length() > namespace.length()) {  
  40.                         namespace = ns;  
  41.                     }  
  42.                 }  
  43.                 if ("/".equals(ns)) {  
  44.                     rootAvailable = true;  
  45.                 }  
  46.             }  
  47.   
  48.             name = uri.substring(namespace.length() + 1);  
  49.   
  50.             // Still none found, use root namespace if found  
  51.             if (rootAvailable && "".equals(namespace)) {  
  52.                 namespace = "/";  
  53.             }  
  54.         }  
  55.   
  56.         if (!allowSlashesInActionNames && name != null) {  
  57.             int pos = name.lastIndexOf('/');  
  58.             if (pos > -1 && pos < name.length() - 1) {  
  59.                 name = name.substring(pos + 1);  
  60.             }  
  61.         }  
  62.   
  63.         mapping.setNamespace(namespace);  
  64.         mapping.setName(name);  
  65.     }  

根据请求的URL,如果ActionMapping找不到,执行静态资源请求,如果找到就转发给action处理,struts的预处理到此就结束了
posted @ 2013-02-27 19:34  狂奔的蜗牛cn  阅读(266)  评论(0编辑  收藏  举报