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/j2ee http://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的名称。