Struts2源码浅析(一)转载

1. Struts2架构图
     请求首先通过Filter chain,Filter主要包括ActionContextCleanUp,它主要清理当前线程的ActionContext和Dispatcher;
     FilterDispatcher主要通过AcionMapper来决定需要调用哪个Action。
     ActionMapper取得了ActionMapping后,在Dispatcher的serviceAction方法里创建ActionProxy,ActionProxy创建ActionInvocation,然后ActionInvocation调用Interceptors,执行Action本身,创建Result并返回。
     当然,如果要在返回之前做些什么,可以实现PreResultListener,这个PreResultListener只能执行一次。


 2. Struts2部分类介绍
     这部分从Struts2参考文档中翻译就可以了。
ActionMapper 
      ActionMapper其实是HttpServletRequest和Action调用请求的一个映射,它屏蔽了Action对于Request等Servlet类的依赖。Struts2中它的默认实现类是DefaultActionMapper,ActionMapper很大的用处可以根据自己的需要来设计url格式,它自己也有Restful的实现,具体可以参考文档的docs\actionmapper.html。

 

ActionProxy&ActionInvocation 
      Action的一个代理,由ActionProxyFactory创建,它本身不包括Action实例,
      默认实现DefaultActionProxy是由ActionInvocation持有Action实例。
      ActionProxy作用是如何取得Action,无论是本地还是远程。而ActionInvocation的作用是如何执行Action,拦截器的功能就是在ActionInvocation中实现的。

 
ConfigurationProvider&Configuration 
     ConfigurationProvider就是Struts2中配置文件的解析器,
     Struts2中的配置文件主要是通过实现类XmlConfigurationProvider及其子类StrutsXmlConfigurationProvider来解析。

 

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

 

4. ActionContext
     ActionContext是被存放在当前线程中的,获取ActionContext也是从ThreadLocal中获取的。
     所以在执行拦截器、action和result的过程中,由于他们都是在一个线程中按照顺序执行的,所以可以在任意时候在ThreadLocal中获取ActionContext。

     ActionContext包括了很多信息,比如Session、Application、Request、Locale、ValueStack等。其中ValueStack可以解析ognl表达式,来动态后去一些值,同时可以给表达式提供对象。

扫描二维码关注公众号,回复: 816455 查看本文章

    ActionContext(com.opensymphony.xwork.ActionContext)是Action执行时的上下文,上下文可以看作是一个容器 (其实我们这里的容器就是一个Map而已),它存放的是Action在执行时需要用到的对象.
    一般情况, 我们的ActionContext都是通过:

    ActionContext context = (ActionContext) actionContext.get(); 来获取的.

    我们再来看看这里的actionContext对象的创建:

     static ThreadLocal actionContext = new ActionContextThreadLocal();

     ActionContextThreadLocal是实现ThreadLocal的一个内部类.
     ThreadLocal可以命名为"线程局部变量",它为每一个使用该变量的线程都提供一个变量值的副本,使每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突.
     这样,我们 ActionContext里的属性只会在对应的当前请求线程中可见,从而保证它是线程安全的.

     通过ActionContext取得HttpSession: Map session = ActionContext.getContext().getSession(); (通过Map模拟HttpServlet的对象,操作更方便)。

 

5. ServletActionContext 
    ServletActionContext(com.opensymphony.webwork. ServletActionContext),这个类直接继承了我们上面介绍的ActionContext,它提供了直接与Servlet相关对象访问的功能,它可以取得的对象有:
 (1)javax.servlet.http.HttpServletRequest : HTTPservlet请求对象
 (2)javax.servlet.http.HttpServletResponse : HTTPservlet相应对象
 (3)javax.servlet.ServletContext : Servlet上下文信息
 (4)javax.servlet.ServletConfig : Servlet配置对象
 (5)javax.servlet.jsp.PageContext : Http页面上下文

如何从ServletActionContext里取得Servlet的相关对象:
<1>取得HttpServletRequest对象: HttpServletRequest request = ServletActionContext. getRequest();
<2>取得HttpSession对象: HttpSession session = ServletActionContext. getRequest().getSession();

 

6. ServletActionContext和ActionContext联系
    ServletActionContext和ActionContext有着一些重复的功能。
   我们遵循的原则是:如果ActionContext能够实现我们的功能,那最好就不要使用ServletActionContext,让我们的Action尽量不要直接去访问Servlet的相关对象.

 

注意:在使用ActionContext时有一点要注意: 不要在Action的构造函数里使用ActionContext.getContext(), 因为这个时候ActionContext里的一些值也许没有设置,这时通过ActionContext取得的值也许是null;
     同样,HttpServletRequest req = ServletActionContext.getRequest()也不要放在构造函数中,也不要直接将req作为类变量给其赋值。
     至于原因,我想是因为前面讲到的static ThreadLocal actionContext = new ActionContextThreadLocal(),
     从这里我们可以看出ActionContext是线程安全的,而 ServletActionContext继承自ActionContext,所以ServletActionContext也线程安全,
     线程安全要求每个线程都独立进行,所以req的创建也要求独立进行,所以ServletActionContext.getRequest()这句话不要放在构造函数中,也不要直接放在类中,而应该放在每个具体的方法体中(eg:login()、queryAll()、insert()等),这样才能保证每次产生对象时独立的建立了一个req。

 


7.ActionContextClearUp
ActionContextClearUp其实是Defer ClearUP.作用就是延长action中属性的生命周期,
包括自定义属性,以便在jsp页面中进行访问,让actionContextcleanup过滤器来清除属性,不让action自己清除。具体看下面的代码,代码很简单:

  1. <SPAN style="FONT-SIZE: small">public void doFilter(...){   
  2.   ...   
  3.   try{   
  4.     ...   
  5.  //继续执行所配置的chain中的Filter   
  6.  chain.doFilter(request, response);   
  7.   }finally{   
  8.   //保证在所有动作执行完之后,调用cleanUp   
  9.     ...   
  10.  cleanUp(request);   
  11.   }   
  12. }   
  13.   
  14. protected static void cleanUp(ServletRequest req) {   
  15.   ...   
  16.   ActionContext.setContext(null);//清除ActionContext实例   
  17.   Dispatcher.setInstance(null);//清除Dispatcher实例(Dispatcher主要是完成将url解析成对应的Action)   
  18. }   
  19. </SPAN>  

public void doFilter(...){ ... try{ ... //继续执行所配置的chain中的Filter chain.doFilter(request, response); }finally{ //保证在所有动作执行完之后,调用cleanUp ... cleanUp(request); } } protected static void cleanUp(ServletRequest req) { ... ActionContext.setContext(null);//清除ActionContext实例 Dispatcher.setInstance(null);//清除Dispatcher实例(Dispatcher主要是完成将url解析成对应的Action) }

 

 

另外注明一下UtilTimerStack的push和pop是用来计算调用方法所执行的开始和结束时间,用来做性能测试的。在struts的源码中随处可见。用法如下:

Java代码 复制代码   收藏代码
  1. <SPAN style="FONT-SIZE: small">String timerKey = "ActionContextCleanUp_doFilter: ";   
  2. UtilTimerStack.setActive(true);   
  3. UtilTimerStack.push(timerKey);   
  4. //调用要测试的方法。   
  5. UtilTimerStack.pop(timerKey);</SPAN>  

String timerKey = "ActionContextCleanUp_doFilter: "; UtilTimerStack.setActive(true); UtilTimerStack.push(timerKey); //调用要测试的方法。 UtilTimerStack.pop(timerKey);

 

 

8.核心源码

首先强调一下struts2的线程程安全,在Struts2中大量采用ThreadLocal线程局部变量的方法来保证线程的安全,
像Dispatcher等都是通过ThreadLocal来保存变量值,使得每个线程都有自己独立的实例变量,互不相干.

接下来就从Dispatcher开始看起,先看其构造函数:

Java代码 复制代码   收藏代码
  1. <SPAN style="FONT-SIZE: small">//创建Dispatcher,此类是一个Delegate,它是真正完成根据url解析转向,读取对应Action的地方   
  2.     public Dispatcher(ServletContext servletContext, Map<String, String> initParams) {   
  3.         this.servletContext = servletContext;   
  4.     //配置在web.xml中的param参数   
  5.         this.initParams = initParams;   
  6.     }</SPAN>  

//创建Dispatcher,此类是一个Delegate,它是真正完成根据url解析转向,读取对应Action的地方 public Dispatcher(ServletContext servletContext, Map<String, String> initParams) { this.servletContext = servletContext; //配置在web.xml中的param参数 this.initParams = initParams; }

 

 我们再看在FilterDispatcher创建Dispatcher的:

Java代码 复制代码   收藏代码
  1. <SPAN style="FONT-SIZE: small">protected Dispatcher createDispatcher(FilterConfig filterConfig) {   
  2.         Map<String, String> params = new HashMap<String, String>();   
  3.         for (Enumeration e = filterConfig.getInitParameterNames(); e.hasMoreElements();) {   
  4.             String name = (String) e.nextElement();   
  5.             String value = filterConfig.getInitParameter(name);   
  6.             params.put(name, value);   
  7.         }   
  8.         //都可以从FilterConfig中得到   
  9.         return new Dispatcher(filterConfig.getServletContext(), params);   
  10.     }</SPAN>  

protected Dispatcher createDispatcher(FilterConfig filterConfig) { Map<String, String> params = new HashMap<String, String>(); for (Enumeration e = filterConfig.getInitParameterNames(); e.hasMoreElements();) { String name = (String) e.nextElement(); String value = filterConfig.getInitParameter(name); params.put(name, value); } //都可以从FilterConfig中得到 return new Dispatcher(filterConfig.getServletContext(), params); }

 

 创建Dispatcher之后,来看init()方法
 init()方法是用来Load用户配置文件,资源文件以及默认的配置文件.



 主要分七步走,看下面注释

Java代码 复制代码   收藏代码
  1. <SPAN style="FONT-SIZE: small">public void init() {   
  2.   
  3.         if (configurationManager == null) {   
  4.             //设置ConfigurationManager的defaultFrameworkBeanName.   
  5.             //这里DEFAULT_BEAN_NAME为struts,这是xwork框架的内容,Framework可以是xwork,struts,webwork等   
  6.             configurationManager = new ConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME);   
  7.         }   
  8.         //读取properties信息,默认的default.properties,   
  9.         init_DefaultProperties(); // [1]   
  10.         //读取xml配置文件   
  11.         init_TraditionalXmlConfigurations(); // [2]   
  12.         //读取用户自定义的struts.properties   
  13.         init_LegacyStrutsProperties(); // [3]   
  14.         //自定义的configProviders   
  15.         init_CustomConfigurationProviders(); // [5]   
  16.         //载入FilterDispatcher传进来的initParams   
  17.         init_FilterInitParameters() ; // [6]   
  18.         //将配置文件中的bean与具体的类映射   
  19.         init_AliasStandardObjects() ; // [7]   
  20.            
  21.         //构建一个用于依赖注射的Container对象   
  22.         //在这里面会循环调用上面七个ConfigurationProvider的register方法   
  23.         //其中的重点就是DefaultConfiguration的#reload()方法   
  24.         Container container = init_PreloadConfiguration();   
  25.         container.inject(this);   
  26.         init_CheckConfigurationReloading(container);   
  27.         init_CheckWebLogicWorkaround(container);   
  28.   
  29.         if (!dispatcherListeners.isEmpty()) {   
  30.             for (DispatcherListener l : dispatcherListeners) {   
  31.                 l.dispatcherInitialized(this);   
  32.             }   
  33.         }   
  34.     }</SPAN>  

public void init() { if (configurationManager == null) { //设置ConfigurationManager的defaultFrameworkBeanName. //这里DEFAULT_BEAN_NAME为struts,这是xwork框架的内容,Framework可以是xwork,struts,webwork等 configurationManager = new ConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME); } //读取properties信息,默认的default.properties, init_DefaultProperties(); // [1] //读取xml配置文件 init_TraditionalXmlConfigurations(); // [2] //读取用户自定义的struts.properties init_LegacyStrutsProperties(); // [3] //自定义的configProviders init_CustomConfigurationProviders(); // [5] //载入FilterDispatcher传进来的initParams init_FilterInitParameters() ; // [6] //将配置文件中的bean与具体的类映射 init_AliasStandardObjects() ; // [7] //构建一个用于依赖注射的Container对象 //在这里面会循环调用上面七个ConfigurationProvider的register方法 //其中的重点就是DefaultConfiguration的#reload()方法 Container container = init_PreloadConfiguration(); container.inject(this); init_CheckConfigurationReloading(container); init_CheckWebLogicWorkaround(container); if (!dispatcherListeners.isEmpty()) { for (DispatcherListener l : dispatcherListeners) { l.dispatcherInitialized(this); } } }

 

   分七步载入各种配置属性,都是通过ConfigurationProvider接口进行的,这个接口提供init(),destroy(),register()等方法.
将各种ConfigurationProvider初始化之后将实例添加到ConfigurationManager的List里面.
最后通过循环调用List里的这些destroy(),register()等方法实现对配置文件的属性进行注册和销毁等功能.


猜你喜欢

转载自liyvzheng.iteye.com/blog/1630203