转载自 http://downpour.iteye.com/blog/1335991java
Struts2中的设计模式
设计模式(Design pattern)是通过程序员反复实践后造成的一套代码设计经验的总结。设计模式随着编程语言的发展,也由最初的“编程惯例”逐步发展成为被反复使用、并为绝大多数程序员所知晓的、完善的理论体系。咱们使用设计模式(Design pattern)的初衷,是使代码的重用度提升、让代码可以更容易被别人理解以及保证代码的可靠性。毫无疑问,在程序中使用设计模式不管是对于程序员自身仍是对于应用程序都是共赢的结果。正确地使用设计模式,可以使咱们编程真正实现工程化和规范化,而且在必定程度上指导着框架的设计和实现。
在深刻探讨Struts2所依赖的核心技术以前,咱们将首先带领读者领略一下在整个Struts2框架之中所使用到的一些最经常使用的设计模式。理解这些设计模式的运用场景和内部机理,也将为往后咱们对这些核心技术的分析打下坚实的基础。
4.1 ThreadLocal模式
ThreadLocal模式,严格意义上来讲并不能称之为一种设计模式,由于它只是一个用来解决多线程程序中数据共享问题的一个解决方案。尽管如此,ThreadLocal模式却贯穿了整个Struts2和XWork框架,成为Struts2框架进行“解耦”设计的核心依赖技术。那么,为何要在Struts2中引入ThreadLocal模式呢?这不得不从Web开发中的线程安全问题谈起。
4.1.1线程安全问题的由来
在传统的Web开发中,咱们处理Http请求最经常使用的方式是经过实现Servlet对象来进行Http请求的响应。Servlet是J2EE的重要标准之一,规定了Java如何响应Http请求的规范。经过HttpServletRequest和HttpServletResponse对象,咱们可以轻松地与Web容器交互。
当Web容器收到一个Http请求时,Web容器中的一个主调度线程会从事先定义好的线程池中分配一个当前工做线程,将请求分配给当前的工做线程,由该线程来执行对应的Servlet对象中的service方法。若是这个工做线程正在执行的时候,Web容器收到另一个请求,主调度线程会一样从线程池中选择另外一个工做线程来服务新的请求。Web容器自己并不关心这个新的请求是否访问的是同一个Servlet实例。所以,咱们能够得出一个结论:对于同一个Servlet对象的多个请求,Servlet的service方法将在一个多线程的环境中并发执行。
因此,Web容器默认采用单实例(单Servlet实例)多线程的方式来处理Http请求。这种处理方式可以减小新建Servlet实例的开销,从而缩短了对Http请求的响应时间。可是,这样的处理方式会致使变量访问的线程安全问题。也就是说,Servlet对象并非一个线程安全的对象。下面的测试代码将证明这一点:
程序员
这里参阅了网络上一段著名的对Servlet线程安全性进行测试的代码(http://zwchen.iteye.com/blog/91088)。运行以后,咱们能够看一下这个例子的输出:
web
经过上面的输出,咱们能够得出如下三个Servlet对象的运行特性:
1. Servlet对象是一个无状态的单例对象(Singleton),由于咱们看到屡次请求的this指针所打印出来的hashcode值都相同
2. Servlet在不一样的线程(线程池)中运行,如http-8081-Processor22和http-8081-Processor23等输出值能够明显区分出不一样的线程执行了同一段Servlet逻辑代码。
3. Counter变量在不一样的线程中共享,并且它的值被不一样的线程修改,输出时已经不是顺序输出。也就是说,其余的线程会篡改当前线程中实例变量的值,针对这些对象的访问不是线程安全的。
【有关线程安全的概念范畴】
谈到线程安全,对于许多初学者来讲很容易引发概念上的混淆。线程安全,指的是在多线程环境下,一个类在执行某个方法时,对类的内部实例变量的访问安全与否。所以,对于下面列出来的2类变量,不存在任何线程安全的说法: 1)方法签名中的任何参数变量。
2)处于方法内部的局部变量。
任何针对上述形式的变量的访问都是线程安全的,由于它们都处于方法体的内部,由当前的执行线程独自管理。
这就是线程安全问题的由来:在传统的基于Servlet的开发模式中,Servlet对象内部的实例变量不是线程安全的。在多线程环境中,这些变量的访问须要经过特殊的手段进行访问控制。
解决线程安全访问的方法不少,比较容易想到的一种方案是使用同步机制,可是出于对Web应用效率的考虑,这种机制在Web开发中的可行性很低,也违背了Servlet的设计初衷。所以,咱们须要另辟蹊径来解决这一困扰咱们的问题。
4.1.2 ThreadLocal模式的实现机理
在JDK的早期版本中,提供了一种解决多线程并发问题的方案: java.lang.ThreadLocal类。ThreadLocal类在维护变量时,实际使用了当前线程(Thread)中的一个叫作ThreadLocalMap的独立副本,每一个线程能够独立修改属于本身的副本而不会互相影响,从而隔离了线程和线程,避免了线程访问实例变量发生冲突的问题。
ThreadLocal自己并非一个线程,而是经过操做当前线程(Thread)中的一个内部变量来达到与其余线程隔离的目的。之因此取名为ThreadLocal,所指望表达的含义是其操做的对象是线程(Thread)的一个本地变量。若是咱们看一下Thread的源码实现,就会发现这一变量,如代码清单4-2所示:
数据库
这是JDK中Thread源码的一部分,从中咱们能够看出ThreadLocalMap跟随着当前的线程而存在。不一样的线程Thread,拥有不一样的ThreadLocalMap的本地实例变量,这也就是“副本”的含义。接下来咱们再来看看ThreadLocal.ThreadLocalMap是如何定义的,以及ThreadLocal如何来操做它,如代码清单4-3所示:
apache
从上述代码中,咱们看到了ThreadLocal类的大体结构和进行ThreadLocalMap的操做。咱们能够从中得出如下的结论:
1. ThreadLocalMap变量属于线程(Thread)的内部属性,不一样的线程(Thread)拥有彻底不一样的ThreadLocalMap变量。
2. 线程(Thread)中的ThreadLocalMap变量的值是在ThreadLocal对象进行set或者get操做时建立的。
3. 在建立ThreadLocalMap以前,会首先检查当前线程(Thread)中的ThreadLocalMap变量是否已经存在,若是不存在则建立一个;若是已经存在,则使用当前线程(Thread)已建立的ThreadLocalMap。
4. 使用当前线程(Thread)的ThreadLocalMap的关键在于使用当前的ThreadLocal的实例做为key进行存储。
ThreadLocal模式,至少从两个方面完成了数据访问隔离,有了横向和纵向的两种不一样的隔离方式,ThreadLocal模式就能真正地作到线程安全:
纵向隔离 —— 线程(Thread)与线程(Thread)之间的数据访问隔离。这一点由线程(Thread)的数据结构保证。由于每一个线程(Thread)在进行对象访问时,访问的都是各自线程本身的ThreadLocalMap。
横向隔离 —— 同一个线程中,不一样的ThreadLocal实例操做的对象之间的相互隔离。这一点由ThreadLocalMap在存储时,采用当前ThreadLocal的实例做为key来保证。编程
深刻比较TheadLocal模式与synchronized关键字设计模式
ThreadLocal模式synchronized关键字都用于处理多线程并发访问变量的问题,只是两者处理问题的角度和思路不一样。安全
1)ThreadLocal是一个java类,经过对当前线程中的局部变量的操做来解决不一样线程的变量访问的冲突问题。因此,ThreadLocal提供了线程安全的共享对象机制,每一个线程都拥有其副本。网络
2)Java中的synchronized是一个保留字,它依靠JVM的锁机制来实现临界区的函数或者变量的访问中的原子性。在同步机制中,经过对象的锁机制保证同一时间只有一个线程访问变量。此时,被用做“锁机制”的变量时多个线程共享的。session
同步机制(synchronized关键字)采用了“以时间换空间”的方式,提供一份变量,让不一样的线程排队访问。而ThreadLocal采用了“以空间换时间”的方式,为每个线程都提供一份变量的副本,从而实现同时访问而互不影响。
ThreadLocal模式并非什么高深的学问,它甚至从JDK1.2开始就存在于Java世界中。因而可知,咱们掌握一种知识的最终目的是熟练而合理地运用它。
4.1.3 ThreadLocal模式的应用场景
在分析了ThreadLocal的源码以后,咱们来看看ThreadLocal模式最合适的业务场景。在一个完整的“请求-响应”过程当中,主线程的执行过程老是贯穿始终。当这个主线程的执行过程当中被加入了ThreadLocal的读写时,会对整个过程产生怎样的影响呢?咱们根据以前源码分析的结果,并结合分层开发模式,把整个流程画下来,如图4-1所示:
从上面图中咱们能够看到,因为ThreadLocal所操做的是维持于整个Thread生命周期的副本(ThreadLocalMap),因此不管在J2EE程序程序的哪一个层次(表示层、业务逻辑层或者持久层),只要在一个Thread的生命周期以内,存储于ThreadLocalMap中的对象都是线程安全的(由于ThreadLocalMap自己仅仅隶属于当前的执行线程,是执行线程内部的一个属性变量。咱们用图中的阴影部分来表示这个变量的存储空间)。而这一点,正是被咱们用于来解决多线程环境中的变量共享问题的核心技术。ThreadLocal的这一特性也使其可以被普遍地应用于J2EE开发中的许多业务场景。
【数据共享 OR 数据传递?】
ThreadLocal模式因为利用了Java自身的语法特性而显得异常简单和便利,于是被普遍应用于J2EE开发,尤为是应对跨层次的资源共享,例如在Spring中,就有使用ThreadLocal模式来管理数据库链接或者Hibernate的Session的范例。
在一些比较著名的论坛中,有着不少关于使用ThreadLocal模式来作数据传递的讨论。事实上,这是对ThreadLocal模式的一个极大的误解。读者须要注意的是,ThreadLocal模式解决的是同一线程中隶属于不一样开发层次的数据共享问题,而不是在不一样的开发层次中进行数据传递。
1)ThreadLocal模式的核心在于实现一个共享环境(类的内部封装了ThreadLocal的静态实例)。因此,在操做ThreadLocal时,这一共享环境会跨越多个开发层次而随处存在。
2)随处存在的共享环境形成了全部的开发层次的共同依赖,从而使得全部的开发层次都耦合在了一块儿,从而变得没法独立测试。
3)数据传递应该经过接口函数的签名显式声明,这样才可以从接口声明中表达接口所表达的真正含义。ThreadLocal模式位于实现的内部,从而使得接口与接口之间没法达成一致的声明契约。
Struts2的解耦合的设计理念使得Struts2的MVC实现成为了使用ThreadLocal模式的自然场所。在第三章中,咱们已经介绍了一些基本概念,Struts2经过引入XWork框架,将整个Http请求的过程拆分红为与Web容器有关和与Web容器无关的两个执行阶段。而这两个阶段的数据交互就是经过ThreadLocal模式中的线程共享副本安全地进行。在其中,咱们没有看到数据传递,存在的只是整个执行线程的数据共享。
4.1.4 ThreadLocal模式的核心元素
仔细分析上一节的示意图(图4-1),咱们能够发现,要完成ThreadLocal模式,其中最关键的地方就是建立一个任何地方均可以访问到的ThreadLocal实例(也就是执行示意图中的菱形部分)。而这一点,咱们能够经过类的静态实例变量来实现,这个用于承载静态实例变量的类就被视做是一个共享环境。咱们来看一个例子,如代码清单4-4所示:
在这个Counter类中,咱们实现了一个静态的ThreadLocal变量,并经过get方法将ThreadLocal中存储的值暴露出来。咱们还封装了一个带有业务逻辑的方法getNextCounter,操做ThreadLocal中的值,将其加1,并返回计算后的值。
此时,Counter类就变成了一个数据共享环境,咱们也拥有了实现ThreadLocal模式的关键要素。有了它,咱们来编写一个简单的测试,如代码清单4-5所示:
这是一个简单的线程类,循环输出当前线程的名称和getNextCounter的结果,因为getNextCounter中的逻辑所操做的是ThreadLocal中的变量,因此不管同时有多少个线程在运行,返回的值将仅与当前线程的变量值有关,也就是说,在同一个线程中,变量值会被连续累加。这一点能够经过以下的测试代码证明:
咱们来运行一下上面的代码,并看看输出结果:
上面的输出结果也证明了,counter的值在多线程环境中的访问是线程安全的。从对例子的分析中咱们能够再次体会到,ThreadLocal模式最合适的使用场景:在同一个线程(Thread)的不一样开发层次中共享数据。
从上面的例子中,咱们能够简单总结出实现ThreadLocal模式的两个主要步骤:
1. 创建一个类,并在其中封装一个静态的ThreadLocal变量,使其成为一个共享数据环境。
2. 在类中实现访问静态ThreadLocal变量的静态方法(设值和取值)。
创建在ThreadLocal模式的实现步骤之上,ThreadLocal的使用则更加简单。在线程执行的任何地方,咱们均可以经过访问共享数据类中所提供的ThreadLocal变量的设值和取值方法安全地得到当前线程中安全的变量值。
这两个步骤,咱们以后会在Struts2的实现中屡次说起,读者只要能充分理解ThreadLocal处理多线程访问的基本原理,就能对Struts2的数据访问和数据共享的设计有一个总体的认识。
讲到这里,咱们回过头来看看ThreadLocal模式的引入,到底对咱们的编程模型有什么重要的意义呢?
这一点,是由ThreadLocal模式的实现机理决定的。由于实现ThreadLocal模式的一个重要步骤,就是构建一个静态的共享存储空间。从而使得任何对象在任什么时候刻均可以安全地对数据进行访问。
这一点是ThreadLocal模式给咱们带来的最为核心的一个影响。由于在通常状况下,Java对象之间的协做关系,主要经过参数和返回值来进行消息传递,这也是对象协做之间的一个重要依赖。而ThreadLocal模式完全打破了这种依赖关系,经过线程安全的共享对象来进行数据共享,能够有效避免在编程层次之间造成数据依赖。这也成为了XWork事件处理体系设计的核心。
Struts2的线程安全
通常状况,咱们的ActionContext都是经过:ActionContext context = (ActionContext) actionContext.get();来获取的。咱们再来看看这里的actionContext对象的建立:
static ThreadLocal actionContext=new ActionContextThreadLocal();,
ActionContextThreadLocal是实现ThreadLocal的一个内部类。ThreadLocal能够命名为“线程局部变量”,它为每个使用该变量的线程都提供一个变量值的副本,使每个线程均可以独立地改变本身的副本,而不会和其它线程的副本冲突。这样,咱们ActionContext里的属性只会在对应的当前请求线程中可见,从而保证它是线程安全的。
Struts2的action中就像一个POJO同样,定义了不少的类变量。此时,就使用scope=prototype来指定是个原型模式,而不是单例,这样就解决了线程安全问题。每一个线程都是一个新的实例
在Struts2.0中,Action已经与Servlet API彻底分离,这使得Struts2.0的Action具备了更加灵活和低耦合的特性,与Struts1.0相比较而言是个巨大的进步。虽然 Struts2.0的Action已经与Servlet API彻底分离,但咱们在实现业务逻辑处理时常常须要访问Servlet中的对象,如Session、Application等。Struts2.0 提供了一个名字为ActionContext的类,在Action中能够经过该类得到Servlet API。
ActionContext是一个Action的上下文对象,Action运行期间所用到的数据都保存在ActionContext中(如Session,客户端提交的参数等信息)。
在Action中能够经过下面的代码来建立和使用ActionContext类,关于该类的方法介绍以下所示:
ActionContext ac=ActionContext.getContext();
如下是ActionContext类的经常使用方法
1.Object get(Object key) :经过参数key来查找当前ActionContext中的值
2.Map getApplication() :返回一个Application级的Map对象
3.Static ActionContext getContext() :得到当前线程的ActionContext对象
4.Map getParameters() :返回一个包含全部HttpServletRequest参数信息的Map对象
5.Map getSession() :返回一个Map类型的HttpSession对象
6.Void put(Object key,Object value) :向当前ActionContext对象中存入名值对信息
7.Void setApplication(Map application) :设置Application上下文
8.Void setSession(Map session) :设置一个Map类型的Session值
Struts2 控制流程
1) 请求到来
2) 建立 ValueStack( Action 放栈顶),迕行初始化
3) 调用拦截器 Interceptor,在拦截器中是能够访问 ValueStack 的
4) 调用 Action,执行 execute()方法
5) 调用 Result, Result 负责把数据显示给用户
6) 最后到页面,经过标记库(Taglib)取出数据
当一个请求到达Servlet容器(Tomcat)后,将被传递给一个标准的过滤器链,在这个过滤器链中包括了可选的ActionContextCleanUp过滤器.当在Struts2 Web应用程序中集成SiteMesh时,才会用到此链。接下来,必须的FilterDispatcher被调用,它轮询ActonMapper(org.apache.struts2.dispatcher.mapper.ActionMapper),以便确认这个请求是否应该调用一个action。若是ActionMapper肯定了一个请求应该被调用,那么FilterDispatcher就把控制权委派给ActionProxy(com.opensymphony.xwork2.ActionProxy),ActionProxy询问框架的配置文件管理器(它从struts.xml文件中读取配置信息),接下来,ActionProxy建立一个实现了命令模式的ActionInvocation,ActionInvocation在调用action以前会一次调用全部配置的链接器。一旦action执行返回,ActionInvocation就要struts.xml里面配置),而后执行这个result,一般状况下result会调用JSP或者freeMarker模板呈现页面(但不老是这样,result也能够是一个action链).
这里介绍一下Struts2框架的组成部分:
1.ActionMapper和ActionMapping:
org.apache.struts2.dispatcher.mapper.ActionMapper接口在HTTP请求和action调用请求之间提供了一个映射,当给定一个HTTP请求时,ActionMapper根据请求的URL来查找是否有对应的action调用。若是有则返回一个描述了action调用的
ActionMapping,若是没有找到匹配的action调用请求,则返回null;
Struts2框架对该接口提供的默认实现是org.apacher.struts2.disspatcher.mapper.
DefaultActionMapper.
ActionMapping本质上是一个数据传输对象,它将Action类和要执行的方法的详细资料收集在一块儿,ActionMapping由org.apache.struts2.dispatcher.Dispatcher和各类用户接口组合使用。
ActionMapping的完整类名是
org.apacher.struts2.dispatcher.mapper.ActionMapping.
2.ActionProxy&ActionInvocation
Action的一个代理,由ActionProxyFactory建立,它自己不包括Action实例,默认实现DefaultActionProxy是由ActionInvocation持有Action实例。ActionProxy做用是如何取得Action,不管是本地仍是远程。而ActionInvocation的做用是如何执行Action,拦截器的功能就是在ActionInvocation中实现的。
ConfigurationProvider&Configuration
ConfigurationProvider就是Struts2中配置文件的解析器,Struts2中的配置文件主要是尤为实现类XmlConfigurationProvider及其子类
StrutsXmlConfigurationProvider来解析。
3.ActionContext
是action执行的上下文,每个上下文都至关于一个action执行所须要的一组对象的容器,例如session,application,parameters,locale等.
ActionContext包含了大量执行期间有用的环境信息,这些信息由org.apacher.
struts2.dispatcher.Dispatcher类在建立ActionProxy前设置,
并封装到一个Map对象extraContext中,
Map<String, Object> extraContext = createContextMap(request,
response, mapping, context);
该对象随后被做为参数传递给ActionProxyFactory的createActionProxy()方法.
ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
namespace, name, method, extraContext, true, false);
ActionContext是线程本地的,这就意味着在ActionContext中存储的值对于每一个线程都是惟一的,因此对于某个Action,若是要从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的对象,操做更方便)
ActionConext的完整类名是:org.opensymphony.xwork2.ActionContext.
4.result
com.opensymphony.xwork2.Result接口表明action执行后的结果。每个action 执行都须要返回一个String类型的结果码,用于配置result元素列表中选择对应的result。result在struts.xml文件中进行配置。
Result接口的不一样实现表明了不一样类型的结果输出,Struts2框架提供的Result 实现包括了Servlet转发,Servlet重定向,Velocity模板输出,FreeMarker模板输出,JasperReports(可生成PDF,CVS,XML等)和ActionChainResult(可用于从当前action 到其余action的链式处理)等.
Struts2框架的调用流程:
1>当Servlet容器接收到一个请求以后,将请求交给了在web.xml文件中配置的过滤器 FilterDispatcher,调用它的doFilter()方法。
2>FilterDispatcher询问ActionMapper,以便确认这个请求是否有对应的action调用.
3>ActionMapper返回一个描述了action调用的ActionMapping对象
4>FilterDispatcher调用Dispatcher类的serviceAction()方法。
5>FilterDispatcher调用ActionProxy的execute()方法。
6>ActionProxy设置ActionInvocation对象的执行上下文,而后调用其invoke()方法。
7>ActionInvocation的invoke()方法从拦截器映射中查找还没有执行的拦截器,调用它的intercepet(invocation)方法,并将自身对象的引用做为参数传递个拦截器。
8>拦截器完成某些预处理工做后,反过来调用ActionInvocation的invoke()方法,
ActionInvocation维护着本身的状态,因此它知道哪些拦截器已经被执行,若是尚未执行的拦截器,就继续执行它的intercept(invocation)方法。
9>若是全部的拦截器都已经执行过了,就调用action实例的execute()方法(若是在struts.xml文件中没有被设置成其它方法的话).
10>ActionInvocation根据action执行返回的结果码,查找对应的Result,调用result的execute(invocation),将结果页面呈现给用户.
11>ActionInvocation的invoke()方法将控制权返回给拦截器映射中的最后一个拦截器,该拦截器完成全部必须的后期处理工做,而后从intercepter(invocation)返回,容许前一个拦截器执行它本身的后处理工做。如此反复,直到全部的拦截器都成功返回。
12>ActionInvocation的invoke0方法执行完毕后,向ActionProxy返回一个String类型的结果码,最后ActionProxy清理状态并返回.