大多数人都会熟悉 Struts, 无论是从项目实战中获得的经验还是从书中了解到的知识。在这一系列文章里,我们将通过一个由 Struts 迁移到 Struts 2 的简单应用例子向大家展现 Struts 2 的所有特征。
在我们开始介绍这个例子之前,你需要去了解一点 Struts 2 的背景知识。文章的第一部分将介绍 Struts 2 与 Struts 的核心架构的不同点,以助于更好地把所有概念联系起来。第二部分将深入探讨两者在 actions 上的差别、action 相关的框架特征和 action 的配置。在文章最后一部分将会讲述用户界面。我们会讲到其架构、UI 构件、主题和标签,还有如何为我们的应用加上新的外观。
我们并不打算谈及迁移过程的所有细节方面,我们只是从普通的出发点开始介绍 Struts 2 的概念和现在可用的所有新特征。但拥有这些知识后,无论以后迁移到何等规模的应用到 Struts 2 中你都可以易如反掌。
导言 / 历史
Struts 的第一个版本是在 2001 年 5 月份发布的。它的最初设想是通过结合 JSP 和 Servlet,使 Web 应用的视图和业务 / 应用逻辑得以清晰地分离开来。在 Struts 之前,最常见的做法是在 JSP 中加入业务和应用逻辑,或者在 Servlet 中通过println()来生成视图。
自从第一版发布以来,Struts 实际上已成为业界公认的 Web 应用标准。它的炙手可热也为自己带来了改进和变更,所以不但要跟上对 Web 应用框架不断变化的需求,而且要与日渐增多竞争激烈的众多框架的特性相融合。
到最后,产生了几个下一代 Struts 的解决方案。其中两个最受瞩目的方案是 Shale 和 Struts Ti。Shale 是一个基于构件的框架,并在最近成为 Apache 的顶级项目。而 Struts Ti 则是在 Struts 的成功经验基础上继续坚持对前端控制器(Front Controller)和 MVC(model-view-controller)模式进行改进。
WebWork 项目是在 2002 年 3 月发布的,它对 Struts 式框架进行了革命性改进,引进了不少新的思想,概念和功能,但和原 Struts 代码并不兼容。WebWork 是一个成熟的框架,经过了好几次重大的改进与发布。
在 2005 年 12 月,WebWork 与 Struts Ti 宣布合并。与此同时,Struts Ti 改名为 Struts Action Framework 2.0,成为 Struts 真正的继承者。
最后要注意的是,并不是说 Struts 或 WebWork 项目已经停止开发了。由于人们对这两个项目的兴趣仍然很高,而且也有很多开发者仍然愿意使用它们,因此这两个项目还在继续开发中,继续修复 Bug,改进功能和继续添加新功能。
请求运作过程
在我们开始详细探讨如何把应用由 Struts 迁移到 Struts 2 之前,让我们通过体验整个请求流程,看看新架构是如何运作的。
在我们体验了整个请求的生命周期后,你应当注意到很重要的一点——Struts 2 仍是以前端控制器框架为主体的。所有的概念还都是你以前所熟悉的。
这意味着:
- Actions 仍然是通过 URL 触发的
- 数据仍然是通过 URL 请求参数和 Form 参数传送到服务端的
- 所有 Servlet 对象(如 request、response 和 session 等)仍在 Action 可用
以下是请求处理过程的高层概览:
整个请求的处理过程可以分为 6 步:
- 由框架产生一个请求并进行处理 - 框架根据请求匹配相应的配置,得到使用哪些拦截器,Action 类和返回结果的信息。
- 请求通过一系列的拦截器 - 拦截器和拦截器组可以按照不同级别进行组合配置来处理请求。它们为请求提供各种预处理和切面处理的应用功能。这和 Struts 的使用 Jakarta Commons Chain 构件的 RequestProcessor 类很相似。
- 调用 Action - 产生一个新的 Action 对象实例,并提供请求所调用的处理逻辑的方法。我们在第二部文章中将对这步骤进行进一步讨论。Struts 2 可以在配置 Action 时为请求分配其指定的方法。
- 调用相应的 Result - 通过匹配处理 Action 方法之后的返回值,获取相应 Result 类,生成并调用它的实例。处理 Result 可能产生的结果之一就是对 UI 模板(但并非只有一个)进行渲染,来产生 HTML。如果是这种情况的话,模板中的 Struts 2 tags 可以直接从 Action 中获取要被要被渲染的值。
- 请求再次经过一系列拦截器处理后返回 - Request 以和进入时相反的方向通过拦截器组,当然,你可以在这个过程中进行回收整理或者额外的处理工作。
- 响应被返回给用户 - 最后一步是将控制权交还给 Servlet 引擎。最常见的结果是把渲染后的 HTML 返回给用户,但返回的也可能是指定的 HTTP 头或者进行 HTTP 重定向。
你应该已经注意到 Struts 2 和 Struts 的差别了。最明显的就是 Struts 2 是一个 pull-MVC 架构。这是什么意思呢?从开发者角度看,就是说需要显示给用户的数据可以直接从 Action 中获取,而不像 Struts 那样必须把相应的 Bean 存到 Page、Request 或者 Session 中才能获取。
配置框架
首先最重要的是,通过配置 web.xml 文件让框架能在 Servlet 容器里运行。
下面这个就是大家都熟悉的 Struts 在 web.xml 里的配置方法:
<servlet></servlet><br></br><servlet-name></servlet-name>action<br></br><servlet-class></servlet-class>org.apache.struts.action.ActionServlet<br></br><init-param></init-param><br></br><param-name></param-name>config<br></br><param-value></param-value>/WEB-INF/struts-config.xml<p><load-on-startup></load-on-startup>2</p><br></br><servlet-name></servlet-name>action<br></br><url-pattern></url-pattern>*.do<br></br><servlet-mapping></servlet-mapping>
在 Struts 2 中,配置有少许改变,最明显的是分发器(dispatcher)已由 Servlet 转为 Servlet Filter, 其配置和 Servlet 一样简单,如下:
<filter></filter><br></br><filter-name></filter-name>webwork<br></br><filter-class></filter-class><br></br> org.apache.struts.action2.dispatcher.FilterDispatcher<br></br><filter-name></filter-name>webwork<br></br><url-pattern></url-pattern>/*<br></br>
和 Servlet 配置一样,Filter 配置中定义了 Filter 的名称(作为引用)和类名。Filter Mapping 通过 URI 和名称匹配来调用相应的 Filter。默认情况下,扩展名为“.action”,这是在 default.properties 文件(在 Struts 2 JAR 文件里)的“struts.action.extension”属性定义的。
工具箱:“default.properties”是默认配置选项定义文件。你可以通过在 classpath 中包含一个叫“struts.properties”的文件,设置不同的属性值,来覆盖默认配置的值,实现自己的配置。
对于 Struts 来说, Servlet 配置提供了一个用于定义文件名的 init-param tag 来配置 Struts,而 Struts 2 没有这样的配置参数,取而代之的是在 classpath 下的默认配置文件“struts.xml”。
工具箱 / 提示:因为 Struts Actions(扩展名“.do”)和 Struts 2 Actions(扩展名“.action”)两者的扩展名命名空间不一样,所以 Struts 和 Struts 2 可以在同一个 Web 应用系统中无碍地共存。所以这就为迁移提供了很好的条件,加入适当的配置,新功能的开发都用 Struts 2。保持原有的遗留功能,如果时间和资源允许的情况下再逐步迁移。另一种方法是,只把 Struts 2 的扩展名改为“.do”,这样就可使得以前的 JSP 页面可重用。
解剖 Actions
在上面介绍的请求运作流程中,我们从高层次上谈及了一些 Struts 和 Struts 2 的不同点。现在我们将较深入地探讨这两个框架中 Action 结构的具体差别。
让我们来回顾一下 Struts 的 Action 的主要结构。Struts Action 的主要形式如下:
public class MyAction extends Action {<br></br> public ActionForward execute(ActionMapping mapping,<br></br> ActionForm form,<br></br> HttpServletRequest request,<br></br> HttpServletResponse response)<br></br> throws Exception {<br></br> // do the work<br></br> return (mapping.findForward("success"));<br></br> }<br></br>}
当你实现一个 Struts Action 时, 需要注意以下问题:
- 所有的 Action 都继承于 Action 基类。
- 所有的 Action 都必须是线程安全的,因为只产生一个 Action 实例。
- 因为所有的 Action 都必须是线程安全的,所有在 Action 处理过程中所需要的对象都必须以方法参数的形式传入。
- 处理 Action 所调用的方法必须命名为“execute”(在 Struts 中的 DispatchAction 类可以调用同一个 Action 的其它方法,但实际上在框架中的入口点仍然是“execute”方法)。
- ActionForward 结果是通过 ActionMapping 类中的方法来产生的,通常的做法是通过调用“findForward”方法。
相比较之下,Struts 2 的 Action 提供了更简单的实现方式。下面就是个例子:
public class MyAction {<br></br> public String execute() throws Exception {<br></br> // do the work<br></br> return "success";<br></br> }<br></br>}
首先你会注意到的是,Struts 2 中的 Action 不再继承于任何类或需要实现任何接口。实际上,它还远不只这些。按照惯例,只有“execute”方法能调用 Action, 但在 Struts 2 中并非必要,任何声明为 public String methodName() 方法都能通过配置来调用 Action。
另外,你会注意到返回的对象不是 ActionForward,而是 String。如果你不喜欢以字符串的形式出现在你的代码中,有个 Helper 接口 Action 可以以常量方式提供常见结果,如“success”、“none”、“error”、“input”和“login”。
最后,和 Struts 最大的革命性的不同是,处理 Action 过程中调用的方法(“execute”方法)是不带参数的。那你如何获取你所需要的对象呢?答案是使用“反转控制(Inversion of Control)”,也叫“依赖注入(Dependency Injection)”的模式(想更多地了解这方面信息请看 Martin Fowler 的文章 http://www.martinfowler.com/articles/injection.html )。Spring 框架使得这个模式流行起来,然而 Struts 2 的前身(WebWork)也在同时应用上了这个模式。
为了更好地了解反转控制,让我们来看看一个例子,如何在 Action 处理过程中可以访问到当前请求 HttpServerRequest 对象。
在我们的例子中,我们使用的依赖注入机制是接口注入。就如其名称一样,接口注入需要的是已经被实现了的接口。这个接口包含了相应属性的 setter,为 Action 提供值。例子中我们使用了 ServletRequestAware 接口,如下:
public interface ServletRequestAware {<br></br> public void setServletRequest(HttpServletRequest request);<br></br>}
当我们继承这个接口后,我们原本简单的 Action 看起来有点复杂了,但是这时我们可以获取 HttpServerRequest 对象来使用了。
public class MyAction implements ServletRequestAware {<br></br> private HttpServletRequest request;<br></br> public void setServletRequest(HttpServletRequest request) {<br></br> this.request = request;<br></br> }<br></br> public String execute() throws Exception {<br></br> // do the work using the request<br></br> return Action.SUCCESS;<br></br> }<br></br>}
看起来现在这些属性是类级别的,并不是线程安全的,会出现问题。其实在 Struts 2 里并没有问题,因为每个请求过来的时候都会产生一个新的 Action 对象实例,它并没有和其他请求共享一个对象,所以不需要考虑线程安全问题。
现在我们还有最后一步,就是为这个 Action 关联上ServletConfigInterceptor拦截器。这个拦截器提供了一系列功能去获取 HttpServletRequest,并可以把它注入到实现了ServletRequestAware接口 Action 中。这时你并不需要担心如何配置这些,我们将在下一篇文章中有具体讲述。最重要的是让我们明白到是拦截器和接口共同合作下为 Action 提供了反转控制功能的。
这个设计的好处是能让 Action 和框架完全解耦。Action 仅仅是一个与框架无关的简单 POJO。这给单元测试带来极大的好处,Struts 2 Action 的单元测试远比 Struts Action 使用 StrutsTestCase 或 MockStrutsTestCase 的单元测试简单。
总结 / 综述
到现在为止,你应该对 Struts2 的基础有所了解了——包括高层的框架概念和基础的请求流程。你也应该能自己动手在 Servlet 容器里配置 Struts 2,并理解 Struts 和 Struts 2 两者之间在 Action 方面的差别了。
在下篇文章中,我们将会介绍一个详细的迁移的例子,同时我们也在这章学到的知识基础上,演示如何由 Struts 向 Struts 2 的 Action 迁移。在最后讲述如何在应用中使用 JSTL、JSP 和 Struts2。我们会进一步探讨 Struts 和 Struts 2 在 Action 上的区别,Struts 2 的配置和其他框架元素,和谈到更多的 Action 相关的框架特征。
关于作者
Ian Roughley 是一位技术演讲人、作家及独立咨询顾问,住在马萨诸塞州的波士顿。他具有十多年提供架构设计、开发、过程改进以及指导等方面服务的经验,客户范围小至创业公司,大到财富 500 强前 10 名的公司。他专注于具有实效性且以结果为目标的方法,是开源及以敏捷开发为基础的过程和质量改进的支持者。
关于译者
陈俊是 SpringSide 开源项目的主力成员,中科院软件工程硕士,就职于 Accenture。长期从事 J2EE 企业级应用开发, 略有小成。热衷于软件体系结构,设计模式,软件过程改进及敏捷开发研究,以成为出色的架构师为目标。爱尝试不同的开源技术,一直投身 SpringSide 开发,为国内开源出一分绵力。
评论