STRUTS 2 概述(节选自《STRUTS 2权威指南》作者:李刚)

STRUTS 2 概述(节选自《STRUTS 2权威指南》作者:李刚)
引 言
1 、MVC思想概述
1.1 Web应用的发展
1.2  Model 1和 Model 2
1.3 MVC思想
1.4 MVC模式的优势
1.5 常用 MVC框架及其特征
2 、Struts 2的起源和背景
2.1 Struts 1的基本结构及其存在的问题
2.2 WebWork的基本结构
2.3 Struts 2的起源
3 、Struts 2体系介绍.
   3.1 Struts 2的框架架构
3.2 Struts 2的标签库
3.3 Struts 2的控制器组件
4 、Struts 1和Struts 2的对比
5 、WebWork和Struts 2对比
6 、总 结  
 
引 言
Struts 1是全世界第一个发布的 MVC框架,它由Craig McClanahan在2001年发布,该框架一经推出,就得到了世界上Java Web开发者的拥护,经过长达6年时间的锤炼,Struts 1框架更加成熟、稳定,性能也有了很好的保证。因此,到目前为止,Struts 1依然是世界上使用最广泛的MVC框架。
目前,基于 Web的MVC框架非常多,发展也很快,每隔一段时间就有一个新的MVC框架发布,例如像JSF、Tapestry和Spring MVC等。除了这些有名的MVC框架外,还有一些边缘团队的MVC框架也很有借鉴意义。
对于企业实际使用 MVC框架而言,框架的稳定性则应该是最值得考虑的问题。一个刚刚起步的框架,可能本身就存在一些隐藏的问题,会将自身的BUG引入自己的应用。
虽然 Struts 2号称是一个全新的框架,但这仅仅是相对Struts 1而言。Struts 2与Struts 1相比,确实有很多革命性的改进,但它并不是新发布的新框架,而是在另一个赫赫有名的框架:WebWork基础上发展起来的。从某种程度上来讲,Strut2没有继承Struts 1的血统,而是继承了WebWork的血统。或者说,WebWork衍生出了Struts 2,而不是Struts 1衍生了Struts 2。因为Struts 2是WebWork的升级,而不是一个全新的框架,因此稳定性、性能等各方面都有很好的保证;而且吸收了Struts 1和WebWork两者的优势,因此,是一个非常值得期待的框架。
 
1.  MVC 思想概述
今天,我们见到的绝大部分应用,都是基于 B/S(浏览器/服务器)架构的,其中的服务器就是Web服务器。可见,Web应用是目前广泛使用的应用模式,而Struts 2是一个具有很好的实用价值的Web MVC框架。介绍Struts MVC框架之前,我们首先介绍Web应用的发展历史和MVC思想。
1.1 Web 技术的发展
随着 Internet技术的广泛使用,Web技术已经广泛应用于Internet上,但早期的Web应用全部是静态的HTML页面,用于将一些文本信息呈现给浏览者,但这些信息是固定写在HTML页面里的,该页面不具备与用户交互的能力,没有动态显示的功能。
很自然地,人们希望 Web应用里应该包含一些能动态执行的页面,最早的CGI(通用网关接口)技术满足了该要求,CGI技术使得Web应用可以与客户端浏览器交互,不再需要使用静态的HTML页面。CGI技术可以从数据库读取信息,将这些信息呈现给用户;还可以获取用户的请求参数,并将这些参数保存到数据库里。
CGI技术开启了动态 Web应用的时代,给了这种技术无限的可能性。但CGI技术存在很多缺点,其中最大的缺点就是开发动态Web应用难度非常大,而且在性能等各方面也存在限制。到1997年时,随着Java语言的广泛使用,Servlet技术迅速成为动态Web应用的主要开发技术。
相比传统的CGI应用而言,Servlet具有大量的优势:
Ø       Servlet是基于 Java语言创建的,而Java语言则内建了多线程支持,这一点大大提高了动态Web应用的性能。
Ø       Servlet应用可以充分利用 Java语言的优势,例如JDBC(Java DataBase Connection)等。同时,Java语言提供了丰富的类库,这些都简化了Servlet的开发。
Ø       除此之外, Servlet运行在Web服务器中,由Web服务器去负责管理Servlet的实例化,并对客户端提供多线程、网络通信等功能,这都保证Servlet有更好的稳定性和性能。
Servlet在 Web应用中被映射成一个URL(统一资源定位),该URL可以被客户端浏览器请求,当用户向指定URL对应的Servlet发送请求时,该请求被Web服务器接收到,该Web服务器负责处理多线程、网络通信等功能,而Servlet的内容则决定了服务器对客户端的响应内容。
 
 
图 1.1  Servlet的响应流程
图 1.1显示了Servlet的响应流程。
正如图 1.1所显示的,浏览器向Web服务器内指定的Servlet发送请求,Web服务器根据Servlet生成对客户端的响应。
实际上,这是后来所有的动态 Web编程技术所使用的模型,这种模型都需要一个动态的程序,或者一个动态页面,当客户端向该动态程序或动态页面发送请求时,Web服务器根据该动态程序来生成对客户端的响应。
到了 1998年,微软发布了ASP 2.0。它是Windows NT 4 Option Pack的一部分,作为IIS 4.0的外接式附件。它与ASP 1.0的主要区别在于它的外部组件是可以初始化的,这样,在ASP程序内部的所有组件都有了独立的内存空间,并可以进行事务处理。标志着ASP技术开始真正作为动态Web编程技术。
当 ASP技术在世界上广泛流行时,人们很快感受到这种简单的技术的魅力:ASP使用VBScript作为脚本语言,它的语法简单、开发效率非常高。而且,世界上已经有了非常多的VB程序员,这些VB程序员可以很轻易地过渡成ASP程序员——因此,ASP技术马上成为应用最广泛的动态Web开发技术。
随后,由 Sun带领的Java阵营,立即发布了JSP标准,从某种程度上来看,JSP是Java阵营为了对抗ASP推出的一种动态Web编程技术。
ASP和 JSP从名称上如此相似,但它们的运行机制存在一些差别,这主要是因为VBScript是一种脚本语言,无需编译,而JSP使用Java作为脚本语句——但Java从来就不是解释型的脚本语言,因此JSP页面并不能立即执行。因此,JSP必须编译成Servlet,这就是说:JSP的实质还是Servlet。不过,书写JSP比书写Servlet简单得多。
JSP的运行机理如图 1.2所示。 
 
 图 1.2  JSP的运行机理
对比图 1.1和图1.2,发现不论是Servlet动态Web技术,还是JSP动态Web技术,它们的实质完全一样。可以这样理解:JSP是一种更简单的Servlet技术,这也是JSP技术出现的意义——作为一个和ASP对抗的技术,简单就是JSP的最大优势。
随着实际 Web应用的使用越来越广泛,Web应用的规模也越来越大,开发人员发现动态Web应用的维护成本越来越大,即使只需要修改该页面的一个简单按钮文本,或者一段静态的文本内容,也不得不打开混杂的动态脚本的页面源文件进行修改——这是一种很大的风险,完全有可能引入新的错误。
这个时候,人们意识到:使用单纯的 ASP,或者JSP页面充当过多角色是相当失败的选择,这对于后期的维护相当不利。慢慢地开发人员开始在Web开发中使用MVC模式。
随后就是 Java阵营发布了一套完整的企业开发规范:J2EE(现在已经更名为Java EE),紧跟着微软也发布了ASP.NET技术,它们都采用一种优秀的分层思想,力图解决Web应用维护困难的问题。
动态 Web编程技术大致有如图1.3所示的路线。
 
 图 1.3  动态Web编程技术的发展历史
1.2 Model 1 和Model 2
对于 Java阵营的动态Web编程技术而言,则经历了所谓的Model 1和Model 2时代。
所谓 Model 1就是JSP大行其道的时代,在Model 1模式下,整个Web应用几乎全部由JSP页面组成,JSP页面接收处理客户端请求,对请求处理后直接做出响应。用少量的JavaBean来处理数据库连接、数据库访问等操作。
图 1.4显示了Model 1的程序流程。
 
 图 1.4  Model 1的程序流程
Model 1模式的实现比较简单,适用于快速开发小规模项目。但从工程化的角度看,它的局限性非常明显: JSP页面身兼View和Controller两种角色,将控制逻辑和表现逻辑混杂在一起,从而导致代码的重用性非常低,增加了应用的扩展性和维护的难度。
早期有大量 ASP和JSP技术开发出来的Web应用,这些Web应用都采用了Model 1架构。
Model 2已经是基于 MVC架构的设计模式。在Model 2架构中,Servlet作为前端控制器,负责接收客户端发送的请求,在Servlet中只包含控制逻辑和简单的前端处理;然后,调用后端JavaBean来完成实际的逻辑处理;最后,转发到相应的JSP页面处理显示逻辑。其具体的实现方式如图1.5所示。
图 1.5显示了Model 2的程序流程。
 
 图 1.5  Model 2的程序流程
正如图 1.5中看到的,Model 2下JSP不再承担控制器的责任,它仅仅是表现层角色,仅仅用于将结果呈现给用户,JSP页面的请求与Servlet(控制器)交互,而Servlet负责与后台的JavaBean通信。在Model 2模式下,模型(Model)由JavaBean 充当,视图(View)由JSP页面充当,而控制器(Controller)则由Servlet充当。
由于引入了 MVC模式,使Model 2具有组件化的特点,更适用于大规模应用的开发,但也增加了应用开发的复杂程度。原本需要一个简单的JSP页面就能实现的应用,在Model 2中被分解成多个协同工作的部分,需花更多时间才能真正掌握其设计和实现过程。
Model 2已经是 MVC设计思想下的架构,下面简要介绍MVC设计思想的优势。
*      注意:对于非常小型的 Web站点,如果后期的更新、维护工作不是特别大,可以使用Model 1的模式来开发应用,而不是使用Model 2的模式。虽然Model 2提供了更好的可扩展性及可维护性,但增加了前期开发成本。从某种程度上讲,Model 2为了降低系统后期维护的复杂度,却导致前期开发的更高复杂度。
1.3 MVC 思想及其优势
MVC并不是 Java语言所特有的设计思想,也并不是Web应用所特有的思想,它是所有面向对象程序设计语言都应该遵守的规范。
MVC思想将一个应用分成三个基本部分: Model(模型)、View(视图)和Controller(控制器),这三个部分以最少的耦合协同工作,从而提高应用的可扩展性及可维护性。
起初, MVC模式是针对相同的数据需要不同显示的应用而设计的,其整体的效果如图1.6所示。
   
    图 1.6  MVC结构
在经典的 MVC模式中,事件由控制器处理,控制器根据事件的类型改变模型或视图,反之亦然。具体地说,每个模型对应一系列的视图列表,这种对应关系通常采用注册来完成,即:把多个视图注册到同一个模型,当模型发生改变时,模型向所有注册过的视图发送通知,接下来,视图从对应的模型中获得信息,然后完成视图显示的更新。
从设计模式的角度来看, MVC思想非常类似于一个观察者模式,但与观察者模式存在少许差别:观察者模式下观察者和被观察者可以是两个互相对等的对象,但对于MVC思想而言,被观察者往往只是单纯的数据体,而观察者则是单纯的视图页面。
概括起来,MVC有如下特点。
Ø       多个视图可以对应一个模型。按 MVC设计模式,一个模型对应多个视图,可以减少代码的复制及代码的维护量,一旦模型发生改变,也易于维护。
Ø       模型返回的数据与显示逻辑分离。模型数据可以应用任何的显示技术,例如,使用 JSP页面、Velocity模板或者直接产生Excel文档等。
Ø       应用被分隔为三层,降低了各层之间的耦合,提供了应用的可扩展性。
Ø       控制层的概念也很有效,由于它把不同的模型和不同的视图组合在一起,完成不同的请求。因此,控制层可以说是包含了用户请求权限的概念。
Ø       MVC更符合软件工程化管理的精神。不同的层各司其职,每一层的组件具有相同的特征,有利于通过工程化和工具化产生管理程序代码。
相对于早期的 MVC思想,Web模式下的MVC思想则又存在一些变化,因为对于一个应用程序而言,我们可以将视图注册给模型,当模型数据发生改变时,即时通知视图页面发生改变;而对于Web应用而言,即使将多个JSP页面注册给一个模型,当模型发生变化时,模型无法主动发送消息给JSP页面(因为Web应用都是基于请求/响应模式的),只有当用户请求浏览该页面时,控制器才负责调用模型数据来更新JSP页面。
*      注意: MVC思想与观察者模式有一定的相似之处,但并不完全相同。经典的MVC思想与Web应用的MVC思想也存在一定的差别,引起差别的主要原因是因为Web应用是一种请求/响应模式下应用,对于请求/响应应用,如果用户不对应用发出请求,视图无法主动更新自己。
1.4  常用的MVC框架
目前常用的 MVC框架,除了Struts 2的两个前身外,还有一些非常流行的MVC框架,这些框架都提供了较好的层次分隔能力。在实现良好的MVC 分隔的基础上,还提供一些辅助类库,帮助应用的开发。
目前常用的 MVC框架还有如下一些。
1)   JSF
准确地说, JSF是一个标准,而不是一个产品。目前,JSF已经有两个实现产品可供选择,包含Sun的参考实现和Apache的MyFaces。大部分的时候,我们所说的JSF都是指Sun的参考实现。目前,JSF是作为JEE 5.0的一个组成部分,与JEE 5.0一起发布。
JSF的行为方法在 POJO中实现,JSF的Managed Bean无需继承任何特别的类。因此,无需在表单和模型对象之间实现多余的控制器层。JSF中没有控制器对象,控制器行为通过模型对象实现。
当然, JSF也允许生成独立的控制器对象。在Struts 1中,Form Bean包含数据,Action Bean包含业务逻辑,二者无法融合在一起。在JSF中,既可以将二者分开,也可以合并在一个对象中,提供更多灵活的选择。
JSF的事件框架可以细化到表单中每个字段。 JSF依然是基于JSP/Servlet的,仍然是JSP/Servlet架构,因而学习曲线相对简单。在实际使用过程中,JSF也会存在一些不足
Ø       作为新兴的 MVC框架,用户相对较少,相关资源也不是非常丰富。
Ø       JSF并不是一个完全组件化的框架,它依然是基于 JSP/Servlet架构的。
Ø       JSF的成熟度还有待进一步提高。
2)   Tapestry
Tapestry并不是一种单纯的 MVC框架,它更像MVC框架和模板技术的结合,它不仅包含了前端的MVC框架,还包含了一种视图层的模板技术,使用Tapestry完全可以与Servlet/JSP API分离,是一种非常优秀的设计。
通过使用 Tapestry,开发者完全不需要使用JSP技术,用户只需要使用Tapestry提供的模板技术即可,Tapestry实现了视图逻辑和业务逻辑的彻底分离。
Tapestry使用组件库替代了标签库,没有标签库概念,从而避免了标签库和组件结合的问题。 Tapsetry是完全组件化的框架。Tapestr只有组件或页面两个概念,因此,链接跳转目标要么是组件,要么是页面,没有多余的path概念。组件名,也就是对象名称,组件名称和path名称合二为一。
Tapestry具有很高的代码复用性,在 Tapestry中,任何对象都可看作可复用的组件。JSP开发者是真正面向对象,而不是URL解析。对于对页面要求灵活度相当高的系统,Tapestry是第一选择。精确地错误报告,可以将错误定位到源程序中的行,取代了JSP中那种编译后的提示。
因此,笔者一直对 Tapestry情有独钟:如果技术允许,使用Tapestry会带给整个应用更加优雅的架构,更好的开发效率。
但是,在实际开发过程中,采用 Tapestry也面临着一些问题必须考虑:
Ø       Tapestry的学习曲线相对陡峭,国内开发群体不是非常活跃,文档不是十分丰富。官方的文档太过学院派,缺乏实际的示例程序。
Ø       Tapestry的组件逻辑比较复杂,再加上 OGNL表达式和属性指定机制,因而难以添加注释。
3)   Spring MVC
Spring提供了一个细致完整的 MVC框架。该框架为模型、视图、控制器之间提供了一个非常清晰的划分,各部分耦合极低。Spring的MVC是非常灵活的,它完全基于接口编程,真正实现了视图无关。视图不再强制要求使用JSP,可以使用Velocity、XSLT或其他视图技术。甚至可以使用自定义的视图机制——只需要简单地实现View接口,并且把对应视图技术集成进来。Spring的Controllers由IoC容器管理。因此,单元测试更加方便。
Spring MVC框架以 DispatcherServlet为核心控制器,该控制器负责拦截用户的所有请求,将请求分发到对应的业务控制器。
Spring MVC还包括处理器映射、视图解析、信息国际化、主题解析、文件上传等。所有控制器都必须实现 Controller接口,该接口仅定义ModelAndView handleRequest(request,response)方法。通过实现该接口来实现用户的业务逻辑控制器。
Spring MVC框架有一个极好的优势,就是它的视图解析策略:它的控制器返回一个 ModelAndView对象,该对象包含视图名字和Model,Model提供了Bean的名字及其对象的对应关系。视图名解析的配置非常灵活,抽象的Model完全独立于表现层技术,不会与任何表现层耦合:JSP、Velocity或者其他的技术——都可以和Spring整合。
但相对于 Tapestry框架而言,Spring MVC依然是基于JSP/Servlet API的。
总体上来看, Spring MVC框架致力于一种完美的解决方案,并与Web应用紧紧耦合在一起。这都导致了Spring MVC框架的一些缺点:
Ø       Spring的 MVC与Servlet API耦合,难以脱离Servlet容器独立运行,降低了Spring MVC框架的可扩展性。
Ø       太过细化的角色划分,太过烦琐,降低了应用的开发效率。
Ø       过分追求架构的完美,有过度设计的危险。
2 .Struts 2的起源和背景
Struts 2以 WebWork优秀的设计思想为核心,吸收了Struts 1的部分优点,建立了一个兼容WebWork和Struts 1的MVC框架,Struts 2的目标是希望可以让原来使用Struts 1、WebWork的开发人员,都可以平稳过渡到使用Struts 2框架。
2.1 Struts 1 简介及存在的问题
从过去的岁月来看, Struts 1是所有MVC框架中不容辩驳的胜利者,不管是市场占有率,还是所拥有的开发人群,Struts 1都拥有其他MVC框架不可比拟的优势。Struts 1的成功得益于它丰富的文档、活跃的开发群体。当然,Struts 1是世界上第一个发布的MVC框架:Struts 1.0在2001年6月发布,这一点可能是使它得到如此广泛拥戴的主要原因。
为了使读者可以明白 Struts 1的运行机制,下面将简要介绍Struts 1的基本框架。
Struts 1框架以 ActionServlet作为核心控制器,整个应用由客户端请求驱动。当客户端向Web应用发送请求时,请求将被Struts 1的核心控制器ActionServlet拦截,ActionServlet根据请求决定是否需要调用业务逻辑控制器处理用户请求(实际上,业务逻辑控制器还是控制器,它只是负责调用模型来处理用户请求),当用户请求处理完成后,其处理结果通过JSP呈现给用户。
对于整个 Struts 1框架而言,控制器就是它的核心,Struts 1的控制器由两个部分组成:核心控制器和业务逻辑控制器。其中核心控制器就是ActionServlet,由Struts 1框架提供;业务逻辑控制就是用户自定义的Action,由应用开发者提供。
对于大部分用户请求而言,都需要得到服务器的处理。当用户发送一个需要得到服务器处理的请求时,该请求被 ActionServlet拦截到,ActionServlet将该请求转发给对应的业务逻辑控制器,业务逻辑控制器调用模型来处理用户请求;如果用户请求只是希望得到某个URL资源,则由ActionServlet将被请求的资源转发给用户。
Struts 1的程序运行流程如图 1.7所示。
 图 1.7  Struts 1的程序运行流程
下面就 Struts 1程序流程具体分析MVC中的三个角色。
1)   Model 部分
Struts 1的 Model部分主要由底层的业务逻辑组件充当,这些业务逻辑组件封装了底层数据库访问、业务逻辑方法实现。实际上,对于一个成熟的企业应用而言,Model部分也不是一个简单的JavaBean所能完成的,它可能是一个或多个EJB组件,可能是一个WebService服务。总之,Model部分封装了整个应用的所有业务逻辑,但整个部分并不是由Struts 1提供的,Struts 1也没有为实现Model组件提供任何支持。
2)   View 部分
Struts 1的 View部分采用JSP实现。Struts 1提供了丰富的标签库,通过这些标签库可以最大限度地减少脚本的使用。这些自定义的标签库可以输出控制器的处理结果。
虽然 Struts 1提供了与Ties框架的整合,但Struts 1所支持的表现层技术非常单一:既不支持FreeMarker、Velocity等模板技术,也不支持JasperReports等报表技术。
3)   Controller 部分
Struts 1的 Controller由两个部分组成。
Ø       系统核心控制器:由 Struts 1框架提供,就是系统中的ActionServlet。
Ø       业务逻辑控制器:由 Struts 1框架提供,就是用户自己实现的Action实例。
Struts 1的核心控制器对应图 1.7中的核心控制器(ActionServlet)。该控制器由Struts 1框架提供,继承HttpServlet类,因此可以配置成一个标准的Servlet,该控制器负责拦截所有HTTP请求,然后根据用户请求决定是否需要调用业务逻辑控制器,如果需要调用业务逻辑控制器,则将请求转发给Action处理,否则直接转向请求的JSP页面。
业务逻辑控制器负责处理用户请求,但业务逻辑控制器本身并不具有处理能力,而是调用 Model来完成处理。
Struts 1提供了系统所需要的核心控制器,也为实现业务逻辑控制器提供了许多支持。因此,控制器部分就是 Struts 1框架的核心。有时候,我们直接将MVC层称为控制器层。
*      提示:对于任何的 MVC框架而言,其实只实现了C(控制器)部分,但它负责用控制器调用业务逻辑组件,并负责控制器与视图技术(JSP、FreeMarker和Velocity等)的整合。
对于 Struts 1框架而言,因为它与JSP/Servlet耦合非常紧密,因而导致了许多不可避免的缺陷,随着Web应用的逐渐扩大,这些缺陷逐渐变成制约Struts 1发展的重要因素——这也是Struts 2出现的原因。下面具体分析Struts 1中存在的种种缺陷
1)   支持的表现层技术单一
Struts 1只支持 JSP作为表现层技术,不提供与其他表现层技术,例如Velocity、FreeMarker等技术的整合。这一点严重制约了Struts 1框架的使用,对于目前的很多Java EE应用而言,并不一定使用JSP作为表现层技术。
虽然 Struts 1处理完用户请求后,并没有直接转到特定的视图资源,而是返回一个ActionForward对象(可以理解ActionForward是一个逻辑视图名),在struts-config.xml文件中定义了逻辑视图名和视图资源之间的对应关系,当ActionServlet得到处理器返回的ActionForword对象后,可以根据逻辑视图名和视图资源之间的对应关系,将视图资源呈现给用户。
从上面的设计来看,不得不佩服 Struts 1的设计者高度解耦的设计:控制器并没有直接执行转发请求,而仅仅返回一个逻辑视图名——实际的转发放在配置文件中进行管理。但因为Struts 1框架出现的年代太早了,那时候还没有FreeMarker、Velocity等技术,因而没有考虑与这些FreeMarker、Velocity等视图技术的整合。
*      提示: Struts 1已经通过配置文件管理逻辑视图名和实际视图之间的对应关系,只是没有做到让逻辑视图名可以支持更多的视图技术。
虽然 Struts 1有非常优秀的设计,但由于历史原因,它没有提供与更多视图技术的整合,这严重限制了Struts 1的使用。
1)   与Servlet API严重耦合,难于测试
因为 Struts 1框架是在Model 2的基础上发展起来的,因此它完全是基于Servlet API的,所以在Struts 1的业务逻辑控制器内,充满了大量的Servlet API。
看下面的 Action代码片段:
//业务逻辑控制器必须继承 Struts 1提供的Action类
public class LoginAction extends Action
{
//处理用户请求的 execute方法
public ActionForward execute(
ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws     AuctionException  {
//获取封装用户请求参数的 ActionForm对象
//将其强制类型转换为登录用的 ActionForm
LoginForm loginForm = (LoginForm)form;
//当用户名为 scott,密码为tiger时返回成功
if (”scott”.equals(loginForm.getUsername()
&& “tiger”.equals(loginForm.getPassword())
{
//处理成功,返回一个 ActionForward对象
return mapping.findForward(”success”);                                    }
else  
{
//处理失败,返回一个 ActionForward对象
return mapping.findForward(”success”);                                    }
    }
}
当我们需要测试上面 Action类的execute方法时,该方法有4个参数:ActionMapping、ActionForm、HttpServletRequest和HttpServletResponse,初始化这4个参数比较困难,尤其是HttpServletRequest和HttpServletResponse两个参数,通常由Web容器负责实例化。
因为 HttpServletRequest和HttpServletResponse两个参数是Servlet API,严重依赖于Web服务器。因此,一旦脱离了Web服务器,Action的测试非常困难。
2)   代码严重依赖于Struts 1 API,属于侵入式设计
正如从上面代码片段中所看到的, Struts 1的Action类必须继承Struts 1的Action基类,实现处理方法时,又包含了大量Struts 1 API:如ActionMapping、ActionForm和ActionForward类。这种侵入式设计的最大弱点在于,一旦系统需要重构时,这些Action类将完全没有利用价值,成为一堆废品。
可见, Struts 1的Action类这种侵入式设计导致了较低的代码复用。
2.2 WebWork 简介
WebWork虽然没有 Struts 1那样赫赫有名,但也是出身名门,WebWork来自另外一个优秀的开源组织:opensymphony,这个优秀的开源组织同样开发了大量优秀的开源项目,如Qutarz、OSWorkFlow等。实际上,WebWork的创始人则是另一个Java领域的名人:Rickard Oberg(他就是JBoss和XDoclet的作者)。
相对于 Struts 1存在的那些先天性不足而言,WebWork则更加优秀,它采用了一种更加松耦合的设计,让系统的Action不再与Servlet API耦合。使单元测试更加方便,允许系统从B/S结构向C/S结构转换。
相对于 Struts 1仅支持JSP表现层技术的缺陷而言,WebWork支持更多的表现层技术,如Velocity、FreeMarker和XSLT等。
WebWork可以脱离 Web应用使用,这一点似乎并没有太多优势,因为,一个应用通常开始已经确定在怎样的环境下使用。WebWork有自己的控制反转(Inversion of Control)容器,通过控制反转,可以让测试变得更简单,测试中设置实现服务接口的Mock对象完成测试,而不需要设置服务注册。
WebWork 2使用 OGNL这个强大的表达式语言,可以访问值栈。OGNL对集合和索引属性的支持非常强大。
WebWork建立在 XWork之上,使用ServletDispatcher作为该框架的核心控制器,处理HTTP的响应和请求。
从处理流程上来看, WebWork与Struts 1非常类似,它们的核心都由控制器组成,其中控制器都由两个部分组成:
Ø       核心控制器 ServletDispatcher,该控制器框架提供。
Ø       业务逻辑控制器 Action,该控制器由程序员提供。
相对 Struts 1的Action与Servlet API紧紧耦合的弱点来说,WebWork的Action则完全与Servlet API分离,因而该Action更容易测试。
WebWork的 Action可以与Servlet API分离,得益于它灵巧的设计,它使用一个拦截器链,负责将用户请求数据转发到Action,并负责将Action的处理结果转换成对用户的响应。
当用户向 Web应用发送请求时,该请求经过ActionContextCleanUp、SiteMesh等过滤器过滤,由WebWork的核心控制器拦截,如果用户请求需要WebWork的业务逻辑控制器处理,该控制器则调用Action映射器,该映射器将用户请求转发到对应的业务逻辑控制器。值得注意的是,此时的业务逻辑控制器并不是开发者实现的控制器,而是WebWork创建的控制器代理。
创建控制器代理时, WebWork需要得到开发者定义的xwork.xml配置文件,控制器代理以用户实现的控制器作为目标,以拦截器链中的拦截器作为处理(Advice)。
*      提示: WebWork中创建控制器代理的方式,就是一种AOP(面向切面编程)编程方式,只是这种AOP中的拦截器由系统提供,因此无需用户参与。
开发者自己实现的业务逻辑控制器只是 WebWork业务控制器的目标——这就是为什么开发者自己实现的Action可以与Servlet API分离的原因。当开发者自己的Action处理完HTTP请求后,该结果只是一个普通字符串,该字符串将对应到指定的视图资源。
指定的试图资源经过拦截器链的处理后,生成对客户端的响应输出。
 上面整个过程的数据流图如图 1.8所示。
 
              图 1.8  WebWork的数据流图
与前面的 Struts 1框架对比,不难发现WebWork在很多地方确实更优秀。
相对Struts 1的种种缺点而言,WebWork存在如下优点:
Ø       Action无需与 Servlet API耦合,更容易测试
相对于 Struts 1框架中的Action出现了大量Servlet API而言,WebWork的Action更像一个普通Java对象,该控制器代码中没有耦合任何Servlet API。看下面的WebWork的Action示例:
public class LoginAction implements Action
{
//该字符串常量将作为 Action的返回值
private final static String LOGINFAIL=”loginfail”;
 //该 Action封装的两个请求参数
private String password;
private String username;
//password请求参数对应的 getter方法
public String getPassword()
{
return password;
}
//password请求参数对应的 setter方法
public void setPassword(String password)
{
this.password = password;
                        }
//username请求参数对应的 getter方法
public String getUsername()
{
return username;
               }
//username请求参数对应的 setter方法
public void setUsername(String username)
{
this.username = username;
                        }
 //处理用户请求的 execute方法
public String execute() throws Exception
{
if (”yeeku”.equalsIgnoreCase(getUsername())
&& “password”.equals(getPassword()))
{
ActionContext ctx = ActionContext.getContext();
//将当前登录的用户名保存到 Session
Map session = ctx.getSession();
session.put(”username”,getUsername());
return SUCCESS;
              }
else
{
return LOGINFAIL;
                }
        }
}
在上面的 Action代码中,我们看不到任何的Servlet API,当系统需要处理两个请求参数:username和password时,Action并未通过HttpServletRequest对象来获得请求参数,而是直接调用访问该Action的username和password成员属性——这两个属性由Action拦截器负责初始化,以用户请求参数为其赋值。
即使 Action中需要访问HTTP Session对象,依然没有在代码中直接出现HttpSession API,而是以一个Map对象代表了HTTP Session对象。
当我们将 WebWork的Action和Struts 1的Action进行对比时,不难发现Struts 1的Action确实太臃肿了,确实不如WebWork的Action那么优雅。
如果需要测试上面的 Action代码,测试用例的书写将非常容易,因为execute方法中没有包含任何Servlet API,甚至没有WebWork的API。
Ø       Action无需与 WebWork耦合,代码重用率高
在上面的 Action代码中,不难发现WebWork中的Action其实就是一个POJO,该Action仅仅实现了WebWork的Action接口,包含了一个execute方法。
Struts 1中的 Action类需要继承Struts 1的Action类。我们知道,实现一个接口和继承一个类是完全不同的概念:实现一个接口对类的污染要小得多,该类也可以实现其他任意接口,还可以继承一个父类;但一旦已经继承一个父类,则意味着该类不能再继承其他父类。
除此之外, Struts 1中Action也包含了一个execute方法,但该方法需要4个参数,类型分别是ActionMapping、ActionForm、HttpServletRequest和HttpServletResponse,一个包含了这4个参数的方法,除了在Struts 1框架下有用外,笔者难以想象出该代码还有任何复用价值。但WebWork的execute方法则完全不同,该方法中没有出现任何Servlet API,也没有出现任何WebWork API,这个方法在任何环境下都有重用的价值。
得益于 WebWork灵巧的设计,WebWork中的Action无需与任何Servlet API、WebWork API耦合,从而具有更好的代码重用率。
Ø       支持更多的表现层技术,有更好的适应性。
正如从图 1.8所见到的,WebWork对多种表现层技术:JSP、Velocity和FreeMarker等都有很好的支持,从而给开发更多的选择,提供了更好的适应性。
2.3  Struts 2 起源
经过五年多的发展, Struts 1已经成为一个高度成熟的框架,不管是稳定性还是可靠性,都得到了广泛的证明。但由于它太“老”了,一些设计上的缺陷成为它的硬伤。面对大量新的MVC框架蓬勃兴起,Struts 1也开始了血液的更新。
目前, Struts已经分化成两个框架:第一个框架就是传统Struts 1和WebWork结合后的Struts 2框架。Struts 2虽然是在Struts 1的基础上发展起来的,但实质上是以WebWork为核心,Struts 2为传统Struts 1注入了WebWork的设计理念,统一了Struts 1和WebWork两个框架,允许Struts 1和WebWork开发者同时使用Struts 2框架。
Struts分化出来的另外一个框架是 Shale,这个框架远远超出了Struts 1原有的设计思想,它与原有的Struts 1的关联很少,它使用全新的设计思想。Shale更像一个新的框架,而不是Struts的升级。
Shale 在很多方面与 Struts存在不同之处,其中有两点最为突出:
Ø       Struts与 JSF集成,而Shale则是建立在JSF之上。
Ø       Struts实质上是一个巨大的、复杂的请求处理器;而 Shale则是一组能以任何方式进行组合的服务,简单地说,Shale是一种SOA(面向服务架构)架构。
在后面的介绍中,我们会发现, Struts 2非常类似于WebWork框架,而不像Struts 1框架,因为Struts 2是以WebWork为核心,而不是以Struts 1为核心的。正因为此,许多WebWork开发者会发现,从WebWork过渡到Struts 2是一件非常简单的事情。
当然,对于传统的 Struts 1开发者,Struts 2也提供了很好的向后兼容性,Struts 2可与Struts 1有机整合,从而保证Struts 1开发者能平稳过渡到Struts 2。
3 . Struts 2体系介绍
Struts 2的体系与 Struts 1体系的差别非常大,因为Struts 2使用了WebWork的设计核心,而不是使用Struts 1的设计核心。Struts 2大量使用拦截器来处理用户请求,从而允许用户的业务逻辑控制器与Servlet API分离。
3.1 Struts 2 框架架构
从数据流图上来看, Struts 2与WebWork相差不大,Struts 2同样使用拦截器作为处理(Advice),以用户的业务逻辑控制器为目标,创建一个控制器代理。
控制器代理负责处理用户请求,处理用户请求时回调业务控制器的 execute方法,该方法的返回值将决定了Struts 2将怎样的视图资源呈现给用户。
图 1.9显示了Struts 2的体系概图。
  
    图 1.9  Struts 2的体系概图
Struts 2 框架的大致处理流程如下:
Ø       浏览器发送请求,例如请求 /mypage.action、/reports/myreport.pdf等。
Ø       核心控制器 FilterDispatcher根据请求决定调用合适的Action。
Ø       WebWork的拦截器链自动对请求应用通用功能,例如 workflow、validation或文件上传等功能。
Ø       回调 Action的execute方法,该execute方法先获取用户请求参数,然后执行某种数据库操作,既可以是将数据保存到数据库,也可以从数据库中检索信息。实际上,因为Action只是一个控制器,它会调用业务逻辑组件来处理用户的请求。
Ø       Action的 execute方法处理结果信息将被输出到浏览器中,可以是HTML页面、图像,也可以是PDF文档或者其他文档。此时支持的视图技术非常多,既支持JSP,也支持Velocity、FreeMarker等模板技术。
3.2 Struts 2 的配置文件
当 Struts 2创建系统的Action代理时,需要使用Struts 2的配置文件。
Struts 2的配置文件有两份:
Ø       配置 Action的struts.xml文件。
Ø       配置 Struts 2全局属性的struts.properties文件。
struts.xml文件内定义了 Struts 2的系列Action,定义Action时,指定该Action的实现类,并定义该Action处理结果与视图资源之间的映射关系。
下面是 struts.xml配置文件的示例:
<struts>
<!– Struts 2的 Action都必须配置在package里 –>
<package name=”default” extends=”struts-default”>
<!– 定义一个 Logon的Action,实现类为lee.Logon –>
<action name=”Logon” class=”lee.Logon”>
<!– 配置 Action返回input时转入/pages/Logon.jsp页面 –>
<result name=”input”>/pages/Logon.jsp</result>
<!– 配置 Action返回cancel时重定向到Welcome的Action–>
<result name=”cancel” type=”redirect-action”>Welcome</result>
<!– 配置 Action返回success时重定向到MainMenu的Action –>
<result type=”redirect-action”>MainMenu</result>
<!– 配置 Action返回expired时进入ChangePassword的Action链 –>
<result name=”expired” type=”chain”>ChangePassword</result>
</action>
<!– 定义 Logoff的Action,实现类为lee.Logoff –>
<action name=”Logoff” class=” lee.Logoff”>
<!– 配置 Action返回success时重定向到MainMenu的Action –>
<result type=”redirect-action”>Welcome</result>
</action>
</package>
</struts>
在上面的 struts.xml文件中,定义了两个Action。定义Action时,不仅定义了Action的实现类,而且的定义Action的处理结果时,指定了多个result,result元素指定execute方法返回值和视图资源之间的映射关系。对于如下配置片段:
<result name=”cancel” type=”redirect-action”>Welcome</result>
表示当 execute方法返回cancel的字符串时,跳转到Welcome的Action。定义result元素时,可以指定两个属性:type和name。其中name指定了execute方法返回的字符串,而type指定转向的资源类型,此处转向的资源可以是JSP,也可以是FreeMarker等,甚至是另一个Action——这也是Struts 2可以支持多种视图技术的原因。
除此之外, Struts 2还有一个配置Struts 2全局属性的Properties文件:struts.properties。该文件的示例如下:
#指定 Struts 2处于开发状态
struts.devMode = false
//指定当 Struts 2配置文件改变后,Web框架是否重新加载Struts 2配置文件struts.configuration.xml.reload=true
正如上面见到的, struts.properties文件的形式是系列的key、value对,它指定了Struts 2应用的全局属性。
3.3 Strut 2 的标签库
Struts 2的标签库也是 Struts 2的重要组成部分,Struts 2的标签库提供了非常丰富的功能,这些标签库不仅提供了表现层数据处理,而且提供了基本的流程控制功能,还提供了国际化、Ajax支持等功能。
通过使用 Struts 2的标签,开发者可以最大限度地减少页面代码的书写。
看下面的 JSP页面的表单定义片段:
<!–  定义一个 Action –>
<form method=”post” action=”basicvalid.action”>
<!– 下面定义三个表单域 –>
    名字: <input type=”text” name=”name”/><br>
    年纪: <input type=”text” name=”age”/><br>
    喜欢的颜色: <input type=”text” name=”favorite”/><br>
                                 <!– 定义一个输出按钮 –>
    <input type=”submit” value=”提交 “/>
</form>
上面页面使用了传统的 HTML标签定义表单元素,还不具备输出校验信息的功能,但如果换成如下使用Struts 2标签的定义方式:
<!– 使用 Struts 2标签定义一个表单 –>
<s:form method=”post” action=”basicvalid.action”>
<!– 下面使用 Struts 2标签定义三个表单域 –>
<s:textfield label=”名字 ” name=”name”/>
<s:textfield label=”年纪 ” name=”age”/>
<s:textfield label=”喜欢的颜色 ” name=”answer”/>
<!– 定义一个提交按钮 –>
<s:submit/>
</s:form>
则页面代码更加简洁,而且有更简单的错误输出。图 1.10是上面使用Struts 2标签执行数据校验后的输出。
图 1.10  使用Struts 2标签的效果
*      提示: Struts 2的标签库的功能非常复杂,该标签库几乎可以完全替代JSTL的标签库。而且Struts 2的标签支持表达式语言,这种表达式语言支持一个强大和灵活的表达式语言:OGNL(Object Graph Notation Language),因此功能非常强大。
3.4 Struts 2 的控制器组件
Struts 2的控制器组件是 Struts 2框架的核心,事实上,所有MVC框架都是以控制器组件为核心的。正如前面提到的,Struts 2的控制器由两个部分组成:FilterDispatcher和业务控制器Action。
实际上, Struts 2应用中起作用的业务控制器不是用户定义的Action,而是系统生成的Action代理,但该Action代理以用户定义的Action为目标。
下面是 Struts 2的Action代码示例:
public class LoginAction
{
//封装用户请求参数的 username属性
private String username;
//封装用户请求参数的 password属性
private String password;
 //username属性的 getter方法
public String getUsername()
{
return username;
               }
//username属性的 setter方法
public void setUsername(String username)
{
this.username = username;
                        }
//password属性的 getter方法
public String getPassword()
{
return password;
               }
//password属性的 setter方法
public void setPassword(String password)
{
this.password = password;
                        }
//处理用户请求的 execute方法
public String execute() throws Exception
{
//如果用户名为 scott,密码为tiger,则登录成功
if (getUsername().equals(”scott” )
&& getPassword().equals(”tiger” ) )
{
return “success”;
                }
else
{
return “error”;
              }
       }
}
通过查看上面的 Action代码,发现该Action比WebWork中的Action更彻底,该Action无需实现任何父接口,无需继承任何Struts 2基类,该Action类完全是一个POJO(普通、传统的Java对象),因此具有很好的复用性。
归纳起来,该 Action类有如下优势:
Ø       Action类完全是一个 POJO,因此具有很好的代码复用性。
Ø       Action类无需与 Servlet API耦合,因此进行单元测试非常简单。
Ø       Action类的 execute方法仅返回一个字符串作为处理结果,该处理结果可映射到任何的视图,甚至是另一个Action。
4 . Struts 2与Struts 1的对比
经过上面简要介绍,不难发现, Struts 2确实在Struts 1上做出了巨大的改进,的确是一个非常具有实用价值的MVC框架。          
Struts 1 和Struts 2在各方面的简要对比。
Ø       在 Action实现类方面的对比:Struts 1要求Action类继承一个抽象基类;Struts 1的一个具体问题是使用抽象类编程而不是接口。Struts 2 Action类可以实现一个Action接口,也可以实现其他接口,使可选和定制的服务成为可能。Struts 2提供一个ActionSupport基类去实现常用的接口。即使Action接口不是必须实现的,只有一个包含execute方法的POJO类都可以用作Struts 2的Action。
Ø       线程模式方面的对比: Struts 1 Action是单例模式并且必须是线程安全的,因为仅有Action的一个实例来处理所有的请求。单例策略限制了Struts 1 Action能做的事,并且要在开发时特别小心。Action资源必须是线程安全的或同步的;Struts 2 Action对象为每一个请求产生一个实例,因此没有线程安全问题。
Ø       Servlet依赖方面的对比: Struts 1 Action依赖于Servlet API,因为Struts 1 Action的execute方法中有HttpServletRequest和HttpServletResponse方法。Struts 2 Action不再依赖于Servlet API,从而允许Action脱离Web容器运行,从而降低了测试Action的难度。 当然,如果Action需要直接访问HttpServletRequest和HttpServletResponse参数,Struts 2 Action仍然可以访问它们。但是,大部分时候,Action都无需直接访问HttpServetRequest和HttpServletResponse,从而给开发者更多灵活的选择。
Ø       可测性方面的对比:测试 Struts 1 Action的一个主要问题是execute方法依赖于Servlet API,这使得Action的测试要依赖于Web容器。为了脱离Web容器测试Struts 1的Action,必须借助于第三方扩展:Struts TestCase,该扩展下包含了系列的Mock对象(模拟了HttpServetRequest和HttpServletResponse对象),从而可以脱离Web容器测试Struts 1的Action类。Struts 2 Action可以通过初始化、设置属性、调用方法来测试。
Ø       封装请求参数的对比: Struts 1使用ActionForm对象封装用户的请求参数,所有的ActionForm必须继承一个基类:ActionForm。普通的JavaBean不能用作ActionForm,因此,开发者必须创建大量的ActionForm类封装用户请求参数。虽然Struts 1提供了动态ActionForm来简化ActionForm的开发,但依然需要在配置文件中定义ActionForm;Struts 2直接使用Action属性来封装用户请求属性,避免了开发者需要大量开发ActionForm类的烦琐,实际上,这些属性还可以是包含子属性的Rich对象类型。如果开发者依然怀念Struts 1 ActionForm的模式,Struts 2提供了ModelDriven模式,可以让开发者使用单独的Model对象来封装用户请求参数,但该Model对象无需继承任何Struts 2基类,是一个POJO,从而降低了代码污染。
Ø       表达式语言方面的对比: Struts 1整合了JSTL,因此可以使用JSTL表达式语言。这种表达式语言有基本对象图遍历,但在对集合和索引属性的支持上则功能不强;Struts 2可以使用JSTL,但它整合了一种更强大和灵活的表达式语言:OGNL(Object Graph Notation Language),因此,Struts 2下的表达式语言功能更加强大。
Ø       绑定值到视图的对比: Struts 1使用标准JSP机制把对象绑定到视图页面;Struts 2使用“ValueStack”技术,使标签库能够访问值,而不需要把对象和视图页面绑定在一起。
Ø       类型转换的对比: Struts 1 ActionForm 属性通常都是String类型。Struts 1使用Commons-Beanutils进行类型转换,每个类一个转换器,转换器是不可配置的;Struts 2使用OGNL进行类型转换,支持基本数据类型和常用对象之间的转换。
Ø       数据校验的对比: Struts 1支持在ActionForm重写validate方法中手动校验,或者通过整合Commons alidator框架来完成数据校验。Struts 2支持通过重写validate方法进行校验,也支持整合XWork校验框架进行校验。
Ø       Action执行控制的对比: Struts 1支持每一个模块对应一个请求处理(即生命周期的概念),但是模块中的所有Action必须共享相同的生命周期。Struts 2支持通过拦截器堆栈(Interceptor Stacks)为每一个Action创建不同的生命周期。开发者可以根据需要创建相应堆栈,从而和不同的Action一起使用。
5 . WebWork和Struts 2对比
从某种程度上来看, Struts 2是WebWork的升级,而不是Struts 1的升级,甚至在Apache的Struts 2的官方文档都提到:WebWork到Struts 2是一次平滑的过渡。实际上,Struts 2.0其实是WebWork 2.3而已,从WebWork 2.2迁移到Struts 2.0不会比从WebWork 2.1到2.2更麻烦。
在很多方面, Struts 2仅仅是改变了WebWork下的名称,因此,如果开发者具有WebWork的开发经验,将可以更加迅速地进入Struts 2的开发领域。
下面是 Struts 2与WebWork命名上存在改变(见表1.1):
Struts 2 成员
    WebWork 成员
com.opensymphony.xwork2.*
    com.opensymphony.xwork.*
org.apache.Struts2.*
    com.opensymphony.webwork.*
struts.xml
    xwork.xml
struts.properties
    webwork.properties
Dispatcher  
    DispatcherUtil
org.apache.Struts2.config.Settings
    com.opensymphony.webwork.config.Configuration
表 1.1  Struts 2和WebWork成员名称的对应
除此之外, Struts 2也删除了WebWork中少量特性:
Ø       AroundInterceptor: Struts 2不再支持WebWork中的AroundInterceptor。如果应用程序中需要使用AroundInterceptor,则应该自己手动导入WebWork中的AroundInterceptor类。
Ø       副文本编辑器标签: Struts 2不再支持WebWork的富文本编辑器,如果应用中需要使用富文本编辑器,则应该使用Dojo的富文本编辑器。
Ø       IoC容器支持: Struts 2不再支持内建的IoC容器,而改为全面支持Spring的IoC容器,以Spring的IoC容器作为默认的Object工厂。
6 . 总 结
本节课大致介绍了 Web应用的开发历史,从历史的角度介绍了Model 1和Model 2的简要模型和特征,进而介绍了MVC模式的主要策略和主要优势。接着介绍了常用的MVC框架,包括JSF、Tapestry和Spring MVC,以及这些框架的基本知识和相关特征。本节重点介绍了Struts 2的两个前身:Struts 1和WebWork,以及这两个框的架构和主要特征,从而引申出对Struts 2起源的介绍。最后大致介绍了Struts 2框架的体系,包括Struts 2框架的架构、标签库、控制器组件等,并就Struts 1和Struts 2的相关方面进行了比较。
 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值