Struts2 在经历了两年漫长的开发后,终于在 2007 年 2 月底正式发布,Struts 1.3.8 是 Struts 1 的最终版本,Struts 1 从此不再升级。Struts 2 和 Struts 1 差别非常大,几乎是两个完全不同的东西,所以不要指望 Struts 1 在不经过修改的情况下就可以跑在 Struts 2 环境下。Struts 2 与其说是 Struts 的升级版本,莫如说是 WebWork 的升级版本——我们可以通过 WebWork 开发团队当年宣称并入 Struts 时所提到的一句话了解到这一点:“WebWork 拥有前卫的技术,Struts 拥有强大的用户群,两者的合并或许将是一个完美的组合……”。
Struts 2 概述
众所周知,Struts 2 是 Struts 和 WebWork 的混血儿,而在 Webwork 2.2 之前的 Webwork 版本,其自身有一套 IoC 实现,在 Spring 框架如火如荼发展的背景下,Webwork 决定放弃控制反转功能的开发,转由使用 Spring 的实现。有越来越多的开源组件(如 iBATIS 等)都放弃与 Spring 重叠功能的开发,因此,Struts 2 推荐大家通过 Spring 实现 IoC 功能。
Struts 2 拥有众多的新特性,下面我们对此进行概括性的描述:
? ·Action 类更加灵活
Struts 2 的 Action 类可以实现一个 Action 接口,也可实现其他接口,因此很容易添加定制性的服务。Struts 2 提供一个 ActionSupport,它是实现了 Struts 常用接口的方便类。Action 接口不是必需的,任何具有 execute 签名的 POJO 都可以用作 Struts 2 的 Action 对象。
? ·每一请求对应一个 Action 实例
Struts 1 的 Action 以单例模式运行,所有的请求对应同一个 Action 实例。因此用户必须保证 Action 本身是线程安全的。Struts 2 的 Action 对象为每一个请求产生一个实例,因此没有线程安全问题,它相当于 Spring MVC 的 ThrowawayController。
? ·不依赖于 Servlet 容器
Struts 1 Action 依赖于 Servlet 容器,但 Struts 2 Action 不依赖于 Servlet 容器,允许 Action 脱离容器单独测试。如果需要,Struts2 的 Action 仍然可以访问到 request 和 response 实例。
? ·具有很强的可测试性
由于 Struts 1 依赖于 Servlet 容器,所以很难脱离容器进行测试,虽然可以通过 StrutsTestCase 使用模拟对象进行测试,但操作依旧比较麻烦。Struts 2 的 Action 不依赖于 Servlet 容器,用户可以简单地通过初始化、设置属性、调用方法进行测试,同时“依赖注入”的支持也使测试更加容易。
? ·捕获输入更加灵活
Struts 1 使用 ActionForm 对象捕获输入,所有的 ActionForm 必须继承一个基类。Struts 2 直接使用 Action 属性绑定请求参数,消除了对第二个输入对象的需求。这种模型驱动的特性简化了页面标签对 POJO 输入对象的引用。
? ·表达式语言更强大
Struts1 整合了 JSTL,因此可以使用 JSTL EL。这种 EL 有基本对象图遍历的功能,但是对集合和索引属性的支持很弱。Struts2 依旧可以使用 JSTL,但是也支持一个更强大和灵活的对象属性引用语言——“Object Graph Notation Language(OGNL)”。
? ·实现值和页面的灵活绑定
Struts 1 使用标准 JSP 机制把对象绑定到页面中进行访问。Struts 2 使用“值堆栈”技术,使标签能够访问值而不需要把页面和对象绑定起来。“值堆栈”策略允许通过一系列名称相同但类型不同的属性重用页面。
? ·类型转换更加方便
Struts 1 的 ActionForm 属性通常都是 String 类型,Struts1 使用 Commons-Beanutils 进行类型转换。每个类对应一个转换器,对每一个实例来说是不可配置的。Struts2 使用 OGNL 进行类型转换。提供基本和常用对象的转换器。
? ·数据校验更加方便
Struts 1 支持在 ActionForm 的 validate() 方法中进行校验,或者通过 Commons Validator 的扩展进行校验。同一个类可以有不同的校验内容,但不能校验子对象。Struts2 支持通过 validate() 方法和 XWork 校验框架来进行校验。
? ·Action 的生命周期更加灵活
Struts1 支持每一个模块有单独的生命周期,但是模块中的所有 Action 必须共享相同的生命周期。Struts 2 支持通过拦截器堆栈为每一个 Action 创建不同的生命周期。堆栈能够根据需要和不同的 Action 一起使用。
集成 Struts 2 的步骤
和 Spring 集成的目标是什么呢?无非是希望 Struts 2 的 Action 定义直接使用 Spring IoC 的功能,将业务层的 Bean 注入到 Struts 的 Action 中。用户当然可以简单地在 Struts 中通过 WebApplicationContextUtils.getWebApplicationContext(ServletContext sc)获取 Spring 容器的 Bean,手工将其装配到 Action 中,但这种“集成”属于低层次的集成,我们要达到的集成目标是:在 Spring 配置文件中配置 Struts Action,并在 Struts 配置文件中引用。
Spring 2.1 目前仅提供了对 Struts 1 集成的实现方案,尚未对 Struts 2 提供集成支持。不过 Spring 或许可以永远卸下这副担子,因为 Struts 2 已经提供了集成到 Spring 中的插件,主动投怀送抱了。历史总是让人玩味,在开发初期,Spring 不厌其烦,殚精竭虑地为各种著名的框架提供集成的实现方案,等到现在 Spring 强大之后,各种框架反过来主动提供了集成到 Spring 的方案。
要将 Struts 2 集成到 Spring 中,需要完成以下三个基本步骤:
1.将以下 Struts 类包添加到类路径下
struts2-core-2.0.6.jar
xwork-2.0.1.jar
ognl-2.6.11.jar
struts2-spring-plugin-2.0.6.jar
freemarker-2.3.8.jar
其中 struts2-spring-plugin-2.0.6.jar 就是 Struts 2 提供的用于集成到 Spring 中的类包。该类包拥有一个 struts-plugin.xml 配置文件,它定义了一个名为 spring 的 StrutsSpring ObjectFactory 的 Bean,以便将 Action 类的管理工作委托给 Spring 容器进行处理。也就是说,通过 StrutsSpringObjectFactory 的配置,Struts 配置文件就可以直接引用 Spring 容器中的 Bean 了。
2.编写 Struts 配置文件 struts.xml
<?xml version=”1.0″ encoding=”UTF-8″ ?> <!DOCTYPE struts PUBLIC “-//Apache Software Foundation//DTD Struts Configuration 2.0//EN” “http://struts.apache.org/dtds/struts-2.0.dtd”> <struts> ① 通过这个配置指定使用 struts-plugin.xml 中的 StrutsSpringObjectFactory 作为 创建 Action 的工厂类 <constant name= “struts.objectFactory” value=”spring” /> … </struts>
将其命名为 struts.xml 文件,并保存在 < 项目根目录 >/src 下。
3.配置 web.xml
<?xml version=”1.0″ encoding=”UTF-8″?> <web-app id=”WebApp_9″ version=”2.4″ xmlns=”http://java.sun.com/xml/ns/j2…; xmlns:xsi=”http://www.w3.org/2001/XMLSch…; xsi:schemaLocation=”http://java.sun.com/xml/ns/j2eehttp://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd”> <context-param> ①指定 Spring 配置文件 <param-name>contextConfigLocation</param-name> <param-value>classpath:baobaotao-*.xml,</param-value> </context-param> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> ② 配置 Struts2 的过滤器 <filter> <filter-name>struts2</filter-name> <filter-class> org.apache.struts2.dispatcher.FilterDispatcher </filter-class> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
我们在类路径下提供了几个 Spring 配置文件:其中 baobaotao-service.xml 用于配置业务层 Bean,而 baobaotao-web.xml 用于配置 Struts 的 Action。在①处,通过 contextConfigLocation 指定 Spring 配置文件,并配置 ContextLoaderListener 启动 Spring 容器。
我们知道 Struts 1 使用基于 Servlet 的 ActionServlet 启动 Struts 框架,Struts 2 使用基于 Filer 的 FilterDispatcher 完成相同的工作。FilterDispatcher 会自动使用类路径下的 struts.xml 初始化 Struts 框架。
通过以上三个基本步骤,我们就将 Struts 2 集成到 Spring 中了,但这只是一个基础的空架子,Spring 究竟如何配置 Struts 的 Action,Struts 又如何引用 Spring 中配置好的 Action,这些具体问题有待于通过实例进行具体的说明。
注册用户实例
下面我们使用一个小例子介绍 Struts 集成到 Spring 中的具体应用。
创建 Action 对象
首先,我们需要编写一个能够处理表单提交的 Action 类,Struts 2 的 Action 类是表单对象和控制器的混合体,Action 类本身的属性就可以和表单数据进行绑定。不过为了保证业务层接口和 Web 层无关,一般情况下,我们还是需要编写一个独立的表单对象,只要将表单对象作为 Action 的属性,就可以通过级联属性的方式绑定页面的表单数据了。来看一下负责处理表单提交的 UserRegisterAction 的代码:
代码清单 1 UserRegisterAction:用户注册 Action package com.baobaotao.web.user; import com.baobaotao.domain.User; import com.baobaotao.service.BbtForum; import com.opensymphony.xwork2.ActionSupport; public class UserRegisterAction extends ActionSupport {private BbtForum bbtForum; ①业务层的 Bean private User user; ②充当表单对象的 POJO 属性对象 @Override public String execute() throws Exception {③Action 处理请求的方法 if (user == null) {③- 1 当 user 为 null 时,导向到输入表单页面 user = new User(); return INPUT; } else {③- 2 当 user 不为 null 时,处理表单提交 System.out.println(“user:”+user); bbtForum.registerUser(user); return SUCCESS; } } public void setBbtForum(BbtForum bbtForum) {this.bbtForum = bbtForum;} public void setUser(User user) {this.user = user;} public User getUser() { return user;} }
在 UserRegisterAction 中,我们定义了一个业务层 BbtForum 对象和用于绑定表单数据的 User 对象。在后续章节中,读者将看到我们如何在 Spring 中配置 UserRegisterAction 以及将 Spring 容器中的 BbtForum Bean 注入到 UserRegisterAction 中。用户也可以直接在 UserRegisterAction 中声明和表单组件相对应的属性(如 userName、password 等),不过由于我们需要调用业务层对象的方法操作表单数据,所以最好还是独立到一个 POJO 中,这便是 User 对象。如果请求参数仅是一些控制性数据,完全可以直接通过 Action 标量属性进行绑定。User 对象是一个 POJO,其代码如下所示:
package com.baobaotao.domain; public class User {private String userName; private String password; private String email; // 省缺 get/setter}
在编写好 Struts 2 的 Action 后,我们下一步要做的是在 Spring 容器中配置这个 Action,以便充分享用 Spring 容器的各项功能。
配置 Action
首先我们在 baobaotao-web.xml 的 Spring 配置文件中配置 UserRegisterAction Bean:
①作用域必须设置为 prototype <bean id=”registerUserAction” class=”com.baobaotao.web.user.UserRegisterAction” scope=”prototype”> <property name=”bbtForum” ref=”bbtForum” /> ②注册 Spring 容器中的 Bean </bean>
特别需要注意的是必须将 UserRegisterAction Bean 的作用域定义为 prototype,因为 UserRegisterAction 是有状态的,必须每一个请求对应一个新的 UserRegisterAction 实例。bbtForum 是在 baobaotao-service.xml 中配置的业务层 Bean,在此将其注入到 Action 中。
既然我们已经在 Spring 中配置好了 Struts 2 的 Action,接下来就碰到了最核心的问题:如何在 Struts 配置文件中引用这个 Bean 呢?也许看一眼 struts.xml 配置文件就水落石出了:
代码清单 2 struts.xml:配置 Action <?xml version=”1.0″ encoding=”UTF-8″ ?> <!DOCTYPE struts PUBLIC “-//Apache Software Foundation//DTD Struts Configuration 2.0//EN” “http://struts.apache.org/dtds/struts-2.0.dtd”> <struts> <constant name=”struts.objectFactory” value=”spring” /> <package name=”user” namespace=”/user” extends=”struts-default”> ① 扩展默认配置 ②引用 Spring 容器中的 Bean <action name=”registerUserAction” class=”registerUserAction”> <result name =”input”>/ WEB-INF /jsp/ registerUser.jsp</result> ③表单输入页面 <result name=” success”>/WEB-INF/jsp/success.jsp</result> ④操作成功转向的页面 </action> </package> </struts>
①处扩展了 struts-default,Struts 2 在 struts2-core-2.0.6.jar 中定义了一个 struts-default.xml 配置文件,定义了 Struts 一些默认的基础设施,在这里我们通过扩展这个配置文件提供额外的配置。②处的 class=”registerUserAction” 也许是最令人好奇的,在正常情况下,Struts 通过 class 属性指定 Action 的实现类,形如:
<action name=”registerUserAction” class=”com.baobaotao.web.user.UserRegisterAction”>
但此时 class 属性却不是一个类名,相反它是一个类似于 Bean 名的字符串,秘密恰恰隐藏在这里:事实上 class 属性的值正是指向 Spring 容器中的 Bean 名称,在后台 StrutsSpringObjectFactory 通过类似于以下的代码获得真实的 Action 实例:
WebApplicationContext ctx= ApplicationContextUtils.etWebApplicationContext(ServletContext sc); UserRegisterAction userRegisterAction = (UserRegisterAction) ctx.getBean (“registerUserAction”);
通过这种集成方式,Struts 2 和 Spring 就浑然一体、水乳交融了。在编写并配置好 Action 后,接下来,我们来看一下用户注册信息输入的 JSP 表单页面。
表单页面和成功页面
用于填写用户注册信息的表单页面需要使用 Struts 2 的标签,这样就可以和服务端的 Action 属性进行绑定。来看一下 registerUser.jsp 页面的代码:
代码清单 3 registerUser.jsp:用户注册表单页面 <%@ page contentType=”text/html; charset=UTF-8″ %> <%@ taglib prefix=”s” uri=”/struts-tags” %> ①声明 Struts 标签 <html> <head><title> 宝宝淘注册页面 </title></head> <body> ② 引用 struts.xml 中定义的 Action <s:form> <s:textfield key=” user.userName” label=” 用户名 ”/> <s:password key=”user.password” label=” 密 码 ”/> <s:textfield key=”user.email” label=”Email”/> <s:submit/> </s:form> </body> </html>
首先,在①处声明 Struts 标签,②处的代码构造了一个表单。读者是否发现 Struts 2 的表单标签和 Spring MVC 的表单标签很相像呢?表单标签 <s:form> 也仅需要一个简单的标签声明就可以了,无须通过 action 属性指定表单提交的地址,表单组件标签也通过级联属性的方式绑定表单对象。唯一不太相同的是,表单组件标签有一个 label 属性,生成表单时,它将作为组件前头的一个文本标签,这种将 HTML 组件标签和组件本身捆绑的方式很有趣,不过好像绑定得过于紧密了,不利于 HTML 页面设计。
那如何访问到这个表单页面呢?答案是和 Spring MVC 的表单控制器一样,通过直接请求 Action,Action 发现 User 是 null 时自动导向到表单输入页面中,这样 <s:form> 标签就可以确定表单提交的地址了。可见事件发展到最后往往是殊途同归,或说英雄所见略同。
当表单提供处理完成后,我们转向一个 success.jsp 页面,这个页面显示注册用户的欢迎信息:
<%@ page contentType=”text/html; charset=UTF-8″ %> <%@ taglib prefix=”s” uri=”/struts-tags” %> <html> <head> <title>Welcome</title> </head> <body> ① 显示用户名属性 <s:property value=”user.userName”/> Welcome to baobaotao </body> </html>
在①处,我们通过 <s:property> 标签显示表单对象的 userName 属性,由于级联属性的根是相对于 Action 而言的,所以必须设置为“user.UserName”。
将 registerUser.jsp、success.jsp 放置到 WEB-INF/jsp 目录下,这样就满足代码清单 2 的③和④中的配置了。
测试用户注册功能
在浏览器地址栏中输入 http://localhost/baobaotao/us…,由于第一次调用时,UserRegisterAction 中的 user 属性为 null,请求将被导向到 registerUser.jsp 页面中,如图 1 所示:
通过这个表单页面,对照代码清 3 ②处的编写方式,我们就可以清楚地知道 Struts 2 表单组件标签的 label 属性究竟对应 HTML 页面中的哪个内容了。
在点击 Submit 提交表单后,RegisterUserAction 负责处理表单提交,并调用 BbtForum# registerUser(User user) 将 User 对象保存到数据库中,然后导向到 success.jsp 页面上。
** 小结
**
和 Spring 采用主动提供插件的方式集成 Struts 1.x,时至今日 Struts 2.0 却反过来提供了一个集成到 Spring 中的插件包。应该说,和集成 Struts 1.x 相比,集成 Struts 2.0 更加容易了,你只需要使用以下两个步骤就可以了:
1) 将 struts2-spring-plugin-2.0.6.jar 添加到类路径中;
2)在 struts.xml 配置文件中添加以下配置:
<constant name=”struts.objectFactory” value=”spring” />
在 Struts 的 <action> 配置项中,原来的 class 属性指定为一个 Action 实现类,集成 Spring 后,class 属性直接指定 Spring 容器中 Bean 的名称。