将Struts应用迁移到Struts2(二)

在上篇文章中,我们已经从较高层解释了整个框架的结构,请 求流程的基础,配置方式和Struts2和Struts1的不同之处。了解这 些后从Struts 应用 迁移到 Struts 2 不再是难事。

在这篇文章中,我们将会更详细地讲述如何由Struts 的action 转为Struts 2的action。

一个应用的例子

这个例子选择了大家都熟悉的 – weblog. 简单地介绍下这例子 的功能需求:

增加一个新的日志

察看一个日志

修改一个日志

删除一个日志

列出所有日至

增删修改(CRUD),是项目中最为普遍的应用。

业务逻辑类在Struts 和 Struts2 应用都是可共用的。如:

public class BlogService ...{private static List blogs = new ArrayList();public List list() ...{ ... }public Blog create(Blog blog) ...{ ... }public void update(Blog blog) ...{ ... }public void delete(int id) ...{ ... }public Blog findById(int id) ...{ ... }}

BlogService 只是个简单的业务逻辑类,并不是接口,Struts 和 Struts2 的action皆可调用其实例。虽然这样设计在实际项目 中会带来不必要的耦合,但我们的例子只是集中在讨论web层上, 所以无关重要。

QUOTE:

工具箱: 在第一篇文章中,我们谈论了在Struts2 actions中的 依赖注入的接口注入方式。这个是servlet 相关类 (HttpServletRequest, HttpServletResponse, PrincipalProxy, 等.)的主要注入方式,但这并不是唯一的方式。

Struts2 可以使用Spring框架作为默认的容器时,依赖注入的 setter方法就可用了。通过在action中加入setter方法(如下演示) , Struts2 框架将能从Spring框架中获得正确的信息,并通过 setter加载在action中。

public void setBlogService(BlogService service) ...{this.blogService = service;}

和接口注入方式类似,我们需要一个拦截器来帮助我们完成任 务,这就是 ActionAutowiringIntercepTor 拦截器。这样我们的 业务逻辑类就通过Spring框架管理自动在action被调用之前注入到 Struts2得action中。有多种的配置参数(如by name, by type 或 automatically)可供选择,可以让对象和setter匹配的注入的方式 根据你的需要而定。

Struts 应用中的代码

我们首先从Struts讲起。在Struts中,最普遍的做法是,对于 每个需求用例(如save,update,remove,list)来说都会有对应的 action类,同时也会有相应的action form类。在我们的应用中的 这个方式或许不是最佳的实现方式(其他的解决方案包括使用 dynamic form或者使用request来分发action),但我们例子中的做 法是所有Struts开发者最熟悉的一种形式。了解了这种简单的实现 方法,你有能力在迁移到Struts2时,使用更为优秀的方法。

在第一篇文章中我们谈过Struts 和 Struts2 中action的区别 。现在我们从UML中再次看看他们的差别。一般来说form在Struts action中的表现形式是: 下图 image1.jpg

这action form将会在多个action中使用,让我们来看看它:

public class BlogForm. extends ActionForm. ...{private String id;private String title;private String entry;private String created;// public setters and getters for all properties}

如UML中展示的那样,其中一个限制就是必须继承ActionForm类 ,另外一个限制就是form中所有属性都必须是String类型,所以所 有的getter和setter都必须只能接受String参数和返回String结果 。

然后我们来看看action。我们这个例子中的action有view, create 和 update action。

The View Action:

The Create Action:

public class ViewBlogAction extends Action ... {public ActionForward execute(Actionmapping mapping,ActionForm. form,HttpServletRequest request,HttpServletResponse response)throws Exception ...{BlogService service = new BlogService();String id = request.getParameter("id");request.setAttribute("blog",service.findById (Integer.parseInt(id)));return (mapping.findForward("success"));}}public class SaveBlogEntryAction extends Action ...{public ActionForward execute(Actionmapping mapping,ActionForm. form,HttpServletRequest request,HttpServletResponse response)throws Exception ...{BlogService service = new BlogService();BlogForm. blogForm. = (BlogForm) form;Blog blog = new Blog();BeanUtils.copyProperties( blog, blogForm. );service.create( blog );return (mapping.findForward("success"));}}public class UpdateBlogEntryAction extends Action ... {public ActionForward execute(Actionmapping mapping,ActionForm. form,HttpServletRequest request,HttpServletResponse response)throws Exception ...{BlogService service = new BlogService();BlogForm. blogForm. = (BlogForm) form;Blog blog = service.findById( Integer.parseInt (blogForm.getId()));BeanUtils.copyProperties( blog, blogForm. );service.update( blog );request.setAttribute("blog",blog);return (mapping.findForward("success"));}}

这三个action都跟随着同一个模式:

产生一个新的业务逻辑对象实例 – 如前面所提到的,我们使用 最直接的方式在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来返回信息的。

public class RemoveBlogEntryAction extends Action ...{public ActionForward execute(Actionmapping mapping,ActionForm. form,HttpServletRequest request,HttpServletResponse response)throws Exception ...{BlogService service = new BlogService();String id = request.getParameter("id");service.delete(Integer.parseInt(id));return (mapping.findForward("success"));}}

list action并不需要任何的用户输入,它只是简单地调用了业 务逻辑的无参方法,同时返回所有的Blog对象。

public class ListBlogsAction extends Action ... {public ActionForward execute(Actionmapping mapping,ActionForm. form,HttpServletRequest request,HttpServletResponse response)throws Exception ...{BlogService service = new BlogService();request.setAttribute("bloglist",service.list());return (mapping.findForward("success"));}}

向 Struts2 迁移

在Struts2中,可选的实现方式有很多,可以像Struts那样每个 需求用例对应一个action,也可以用一个action对应所有需求用例 。但在我们的例子中,使用的方法是我认为最佳的解决方案 – 在一 个action类中实现整套CRUD功能。

也许你人为把list需求用例也同样地整合到同一个action类里 会比较好,而我认为把list的功能分到另外一个action中,会减少 容易产生的混淆,因为list用例中并不需要Blog这个类作为属性, 而在其他用例中则需要。

对于 Struts2的例子, 它的UML模型展示如下:

下图image2.jpg

每个用例在action中都有自己所对应的方法。从上图中我们可 以看到,在BlogAction 中我们有save, update 和 remove三个方 法。而ListBlogAction中,没有list这个方法,因为 ListBlogAction继承了ActionSupport 类,实际上就是在默认的 execute 方法中实现list功能。

为了更容易看,图中的BlogAction并没有画出它所实现了的三 个接口。它们分别是ServletRequestAware 接口, Prepareable 接口和 ModelDriven 接口。

首先回顾一下ServletRequestAware, 我们在第一篇文章中已经 详细介绍它了。这个ParametersIntercepTor 拦截器提供了把 HttpServletRequest 自动set到action中的功能,让我们能通过 request, 把所需的值传回到JSPs。

接着看看Preparable 接口, 它会联合PrepareIntercepTor拦 截器一起工作,让action在执行execute() 方法前, 执行一个 prepare()方法,实现在执行前设定,配置或预设一些值在action 中。 在我们的例子里,prepare方法会检查blogId 属性,如果为零 则这是一个新日志,非零则该日志已经存在,根据blogId取出日志 。

最后我们说说ModelDriven 接口,在上一篇文章中,我们已经 了解到 Struts action的很大的不同在于它是需要线程安全的,而 在Struts2中则没有这个限制,因为每次的请求都会有一次action 对象的初始化和调用。没有了这个限制,能允许Struts2使用类级 别的属性变量(特别是getters和setters),从而获得更多编码优势 。

和拦截器的功能结合起来, 把HttpServletRequest 中的 attribute 注入action中的流程如下所示:

循环读取HTTP request中的attribute

查找当前request attribute中是否有和action中的setter中的 属性匹配的

有则根据attribute从HttpServletRequest 里取出其值

把取出来的值由String转成setter中相应的类型

调用setter把该转换后的值注入action中

QUOTE:

提示: 当调用action时,如果发现不明原因使不能正确地通过 setter注入值情况下,第一步最好是先检查下各拦截器,确保它们 都已作用于该action。因为这些意外通常有时由拦截器设置不当形 成的,检查是否各个拦截器都已起作用,并看看它们作用的顺序, 因为有些情况下它们间会相互影响而产生错误。

现在我们已经有基于String类型的form. bean中取值的方法或者 是自动把request的attributes 注入到action的方法,那下一步就 是如何把值传入 domain object 或 value / transfer object的 属性中去。其实这很简单,你只需要实现ModelDriven 接口(即实 现getModel()方法)就可以了,确保ModelDrivenIntercepTor 拦截 器已作用于action。

除了会调用action中的setter外,model 首先检查是否有和 setter可以匹配当前的attribute名。如果在model中没有这个 attribute相应的setter,则会再在action上找相应的setter来设 值。

在BlogAction 的例子中我们可以看到如何很灵活地使用这些方 法,首先通过prepare() 方法根据Id获取相应的 Blog model object 或新建一个instance, 然后再根据把request中相应的属性 注入Blog instance中和action中。

以上的两个功能使得现在调用action那么简单 – 调用具体的业 务逻辑,和把数据设在HttpServletRequest供返回用。

public class BlogAction extends ActionSupportimplements ModelDriven, Preparable, ServletRequestAware ...{private int blogId;private Blog blog;private BlogService service = new BlogService();private HttpServletRequest request;public void setServletRequest(HttpServletRequest httpServletRequest) ...{this.request = httpServletRequest;}public void setId(int blogId) ...{this.blogId = blogId;}public void prepare() throws Exception ...{if( blogId==0 ) ...{blog = new Blog();} else ...{blog = service.findById(blogId);}}public Object getModel() ...{return blog;}public String save() ...{service.create(blog);return SUCCESS;}public String update() ...{service.update(blog);request.setAttribute("blog",blog);return SUCCESS;}public String remove() ...{service.delete(blogId);return SUCCESS;}public String execute() ...{request.setAttribute("blog",blog);return SUCCESS;}}

最后就是说说 list这个用例了。它同样需要访问 HttpServletRequest对象去返回数据给JSP,所以也需要实现 ServletRequestAware 接口。但是,因为它并不需要任何输入值, 所以就不需要实现其他的接口了。以下是它的具体实现:

public class ListBlogsAction extends ActionSupport implements ServletRequestAware ...{private BlogService service = new BlogService();private HttpServletRequest request;public void setServletRequest(HttpServletRequest httpServletRequest) ...{this.request = httpServletRequest;}public String execute() ...{request.setAttribute("bloglist",service.list());return SUCCESS;}}

这样就完成了我们该实现的action代码了。 在下 一篇文章中,当我们新的Struts2用户界面结合时,我们还会进一 步简化action的代码。

在我们调用action之前,我们必须通过XML配置文件去配置它们 。

在Struts中, 我们习惯用在WEB-INF 目录的”struts- config.xml”配置文件,在这里我们需要配置action form和action 属性。在Struts2中, 用的是在classpath中的”struts.xml”配置 文件, 它看起来好象会稍微复杂一些,因为它需要在配置action 的同时也配置其拦截器。

在Struts中配置 form-beans 节点很容易, 只需要一个唯一的 名字,还有就是继承ActionForm类的class作为type。

              ...

在我们的例子中,我们的配置 文件有3点不相同:

1. 重定向配置

在Struts的配置中,每个mapping 都需要提供调用action时所 需要对应的路径,Struts默认为”.do”, 例如paht是”/struts/add” 对应于URL”/struts/add.do”。同时也需要一个forward 属性来提 供给URL去转向,如”/struts/add.jsp”.

   ...           ...

而Struts2的需要更多的一些配置,如:

首先你会注意到的是,代替action-mappings 节点的是include 和package 节点。Struts2可以把配置细分到任意数目的配置文件 中,来实现配置可模块化管理。每个配置文件的结构其实都是一样 的,不同的只是文件名。

include 节点中,以文件名作为file 属性,可把所include的 文件内容包含到当前文件中。

package 节点把actions组成一组,其name 属性的值必须是唯 一的。

在 Struts action的配置中, paht属性需要指定完整的URL路 径。而在Struts2中,URL是通过package节点中的namespace属性, 还有在action 节点中的name 属性, 和action扩展(默认 是”.action”)共同起作用的。在上面的例子中,则URL 为”/struts2/add.action”时会调用action。

package节点除了可以分离命名空间外, package 节点中的 extends 属性,还提供了某种可复合的组成结构。通过继承另外一 个package节点,你就能继承那个节点的配置,包括其actions, results, intercepTors, exception,等值。在我们的例子 中,”struts2″ package节点继承了 “struts-default” package 节点(在”struts-default.xml” 文件里定义了该节点) ,注意这个 是主要的include文件,所以必须在所有配置之前的第一行中写出 。 这个功能有助于大大减少你重复性输入默认配置所浪费的时间 。

最后是result 节点, 它只是存放你这个action所需要转向的 URL. 在这里我们没有提及name 和 type 属性。如果你不想改变它 们的默认属性的话,你能忽略不写它们,让你的配置文件看起来更 清晰。从action返回的 “success” 的结果将组成这个JSP显示给用 户。

2. Action 配置

在Struts 中forward 节点指定了action处理后,结果将重定向 到哪个相应的页面。type属性指定了action的类,scope 属性保证 了form. beans只在request范围内。

   ...               type="com.fdar.articles.infoq.conversion.struts.ListBlogsA ction" >                 ...

Struts2 的 XML配置和上面提到的基本相同。唯一不同的就是 通过class属性为action节点提供了它所需要调用的类的完整路径

   ...                     class="com.fdar.articles.infoq.conversion.struts2.ListBlog sAction">       /struts2/list.jsp                 ...

如果是用其他的方法而不是用默认的execute 方法去调用 action(在BlogAction 类中大多数方法如此), 则需要在action节 点的 method 属性里加入方法名,下面就是个例子,这时候update 方法将会被调用。

   class="com.fdar.articles.infoq.conversion.struts2.BlogActi on" >     ...

default-intercepTor-ref 和 intercepTor-ref 节点有几点不 同。在第一篇文章中,我们看到在action被调用之前必须通过一系 列的拦截器,而这两个节点就是用来配置拦截器组的。default- intercepTor-ref 节点为该package提供了默认的拦截器组。当在 action节点中提供 intercepTor-ref节点时 ,它就会覆盖默认的 拦截器(intercepTor-ref 节点能够和单独一个拦截器相关联,或 者跟一个拦截器组相关联),在action节点中可以存在多个 intercepTor-ref节点,处理拦截器组的顺序会和该节点列出的顺 序一致。

3. 再重定向配置

当我们提交表格的时候,我们需要重定向到更新后的结果页面 。这个通常称为 “post-redirect pattern” 或, 最近出现的, “flash scope.”

由于这是一个form, 所以在Struts中我们需要为Struts指定一 个ActionForm。需要在name属性中提供form的名称,同样地,我们 也需要在forward 节点中举加入redirect属性为true。

   ...               type="com.fdar.articles.infoq.conversion.struts.SaveBlogEn tryAction"         name="blogForm" scope="request">                  ...

Struts2 在result 节点里提供了type 属性, 默认情况下 是”dispatch”, 如果需要重定向,则需要设为 “redirect”。

   ...               class="com.fdar.articles.infoq.conversion.struts2.BlogActi on" >       list.action                  ...

总结

我们并不可能在这篇文章中覆盖所有的内容,如果你需要更好 的了解整个框架,还有其他的实现方式和选项,这里有几点可以供 你参考:

配置拦截器和拦截器组 – 以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也是很容易的。

最大的成功在于最大的付出。

将Struts应用迁移到Struts2(二)

相关文章:

你感兴趣的文章:

标签云: