`
mazhiyuan
  • 浏览: 62654 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

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表达式,来动态后去一些值,同时可以给表达式提供对象。

    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自己清除。具体看下面的代码,代码很简单:

 

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的源码中随处可见。用法如下:

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

 

8.核心源码

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

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

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

 我们再看在FilterDispatcher创建Dispatcher的:

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用户配置文件,资源文件以及默认的配置文件.



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

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()等方法实现对配置文件的属性进行注册和销毁等功能.

文章先写到这里吧,下一篇会分析这七层功夫是怎样一步步练成的.http://mazhiyuan.iteye.com/blog/1202086

 

  • 大小: 48.4 KB
  • 大小: 110.9 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics