返回列表 回复 发帖

Struts应用转移到Struts2

有很多人都很熟悉 Struts, 无论是从项目中直接获得的实战经验还是从书中了解到的。我们这一系列文章,将通过一个由 Stuts 转移到 Struts2 简单的例子向大家展现Struts2的所有特征。
, p9 Y7 E4 ~8 |0 R忽悠社区是综合性社区网站,将最新、最快、最专业的资讯、新闻,图片,视频奉献给所有爱好者。    在我们开始这个例子之前,你需要去知道一点 Struts2的背景知识。 在第一部分的文章中,我们将介绍Struts2与Struts的核心框架的不同点,以助于更好地了解其他方面的整合。第二部分中,我们将深入探讨 actions 的差别, action相关的框架特征,和action配置。在最后一部分中,我们将会讲述 user interface,我们也会讲到其架构,UI构件,themes 和标签。 还有如何为你的应用加上新的外观。
2 P" x& r  C3 j/ A忽悠,忽悠社区,忽悠论坛.    我们并不打算谈及迁移过程的所有细节方面,我们只是从出发点开始介绍Struts2 的概念和现在可用的所有特征。但拥有这些知识,你将在以后Struts2的应用中无往而不利。
, v  x2 B# _# {* x, o3 X" [* z忽悠,忽悠社区,忽悠论坛.   
7 o; [9 f6 L5 {" d0 N: S忽悠,忽悠社区,忽悠论坛.Struts的历史
8 b: C' [/ h0 R+ J8 R5 }www.huuoo.com    Struts的第一个版本 是在 2001年5月份发布。它提供了一个Web应用的解决方案,如何让 JSPs 和 servlets 共存去提供清晰的分离视图和业务和应用逻辑的架构。在Struts之前,最通常的做法是在JSP中加入业务和应用逻辑,或者在servlets中生成视图。
& `, ?1 v+ k9 u: o忽悠社区    自从第一个版本的发布, Struts 实际上已成为业界公认的Web应用标准。但随着时间的推移,Web应用框架经常变化的需求,产生了几个下一代 Struts的解决方案。其中两个可选方案是Shale 和 Struts Ti。 Shale 是一个基于构建的框架,并在最近成为 Apache 中的重要项目。而 Struts Ti 则是继续坚持 MVC模式的基础上改进,继续Struts的成功经验。
1 D- |3 g$ S4 L+ y# N0 `5 k# A    WebWork项目是在2002年3月发布的,它对Struts式框架进行了革命性改进,引进了不少新的思想,概念和功能,但和原Struts代码并不兼容。WebWork是一个成熟的框架,经过了好几次重大的改进与发布。在2005年12月,WebWork与Struts Ti决定合拼, 再此同时, Struts Ti 改名为 Struts Action Framework 2.0,成为Struts真正的下一代。
0 Y, X1 o# ]& |. m$ Z, V5 M# D9 C
' C  e& r) C& |4 c+ Dwww.huuoo.com请求如何运作
8 P+ N' B( z0 g6 J/ c8 S' `0 d% o  H忽悠社区是综合性社区网站,将最新、最快、最专业的资讯、新闻,图片,视频奉献给所有爱好者。    在我们开始详细探讨如何转移Struts到Struts2之前,让我们来看看整个请求流程在新架构中是如何运作的。你会注意到在整个请求的生命周期,仍是以controller作主体,而且所有的概念还都是你以前所熟悉的, 就如:# m: e* ~( o3 _$ ?1 S. q) a( ?- K
www.huuoo.com. A& J7 y2 r5 q) q' z6 F" z; M; p
  • 通过URL请求的参数来调用Actions来把数据传给server.
  • 所有的Servlet objects (request, response, session,之类.) 仍然可以在Action中获取
下图展示了整个请求的概要过程:
! i) X( S* r$ O- E' S  g忽悠,忽悠社区,忽悠论坛.

image1.jpg
2007-4-5 21:57


  |! r& e% i" f3 u# ]) L6 g忽悠社区是综合性社区网站,将最新、最快、最专业的资讯、新闻,图片,视频奉献给所有爱好者。; Z, P) C! j  y1 W
整个请求过程可以分为六步骤:
$ d5 m! @% S, P6 q8 W% _: c) y
' Z6 h9 {2 p' mwww.huuoo.com
  • 一个请求产生并经由框架处理 - 框架根据请求匹配相应的配置,如使用哪些拦截器,action 类和结果。
  • 请求通过一系列的拦截器 - 拦截器,和拦截器组经配置后,能处理不同等级的请求,它们为请求提供了各种预处理,切面处理。这和Struts的使用 Jakarta Commons Chain 构件的 RequestProcessor类很相似。
  • 调用 Action - 产生一个新的action对象实例,并提供请求所调用的处理逻辑的方法。Struts2 可以在配置action时为请求分配其指定的方法。我们在第二部文章中将对这步骤进行进一步讨论;
  • 调用产生的结果 - 获取通过action的方法处理后返回来的结果,匹配其result class并调用产生的实例。有种情况是在UI模板去生成HTML时才去处理这些结果。如果在这种情况下,在Struts2 模板中的tags能直接返回到 action 中,取结果来呈现界面。 
  • 请求再次经过一系列的拦截器处理后返回 - 请求反顺序通过与原来进入时的拦截器链, 当然,你也可以配置在这个过程中减少或增加拦截器处理.
  • 请求返回到用户 - 最后一步是由 control 返回到servlet。通常是生成HTML返回到user, 但你也可以指定返回的HTTP头或HTTP重定向。
你应该已注意到,Struts2与Struts的差别。最明显的就是Struts2是pull-MVC 架构,就是可以直接从Action中获取所需要的数据,而不是像Struts那样必须把 beans 存到page, request,或者session中才能获取。这个我们将在下一章中详细提及。忽悠社区2 x3 z- q0 \9 E. W

5 n/ A" n' Q5 n3 t3 u- d忽悠社区配置框架& ^- O+ ?: F, l% [
首先最重要的是,让框架能通过web.xml在servlet containers里运行。忽悠社区是综合性社区网站,将最新、最快、最专业的资讯、新闻,图片,视频奉献给所有爱好者。) Q$ n" Q0 m- W+ ]0 P: o
下面这个就是大家都熟悉的 Struts在 web.xml里的配置方法
  1. <servlet>
  2.         <servlet-name>action</servlet-name>
  3.         <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
  4.         <init-param>
  5.             <param-name>config</param-name>
  6.             <param-value>/WEB-INF/struts-config.xml</param-value>
  7.         </init-param>
  8.         <load-on-startup>2</load-on-startup>
  9.     </servlet>

  10.     <servlet-mapping>
  11.         <servlet-name>action</servlet-name>
  12.         <url-pattern>*.do</url-pattern>
  13.     </servlet-mapping>
复制代码
在 Struts2 中,这个有少许改变,最明显的是dispatcher 由servlet转为servlet filter, 其配置和servlet一样简单,如下:
  1. <filter>
  2.        <filter-name>webwork</filter-name>
  3.         <filter-class>
  4.             org.apache.struts.action2.dispatcher.FilterDispatcher
  5.   </filter-class>
  6. </filter>

  7.     <filter-mapping>
  8.    <filter-name>webwork</filter-name>
  9.    <url-pattern>/*</url-pattern>
  10.     </filter-mapping>
复制代码
和servlet配置一样,filter配置定义了名称(供关联)和filter的类。filter mapping让URI匹配成功的的请求调用该filter。默认情况下,扩展名为".action"。忽悠社区& {5 z6 i# F% j" g- I
这个是在default.properties文件里的"struts.action.extension" 属性定义的。5 I- U0 i' \  g$ g% L( J
+ A; M- l5 _4 E; H. f& x
工具箱:  "default.properties"是配置选项定义文件。通过在classpath中包含一个叫"struts.properties"的文件,并设置不同的属性值,你可以覆盖这个默认的配置,实现自己的配置。
对于Struts, servlet配置提供了初始化tag的参数和使用的文件,而Struts2没有这样的配置参数,取而代之的是在classpath下的默认配置文件"struts.xml"。" i% O1 `& j! |# O; C3 s

* c# X# ?' B) q忽悠社区是综合性社区网站,将最新、最快、最专业的资讯、新闻,图片,视频奉献给所有爱好者。
工具箱/提示: Struts actions(扩展名".do"),Struts2 actions(扩展名".action"),所以Struts和Struts2可以在一个系统中共存。所以最好是保持原先的系统,在新功能的开发上用Struts2, 如果时间和资源允许的情况下再逐步迁移。另一种方法是只是把Struts2的扩展名改为".do",可重用JSPs.
分析Actions
5 e1 ?+ J# s8 c1 j- V! n3 ^" v忽悠社区
在上面介绍的请求运作流程中,我们谈及了一些Struts和Struts2的不同点。现在我们将较深入地探讨这两个框架中action结构的具体差别。0 b8 g3 R* z6 D9 p
让我们来回顾一下 Struts 的 action 结构, 主要的形式如下:
  1. public class MyAction extends Action{
  2.     public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request,
  3.                                                   HttpServletResponse response) throws Exception
  4.    {
  5.         // do the work
  6.         return (mapping.findForward("success"));
  7.     }
  8. }
复制代码
当实现一个Struts action时, 你需要注意一下问题:% P& o6 a, {9 h% z  ~. J

7 g. w. A$ }* \! L2 g/ J" ~
  • 所有的action 都必须继承于base Action 类.
  • 所有的action都必须是线程安全的,因为action是单例的,singleton的.
  • 因为所有的action都必须是线程安全的,所以所有的对象都不能是类属性, 都必须以方法参数的形式传值。
  • 调用action的方法必须命名为 "execute" ( 在Struts中的  DispatchAction 类好像可以用其它方法去执行同一个action ,但实际上在框架中调用的仍然是 "execute" 方法。).
  • ActionForward 的结果是通过ActionMapping 类中的方法来产生的,通常是"findForward"方法.
相比较之下, Struts2的action 提供了很多简单的实现。下面就是个例子
  1. public class MyAction{
  2.    public String execute() throws Exception{
  3.         // do the work
  4.         return "success";
  5.    }
  6. }
复制代码
首先你会注意到的是,Struts2中的action不再继承于任何类或需要实现任何接口。实际上,它还远不只这些。按照惯例,只有"execute"方法能调用action, 但在Struts2中并非必要,任何声明为public String methodName() 方法都能通过配置来调用action.. P5 I0 {, r# e" y# F6 I
另外,你会注意到返回值不再是"ActionForward ",而是String, 如果你需喜欢String的形式,那在Action接口里有个帮助方法可以提供简单的结果常量,如"success", "none", "error", "input" 和 "login"。
# x1 D" Z! ?2 e7 U* o$ M( ?最后,和Struts最大的革命性的不同是, 调用action不再是带参数的。那你如何在获得你所需要的值呢?答案是"inversion of control" 或 "dependency injection", 反转控制(想了解更多可以看Martin Fowler的文章 http://www.martinfowler.com/articles/injection.html)。
$ T7 R+ X7 t: N7 A2 b6 {+ k# n为了更好地了解反转控制,让我们来看看一个例子,如何在action处理过程中可以访问到HttpServerRequest 。在我们的例子中,我们用ServletRequestAware 接口,这个接口包含了相应属性的setter,如下
  1. public interface ServletRequestAware {
  2.     public void setServletRequest(HttpServletRequest request);
  3. }
复制代码
当我们继承这个接口时,我们需要通过setter为我们的HttpServerRequest 属性变量赋值:
  1. public class MyAction implements ServletRequestAware{
  2.    private HttpServletRequest request;
  3.    public void setServletRequest(HttpServletRequest request) {
  4.         this.request = request;
  5.    }
  6.    public String execute() throws Exception{
  7.         // do the work using the request
  8.         return Action.SUCCESS;
  9.    }
  10. }
复制代码
看起来现在这些属性是类级别的,并不是线程安全的,但是在Struts2里并没有问题,因为每个请求过来的时候都会产生一个新的action对象实例,它并没有和其他请求共享一个对象,所以不需要考虑线程安全问题。3 w" E: I$ I' I! l% r
; l7 s% W7 N/ K8 Z" t% L: m, s
现在我们还有最后一步,就是把action关联上ServletConfigInterceptor 拦截器。这个拦截器继承了ServletRequestAware 接口,并提供了把HttpServletRequest 注入到action中的功能。但是你现在不用担心如何配置这些,我们将在下一篇文章中具体讲述。最重要的是我们明白了拦截器和接口共同为action提供了反转控制的功能。
8 @9 Z) Y; e' h) W$ x; A这个设计的好处是能让action完全和框架解耦。action仅仅是一个被框架使用的简单的POJO。这对于单元测试但来极大的好处, 你能方便的为Struts action实现 StrutsTestCase 或  MockStrutsTestCase 单元测试。
7 ?, n! r6 Q: b$ I  Q& ^忽悠社区
0 X3 E7 X3 @: z1 B0 ~, owww.huuoo.com总结
5 `4 z7 O+ S2 U6 e. f9 vwww.huuoo.com
By到现在为止,你应该已经了解了Struts2的整个请求流程,还有高层的框架概念, 你也应该能自己动手配置Struts2的action,和讲出Struts和Struts2的差别了。忽悠,忽悠社区,忽悠论坛.7 }3 ^7 z9 S: L
在下篇文章中,我们将会介绍一个详细的Struts应用向Struts2迁移的例子,同时我们也会介绍迁移中相关的知识,会讲述如何综合使用JSTL, JSP 和 Struts2,进一步讲述Struts和Struts2的action的差别,Struts2的配置和其他框架元素,和谈到更多的其他相关框架的特征。
我们已经从较高层解释了整个框架的结构,请求流程的基础,配置方式和Struts2和Struts1的不同之处。了解这些后从Struts 应用 迁移到 Struts 2 不再是难事。
- Q7 `6 n3 S8 j/ K0 v忽悠,忽悠社区,忽悠论坛.    在这篇文章中,我们将会更详细地讲述如何由Struts 的action转为Struts 2的action。
! i4 f* J% a$ x& J
0 `& x/ W1 w! A  \5 r! O+ p. B一个应用的例子www.huuoo.com$ D: k0 S; D) X
这个例子选择了大家都熟悉的 - weblog. 简单地介绍下这例子的功能需求:( l$ W  i# B. |' S
  • 增加一个新的日志
  • 察看一个日志
  • 修改一个日志
  • 删除一个日志
  • 列出所有日至
     增删修改(CRUD),是项目中最为普遍的应用。
2 q0 y6 A9 j2 i8 z# G3 `9 X忽悠社区     业务逻辑类在Struts 和 Struts2 应用都是可共用的。如:
  1. public class BlogService{
  2.      private static List<Blog> blogs = new ArrayList<Blog>();
  3.      public List<Blog> list() {  ... }
  4.      public Blog create(Blog blog) {  ... }
  5.      public void update(Blog blog) {  ... }
  6.      public void delete(int id){  ... }
  7.      public Blog findById(int id){  ... }
  8. }
复制代码
BlogService 只是个简单的业务逻辑类,并不是接口,Struts 和 Struts2 的action皆可调用其实例。虽然这样设计在实际项目中会带来不必要的耦合,但我们的例子只是集中在讨论web层上,所以无关重要。
& Z1 x) q& U8 @* k9 p忽悠社区是综合性社区网站,将最新、最快、最专业的资讯、新闻,图片,视频奉献给所有爱好者。
# [9 F" n/ v0 T& w" o- W忽悠,忽悠社区,忽悠论坛.工具箱: 在第一篇文章中,我们谈论了在Struts2 actions中的依赖注入的接口注入方式。这个是servlet 相关类(HttpServletRequest, HttpServletResponse, PrincipalProxy, 等.)的主要注入方式,但这并不是唯一的方式。
" p  p. `+ y% S. R+ ?0 r! b# W
: ?6 u  M7 J3 l) P忽悠社区是综合性社区网站,将最新、最快、最专业的资讯、新闻,图片,视频奉献给所有爱好者。Struts2 可以使用Spring框架作为默认的容器时,依赖注入的setter方法就可用了。通过在action中加入setter方法(如下演示), Struts2 框架将能从Spring框架中获得正确的信息,并通过setter加载在action中。
  1. public void setBlogService(BlogService service){
  2.      this.blogService = service;
  3. }
复制代码
和接口注入方式类似,我们需要一个拦截器来帮助我们完成任务,这就是 ActionAutowiringInterceptor 拦截器。这样我们的业务逻辑类就通过Spring框架管理自动在action被调用之前注入到Struts2得action中。有多种的配置参数(如by name, by type 或 automatically)可供选择,可以让对象和setter匹配的注入的方式根据你的需要而定。
6 d  z: k% A: h! z: T' }4 F: \) k! R/ a$ q) w  j+ _6 U
Struts 应用中的代码忽悠社区& l( {$ ^8 d" g' W8 d5 M
     我们首先从Struts讲起。在Struts中,最普遍的做法是,对于每个需求用例(如save,update,remove,list)来说都会有对应的action类,同时也会有相应的action form类。在我们的应用中的这个方式或许不是最佳的实现方式(其他的解决方案包括使用dynamic form或者使用request来分发action),但我们例子中的做法是所有Struts开发者最熟悉的一种形式。了解了这种简单的实现方法,你有能力在迁移到Struts2时,使用更为优秀的方法。
' {, z; J% m# x5 e8 ]+ a- s在第一篇文章中我们谈过Struts 和 Struts2 中action的区别。现在我们从UML中再次看看他们的差别。一般来说form在Struts action中的表现形式是:www.huuoo.com' Q+ f' x3 n9 U7 E# X7 C
忽悠社区是综合性社区网站,将最新、最快、最专业的资讯、新闻,图片,视频奉献给所有爱好者。1 `" ~/ L4 D& c+ c8 [( d

image2.jpg
2007-4-5 22:00

/ u0 m, J; F" S: h1 c" M

+ E! ~2 e2 k3 J6 m0 Fwww.huuoo.com这action form将会在多个action中使用,让我们来看看它:
  1. public class BlogForm extends ActionForm {

  2.      private String id;
  3.      private String title;
  4.      private String entry;
  5.      private String created;

  6.      // public setters and getters for all properties
  7. }
复制代码
如UML中展示的那样,其中一个限制就是必须继承ActionForm类,另外一个限制就是form中所有属性都必须是String类型,所以所有的getter和setter都必须只能接受String参数和返回String结果。' ?% `! O  Z& n+ R1 \

- J: p1 I; D" P/ A0 Q1 y8 n& j忽悠社区是综合性社区网站,将最新、最快、最专业的资讯、新闻,图片,视频奉献给所有爱好者。然后我们来看看action。我们这个例子中的action有view, create 和 update action。忽悠,忽悠社区,忽悠论坛.9 M1 G4 s( M" B+ m  B4 h% z% x
The View Action:www.huuoo.com+ e8 {6 _; g$ z1 a0 w
The Create Action:
  1. public class ViewBlogAction extends Action{

  2.      public ActionForward execute(ActionMapping mapping,
  3.                                   ActionForm form,
  4.                                   HttpServletRequest request,
  5.                                   HttpServletResponse response)
  6.              throws Exception {

  7.          BlogService service = new BlogService();
  8.          String id = request.getParameter("id");
  9.          request.setAttribute("blog",service.findById(Integer.parseInt(id)));

  10.           return (mapping.findForward("success"));
  11.      }
  12. }
复制代码
  1. public class SaveBlogEntryAction extends Action {

  2.       public ActionForward execute(ActionMapping mapping,
  3.                                   ActionForm form,
  4.                                   HttpServletRequest request,
  5.                                   HttpServletResponse response)
  6.              throws Exception{

  7.          BlogService service = new BlogService();
  8.          BlogForm blogForm = (BlogForm) form;
  9.          Blog blog = new Blog();
  10.          BeanUtils.copyProperties( blog, blogForm );

  11.          service.create( blog );

  12.          return (mapping.findForward("success"));
  13.      }
  14. }
复制代码
The Update Action:
  1. public class UpdateBlogEntryAction extends Action{

  2.      public ActionForward execute(ActionMapping mapping,
  3.                                   ActionForm form,
  4.                                   HttpServletRequest request,
  5.                                   HttpServletResponse response)
  6.              throws Exception{

  7.          BlogService service = new BlogService();
  8.          BlogForm blogForm = (BlogForm) form;

  9.          Blog blog = service.findById( Integer.parseInt(blogForm.getId()));
  10.          BeanUtils.copyProperties( blog, blogForm );
  11.          service.update( blog );
  12.          request.setAttribute("blog",blog);

  13.          return (mapping.findForward("success"));
  14.      }
  15. }
复制代码
这三个action都跟随着同一个模式: 忽悠社区$ i1 E" G! D% O2 r3 P* h7 c
忽悠社区是综合性社区网站,将最新、最快、最专业的资讯、新闻,图片,视频奉献给所有爱好者。4 L: N6 O% M% E) u
  • 产生一个新的业务逻辑对象实例 - 如前面所提到的,我们使用最直接的方式在action中使用业务逻辑对象,这表示在每个action中都会产生新的业务逻辑对象实例。
  • 从请求中获得数据 - 这是两种形式之一。在view action中,"id"是从HttpServletRequest 对象中直接获取的。而在create 和 update action 中,则从ActionForm 中取值。ActionForm 与 HttpServletRequest 的调用方式其实很相似,唯一不同的ActionForm 是bean的从field中取值。
  • 调用业务逻辑- 现在开始生成调用业务逻辑所需的参数并调用逻辑。如果参数(在view action中)是一个简单对象类型,则转换值时会自动转为正确的类型(如从String转到Integer)。如果参数是复杂的对象类型,,则ActionForm 需要通过BeanUtil 来帮忙转成相应的对象。
  • 设定返回的数据 - 如果需要把数据返回显示给用户,那则要把这个数据设在HttpServletRequest 的attribute 中返回。
  • 返回一个 ActionForward - 所有 Struts action的最后都需要找到并返回其相应的 ActionForward 对象.
     最后的两个action,remove和list action, 只有很少的差别。remove action如下所示,没有用BlogForm类. 通过从request的attribute中获取"id"(和view action相似),就能调用业务逻辑完成其需要的工作。在下面我们介绍配置时,你可以看到它并没有返回任何数据,因为它的"success"返回结果其实是执行remove后再执行了list action来返回信息的。
  1. public class RemoveBlogEntryAction extends Action {

  2.      public ActionForward execute(ActionMapping mapping,
  3.                                   ActionForm form,
  4.                                   HttpServletRequest request,
  5.                                   HttpServletResponse response)
  6.              throws Exception {

  7.          BlogService service = new BlogService();
  8.          String id = request.getParameter("id");
  9.          service.delete(Integer.parseInt(id));

  10.          return (mapping.findForward("success"));
  11.      }
  12. }
复制代码
list action并不需要任何的用户输入,它只是简单地调用了业务逻辑的无参方法,同时返回所有的Blog对象。
  1. public class ListBlogsAction extends Action {

  2.      public ActionForward execute(ActionMapping mapping,
  3.                                   ActionForm form,
  4.                                   HttpServletRequest request,
  5.                                   HttpServletResponse response)
  6.              throws Exception{

  7.          BlogService service = new BlogService();
  8.          request.setAttribute("bloglist",service.list());

  9.          return (mapping.findForward("success"));
  10.      }
  11. }
复制代码
向 Struts2 迁移忽悠社区; a, j! z7 K+ }- A( V9 k; n; o
     在Struts2中,可选的实现方式有很多,可以像Struts那样每个需求用例对应一个action,也可以用一个action对应所有需求用例。但在我们的例子中,使用的方法是我认为最佳的解决方案 - 在一个action类中实现整套CRUD功能。! j$ ?) A$ I# f$ V7 i6 r
     也许你人为把list需求用例也同样地整合到同一个action类里会比较好,而我认为把list的功能分到另外一个action中,会减少容易产生的混淆,因为list用例中并不需要Blog这个类作为属性,而在其他用例中则需要。3 f# N- r# s# N. U8 D

. l8 h8 y2 ^- [$ Z% [/ H7 k对于 Struts2的例子, 它的UML模型展示如下:
: t7 E4 u0 u0 |7 A+ M+ I( m& M8 t忽悠,忽悠社区,忽悠论坛.
% l5 f9 ^0 z1 z5 z! f7 ]6 a1 x0 y& h* J5 J+ I/ i+ i6 e

image3.jpg
2007-4-5 22:00

7 D. K: J8 ?; O$ N7 q$ Q

( g$ G  Z1 d4 j: t* w) E忽悠社区     每个用例在action中都有自己所对应的方法。从上图中我们可以看到,在BlogAction 中我们有save, update 和 remove三个方法。而ListBlogAction中,没有list这个方法,因为ListBlogAction继承了ActionSupport 类,实际上就是在默认的execute 方法中实现list功能。www.huuoo.com# _- M+ |3 i. h* d% x" I( R% o
     为了更容易看,图中的BlogAction并没有画出它所实现了的三个接口。它们分别是ServletRequestAware 接口,  Prepareable 接口和 ModelDriven 接口。忽悠社区是综合性社区网站,将最新、最快、最专业的资讯、新闻,图片,视频奉献给所有爱好者。7 _; U0 j5 k' g+ A" o0 i
     首先回顾一下ServletRequestAware, 我们在第一篇文章中已经详细介绍它了。这个ParametersInterceptor 拦截器提供了把HttpServletRequest 自动set到action中的功能,让我们能通过request, 把所需的值传回到JSPs。忽悠,忽悠社区,忽悠论坛.2 L! @! z3 g' v0 u0 n7 H
     接着看看Preparable 接口, 它会联合PrepareInterceptor拦截器一起工作,让action在执行execute() 方法前, 执行一个prepare()方法,实现在执行前设定,配置或预设一些值在action中。 在我们的例子里,prepare方法会检查blogId 属性,如果为零则这是一个新日志,非零则该日志已经存在,根据blogId取出日志。
; P/ Y* f( a# J( W( N9 U1 F忽悠,忽悠社区,忽悠论坛.     最后我们说说ModelDriven 接口,在上一篇文章中,我们已经了解到 Struts action的很大的不同在于它是需要线程安全的,而在Struts2中则没有这个限制,因为每次的请求都会有一次action对象的初始化和调用。没有了这个限制,能允许Struts2使用类级别的属性变量(特别是getters和setters),从而获得更多编码优势。! p7 Q8 p3 D( @3 L3 I' w

' j+ D% O9 H2 k5 P和拦截器的功能结合起来, 把HttpServletRequest 中的attribute 注入action中的流程如下所示:忽悠社区( _7 w2 {# s9 Y' }8 o! B' q# }9 u

7 M" e& L7 P( ^0 ?- X) R1 i: o" M
  • 循环读取HTTP request中的attribute
  • 查找当前request attribute中是否有和action中的setter中的属性匹配的
  • 有则根据attribute从HttpServletRequest 里取出其值
  • 把取出来的值由String转成setter中相应的类型
  • 调用setter把该转换后的值注入action中
提示: 当调用action时,如果发现不明原因使不能正确地通过setter注入值情况下,第一步最好是先检查下各拦截器,确保它们都已作用于该action。因为这些意外通常有时由拦截器设置不当形成的,检查是否各个拦截器都已起作用,并看看它们作用的顺序,因为有些情况下它们间会相互影响而产生错误。
    现在我们已经有基于String类型的form bean中取值的方法或者是自动把request的attributes 注入到action的方法,那下一步就是如何把值传入 domain object 或 value / transfer object的属性中去。其实这很简单,你只需要实现ModelDriven 接口(即实现getModel()方法)就可以了,确保ModelDrivenInterceptor 拦截器已作用于action。
* @+ b$ x( C: M$ _& w8 o$ Jwww.huuoo.com    除了会调用action中的setter外,model 首先检查是否有和setter可以匹配当前的attribute名。如果在model中没有这个attribute相应的setter,则会再在action上找相应的setter来设值。忽悠社区3 ]0 L9 A  K6 L) H( c0 T
    在BlogAction 的例子中我们可以看到如何很灵活地使用这些方法,首先通过prepare() 方法根据Id获取相应的 Blog model object 或新建一个instance, 然后再根据把request中相应的属性注入Blog instance中和action中。
  A9 ?8 _& T! t3 vwww.huuoo.com    以上的两个功能使得现在调用action那么简单 - 调用具体的业务逻辑,和把数据设在HttpServletRequest供返回用
  1. public class BlogAction extends ActionSupport
  2.          implements ModelDriven, Preparable, ServletRequestAware{

  3.      private int blogId;
  4.      private Blog blog;
  5.      private BlogService service = new BlogService();
  6.      private HttpServletRequest request;

  7.      public void setServletRequest(HttpServletRequest httpServletRequest){
  8.          this.request = httpServletRequest;
  9.      }

  10.       public void setId(int blogId){
  11.          this.blogId = blogId;
  12.      }

  13.       public void prepare() throws Exception{
  14.          if( blogId==0 ){
  15.              blog = new Blog();
  16.          } else{
  17.              blog = service.findById(blogId);
  18.          }
  19.      }

  20.       public Object getModel() {
  21.          return blog;
  22.      }

  23.       public String save(){
  24.          service.create(blog);
  25.          return SUCCESS;
  26.      }
  27.       public String update() {
  28.          service.update(blog);
  29.          request.setAttribute("blog",blog);
  30.          return SUCCESS;
  31.      }

  32.       public String remove() {
  33.          service.delete(blogId);
  34.          return SUCCESS;
  35.      }

  36.       public String execute() {
  37.          request.setAttribute("blog",blog);
  38.          return SUCCESS;
  39.      }
  40. }
复制代码
最后就是说说 list这个用例了。它同样需要访问HttpServletRequest对象去返回数据给JSP,所以也需要实现ServletRequestAware 接口。但是,因为它并不需要任何输入值,所以就不需要实现其他的接口了。以下是它的具体实现:
  1. public class ListBlogsAction extends ActionSupport implements ServletRequestAware{

  2.      private BlogService service = new BlogService();
  3.      private HttpServletRequest request;

  4.      public void setServletRequest(HttpServletRequest httpServletRequest){
  5.          this.request = httpServletRequest;
  6.      }

  7.      public String execute() ...{
  8.          request.setAttribute("bloglist",service.list());
  9.          return SUCCESS;
  10.      }
  11. }
复制代码
这样就完成了我们该实现的action代码了。 在下一篇文章中,当我们新的Struts2用户界面结合时,我们还会进一步简化action的代码。
! ~! E3 x2 H4 E# ^( U# b3 Q忽悠社区
. _- P) i( F' N! F* o
% h/ ^9 m! ~3 S" F! I忽悠,忽悠社区,忽悠论坛.配置Actions忽悠社区是综合性社区网站,将最新、最快、最专业的资讯、新闻,图片,视频奉献给所有爱好者。9 g, I$ i1 j; r1 _
    在我们调用action之前,我们必须通过XML配置文件去配置它们。
, q- W* o: g% j5 y" x/ g, B& Z, H忽悠社区    在Struts中, 我们习惯用在WEB-INF 目录的"struts-config.xml"配置文件,在这里我们需要配置action form和action属性。在Struts2中, 用的是在classpath中的"struts.xml"配置文件, 它看起来好象会稍微复杂一些,因为它需要在配置action的同时也配置其拦截器。忽悠社区1 |! o. T1 x$ W
忽悠社区是综合性社区网站,将最新、最快、最专业的资讯、新闻,图片,视频奉献给所有爱好者。6 q6 r8 d0 \! X
    在Struts中配置 form-beans 节点很容易, 只需要一个唯一的名字,还有就是继承ActionForm类的class作为type。
  1. <struts-config>

  2.      <form-beans>
  3.          <form-bean name="blogForm" type="com.fdar.articles.infoq.conversion.struts.BlogForm"/>
  4.      </form-beans>
  5.           ...
  6. </struts-config>
复制代码
在我们的例子中,我们的配置文件有3点不相同:忽悠社区是综合性社区网站,将最新、最快、最专业的资讯、新闻,图片,视频奉献给所有爱好者。$ [7 F/ w* J& Y! D5 h
1. 重定向配置www.huuoo.com3 y% P/ b* Y/ ^. A( U
    在Struts的配置中,每个mapping 都需要提供调用action时所需要对应的路径,Struts默认为".do", 例如paht是"/struts/add"对应于URL"/struts/add.do"。同时也需要一个forward 属性来提供给URL去转向,如"/struts/add.jsp".
  1. <struts-config>
  2.      ...
  3.       <action-mappings>

  4.           <action path="/struts/add" forward="/struts/add.jsp"/>
  5.          ...

  6.       </action-mappings>
  7. </struts-config>
复制代码
而Struts2的需要更多的一些配置,如:
  1. <struts>
  2.       <include file="struts-default.xml"/>

  3.       <package name="struts2" extends="struts-default" namespace="/struts2">

  4.           <action name="add" >
  5.              <result>/struts2/add.jsp</result>
  6.          </action>
  7.          ...
  8.       </package>
  9. </struts>
复制代码
首先你会注意到的是,代替action-mappings 节点的是includepackage 节点。Struts2可以把配置细分到任意数目的配置文件中,来实现配置可模块化管理。每个配置文件的结构其实都是一样的,不同的只是文件名。
. ]; |: Z/ z( }) X% m( [www.huuoo.com    include 节点中,以文件名作为file 属性,可把所include的文件内容包含到当前文件中。
9 Y, j- S' }- j$ j, vwww.huuoo.com   package 节点把actions组成一组,其name 属性的值必须是唯一的。www.huuoo.com0 V6 k4 _. @- m  R$ G
   在 Struts action的配置中, paht属性需要指定完整的URL路径。而在Struts2中,URL是通过package节点中的namespace属性,还有在action 节点中的name 属性, 和action扩展(默认是".action")共同起作用的。在上面的例子中,则URL为"/struts2/add.action"时会调用action。. S/ L# k0 W- L; E
   package节点除了可以分离命名空间外, package 节点中的 extends 属性,还提供了某种可复合的组成结构。通过继承另外一个package节点,你就能继承那个节点的配置,包括其actions, results, interceptors, exception,等值。在我们的例子中,"struts2" package节点继承了 "struts-default" package 节点(在"struts-default.xml" 文件里定义了该节点) ,注意这个是主要的include文件,所以必须在所有配置之前的第一行中写出。 这个功能有助于大大减少你重复性输入默认配置所浪费的时间。忽悠社区1 H7 T- Y2 m$ t4 k
    最后是result 节点, 它只是存放你这个action所需要转向的URL. 在这里我们没有提及nametype 属性。如果你不想改变它们的默认属性的话,你能忽略不写它们,让你的配置文件看起来更清晰。从action返回的 "success" 的结果将组成这个JSP显示给用户。忽悠,忽悠社区,忽悠论坛.9 [% x" r% G) W! c4 u5 _6 w

  U' F- V; o: d0 w忽悠社区是综合性社区网站,将最新、最快、最专业的资讯、新闻,图片,视频奉献给所有爱好者。忽悠,忽悠社区,忽悠论坛.* Y* J# g$ D- R3 d/ [% O, }
2. Action 配置忽悠社区是综合性社区网站,将最新、最快、最专业的资讯、新闻,图片,视频奉献给所有爱好者。: R$ \% a4 B0 s; Z$ U" |
    在Struts 中forward 节点指定了action处理后,结果将重定向到哪个相应的页面。type属性指定了action的类,scope 属性保证了form beans只在request范围内。
  1. <struts-config>
  2.      ...
  3.       <action-mappings>

  4.           <action path="/struts/list" scope="request"
  5.                  type="com.fdar.articles.infoq.conversion.struts.ListBlogsAction" >
  6.              <forward name="success" path="/struts/list.jsp"/>
  7.          </action>
  8.          ...
  9.       </action-mappings>
  10. </struts-config>
复制代码
Struts2 的 XML配置和上面提到的基本相同。唯一不同的就是通过class属性为action节点提供了它所需要调用的类的完整路径
  1. <struts>
  2.      ...
  3.       <package name="struts2" extends="struts-default" namespace="/struts2">
  4.           <default-interceptor-ref name="defaultStack"/>
  5.           <action name="list"  class="com.fdar.articles.infoq.conversion.struts2.ListBlogsAction">
  6.              <result>/struts2/list.jsp</result>
  7.              <interceptor-ref name="basicStack"/>
  8.          </action>
  9.          ...
  10.       </package>
  11. </struts>
复制代码
如果是用其他的方法而不是用默认的execute 方法去调用action(在BlogAction 类中大多数方法如此), 则需要在action节点的 method 属性里加入方法名,下面就是个例子,这时候update方法将会被调用。
  1. <action name="update" method="update"  class="com.fdar.articles.infoq.conversion.struts2.BlogAction" >
  2.         ...
  3.     </action>
复制代码
default-interceptor-refinterceptor-ref 节点有几点不同。在第一篇文章中,我们看到在action被调用之前必须通过一系列的拦截器,而这两个节点就是用来配置拦截器组的。default-interceptor-ref 节点为该package提供了默认的拦截器组。当在action节点中提供 interceptor-ref节点时 ,它就会覆盖默认的拦截器(interceptor-ref 节点能够和单独一个拦截器相关联,或者跟一个拦截器组相关联),在action节点中可以存在多个interceptor-ref节点,处理拦截器组的顺序会和该节点列出的顺序一致。
) O% k8 m& _; A9 B2 j. hwww.huuoo.com
$ x8 e! `. u5 c" Q% o+ v6 k
! W- x2 o8 f. awww.huuoo.com3. 再重定向配置
9 b4 C$ Z: y9 k' M& a. Z2 r
    当我们提交表格的时候,我们需要重定向到更新后的结果页面。这个通常称为 "post-redirect pattern" 或, 最近出现的, "flash scope."( \8 C3 [2 z$ C1 W/ _: X' l
    由于这是一个form, 所以在Struts中我们需要为Struts指定一个ActionForm。需要在name属性中提供form的名称,同样地,我们也需要在forward 节点中举加入redirect属性为true。
  1. <struts-config>
  2.      ...
  3.       <action-mappings>
  4.           <action path="/struts/save"  type="com.fdar.articles.infoq.conversion.struts.SaveBlogEntryAction"
  5.                  name="blogForm" scope="request">
  6.              <forward name="success" redirect="true" path="/struts/list.do"/>
  7.           </action>
  8.          ...
  9.       </action-mappings>
  10. </struts-config>
复制代码
Struts2 在result 节点里提供了type 属性, 默认情况下是"dispatch", 如果需要重定向,则需要设为 "redirect"。
  1. <struts>
  2.      ...
  3.       <package name="struts2" extends="struts-default" namespace="/struts2">
  4.           <action name="save" method="save"  class="com.fdar.articles.infoq.conversion.struts2.BlogAction" >
  5.              <result type="redirect">list.action</result>
  6.              <interceptor-ref name="defaultStack"/>
  7.           </action>
  8.          ...
  9.       </package>
  10. </struts>
复制代码
总结www.huuoo.com6 S: y0 q: B3 P2 t0 v
    我们并不可能在这篇文章中覆盖所有的内容,如果你需要更好的了解整个框架,还有其他的实现方式和选项,这里有几点可以供你参考:0 O2 {5 Q5 j* F
5 ^8 ?- w6 `8 M2 e, J
忽悠社区& A; M/ y8 a3 r! H: {
  • 配置拦截器和拦截器组 - 以Struts2-core JAR 包里的"struts-default.xml" 文件作为例子。"struts-default.xml" 演示了如何配置你自己的拦截器组,包含新的拦截器,你可以尝试实现自己的拦截器。
  • 配置文件中的通配符模式 - 你可以选择使用Struts2中的通配符模式来简化你的配置。
  • 通过 ParameterAware 接口把form值传入maps中 - 你可以在Struct2中配置,让所有request的form属性都存于action的一个map中,这样就不需要专门再为action指定model / transfer / value object了。这和Struts的dynamic form特点很相似。
    也许到现在为,也许你有个疑问,"迁移后我们的界面是否可以完全重用呢?",答案是yes。你能从这里, 下载到我这篇文章中的完整源代码,你可以自己尝试把URL的扩展名由".do" 改为 ".action",使用的页面时一样的。除此之外,其实用JSTL来代替Struts taglib也是很容易的。5 `4 ^2 c8 e8 N+ ]  i$ ?
在下一篇文章中,我们将讲述用户界面,讨论themes 和 tags; 如何做validation;  如何重用UI控件。
返回列表
高级回复 | 发新话题
B Color Image Link Quote Code Smilies
换一个