Transcript

第 7 章 Struts2 的高级应用

• 本章将讲述一些 Struts2 的高级应用,有一些是 struts2 提供的特殊功能,大部分是由 struts2 提供的基本功能组合而来。这些方法与设计模式的思路类型,“面对同样的问题采用经典的、可靠的、易用的方法来解决”。还有一些问题是 Web开发中经常遇到的,比如类型转换,此类问题工作量巨大。 Struts2 也提供了完美的解决方案,使开发者从这些重复劳动中解放出来。

7.1 类型转换( Type Conversion )

• 在所有的基于 Web 的 Java 开发框架中, Struts2 拥有最优秀的类型转换能力。通常情况下,要利用这种能力,只需要把 HTML 输入项 ( 表单元素和其他 GET/POST 的参数 ) 命名为合法的OGNL 表达式。

7.1.1 为什么需要类型转换

• 在 Web 世界中输入输出是没有数据类型的概念的,任何数据都被当作字符串或字符串数组来传递。如果需要转化为其他类型如 int 进行计算就要使用 Java 的转换函数,在本书关于 EL 章节中已经介绍过,EL 提供了某些转换功能,如 String 对基本类型的数据的转化。但这样是还是不够的,当需要将一个字符串转换成为一个更为复杂的对象时,类型转换能发挥强大的作用 . 例如,如果提示用户使用字符串格式 ("3 , 22") 输入一个坐标,需要让 Struts2完成 String 到 Point 和 Point 到 String 的转换,Struts2 正是提供这样的功能。下面介绍如何配置和使用 Struts2 提供的类型转化。

7.1.2 定义类型转换器

• 类型转化器都需要实现 ognl.TypeConverter 类,而 Struts2 提供了一个很好的工具类 org.apache.struts2.util.StrutsTypeConverter 。该类可以让很方便的编写处理对象和字符串相互转换的类型转换器,如代码所示。

7.1.3 内建的 (Built in) 类型转换支持• Struts2 可以自动完成大多数常用的类型转换。这也是为什么在 st

ruts2 中字符串对基本类型如 int 的转化是自动完成的,无需任何配置和代码。已支持的与字符串之间转换类型包括:

• Struts2 可以自动完成大多数常用的类型转换。这也是为什么在 struts2 中字符串对基本类型如 int 的转化是自动完成的,无需任何配置和代码。已支持的与字符串之间转换类型包括:

• String• boolean/Boolean• char/Character• int/Integer 、 float/Float 、 long/Long 、 double/Double 。• dates :使用当前 request指定的 Locale信息对应的 SHORT 格

式。• arrays :假定每一个字符串都能够转换成对应的数组元素。• collections :如果不能确定对象类型, 将假定集合元素类型为 St

ring , 并创建一个新的 ArrayList 。

7.1.4 参数名称的关系• 利用 Struts2 的类型转换最好的方式是直接装配对象 ( 理想情况下应当直接使用业务对象 (domain objects) ,而不是使用基本类型或字符串类型的表单参数值作为中间值,然后在 Action 的 execute() 方法中把这些中间值组装成完整的对象下面是一些提示:

• 使用组合的 (complex)OGNL 表达式。 Struts2 能自动创建实际对象。• 使用 JavaBeans 。 Struts2 只能创建遵守 JavaBean规范的对象,这需要

对象提供一个无参构造函数,并包含适当的 getter 和 setter 方法。• 记住 person.name 将调用 getPerson().setName() ,但如果希望 Struts2创建 Person 对象,那么必须包含一个 setPerson() 方法。

• 对于 list 和map 对象,使用索引符号,如 people[0].name or friends['patrick'].name 。通常这些 HTML 表单元素是在一个循环中绘制出来的, 因此可以在 JSP Tags 中使用 iterator 标签的状态属性 (status attribute) 或在 FreeMarker Tags 中使用 ${foo_index} 来指定这一属性。

• 对于多选的列表, 显然不能为每个单独的选项使用对应的属性符号来命名( 由于 ) 。替代的方法是,使用简单的名称 people.name 来命名表单元素, Struts2知道需要为每一个选中的选项创建一个新的 Person 对象并设定它的名字。

7.1.5 空值属性处理

• Struts2 还有一些非常有用的类型转换特性。对空值 (Null)属性的处理可以在发现空值引用时自动创建对象。对 Collection 和 Map 的支持提供了针对 Java集合的智能空值处理和类型转换。类型转换错误处理提供了一种简单的方法可以把输入校验问题和输入类型转换问题区别开。

• 通过把 action context 中的键值 CREATE_NULL_OBJECTS 设置为 true 支持空值处理。 这样,出现 NullPointerException异常的 OGNL 表达式将被自动临时中断,然后系统将通过创建所需对象的方法来自动尝试解决 null引用。

7.1.6 Collection 和 Map 支持

• Struts2 支持多种方法来判断集合中的对象类型。这是通过一个 ObjectTypeDeterminer 完成的。 Struts2 提供了缺省实现对这个接口提供了一个缺省实现的类 DefaultObjectTypeDeterminer 。

• ObjectTypeDeterminer检查 Class-conversion.properties文件中包含的用于表示 Map 和 Collection 中包含的对象类型的相关内容。对于 Collection(如 List )使用格式 Element_xxx来指定其中的元素类型,这里 xxx是 action 或其他对象中的集合属性名称。对于 Map ,需要按照格式 Key_xxx和 Element_xxx分别指定 key 和 value 的类型。如上例中如果有个包含 point 的 list属性,那么 conversion文件中应该配置为:

7.1.7 类型转换错误处理

• 在类型转换发生错误时,有时希望报告这些错误,而有时不希望报告。例如,报告输入的“ abc”不能转换成数字可能很重要。另一方面,报告一个空字符串(“” ) 不能装换成数字可能不重要。除非是在一个 Web环境下,难以区分用户没有输入还是输入了一个空白值。

• 缺省情况下,所有的转换错误使用通用的 i18n信息 struts.default.invalid.fieldvalue ,可以在全局 il8n资源包中替换它(缺省文本是 "Invalid field value for field xxx" ,这里 xxx是字段名称)。

7.2 校验( Validation )

• 校验表单对于防止不正确的数据进入应用程序是必不可少的。最好是尽可能捕获用户输入数据的问题,告诉用户发生了什么错误,以及他们如何来修整它,这个一个用户友好系统的检验尺度。

• 如果用户输入的数据是不能正确格式化并且不能正确转好到此属性的格式的时候,这个问题会被在“类型转化”一章中介绍的类型转换框架捕获。

7.2.1 手动校验• 最直接的校验表单数据的方式就是在 action里编写校验代

码。这个方法的问题是限制了复用,因为校验被限制的 action 类里,不容易在 action 之间或者应用程序的其他部分服用。但是在一些情况下,一些负责的商业逻辑可能必须也只能在 action 类中实现,也就是说手动校验可能是最后的选择。

• 在第 5章“ acton 的基本校验”一节已经介绍了如何利用Validateable接口来实现校验代码与 excute() 函数分离以及利用 ValidationAware 来处理错误信息。如果必须使用手动校验应该采用这种方法,而不要把校验代码直接加入到 excute() 方法中去。而本节重点是如何利用 struts2 的框架来进行校验。

7.2.2 使用框架校验

• 校验框架自动读取 volidation文件里面的定义进行校验。这些文件和 action 类放在相同的包里,命名为 ClassName-voidation.xml 。这是一个 SimpleAction-validation.xml 的例子,

• 校验器 ( 和字段校验器 )必须有一个 type属性,这里指向是已经注册的校验器的名字。 validators 元素还必须有 <param>元素,带有 name 和 value属性来设置校验器实例的任何需要设置的参数。

• ( 1 )校验错误信息• ( 2 )用别名区分特定的校验规则• ( 3 )继承关系• ( 4)打开校验

7.2.3 注册校验器

• 校验规则是通过校验器来处理的,它们必须注册到 ValidatorFactory (使用 registerValidator 方法)最简单的方法就是添加一个文件名为 validators.xml 的文件,位置在 classpath ( /WEB-INF/classes )的根目录下,来声明所有的校验器。

7.2.4 字段校验和非字段校验

• 非字段校验又称为简单校验器 ( 例如 ExpressionValidator)执行校验检查不是绑定到单一的指定的字段上的当 -validation.xml 文件中声明一个简单的校验器时,不需要把一个 fieldname属性和它关联。

• 字段校验器 ( 例如 Email Validator) 设置用来在一个单一字段上进行校验检查。它们要求在 -validation.xml文件中执行一个 fieldname属性。有两种不同的 XML语法 ( 但是等效 ) 可以用来声明字段校验器。

7.2.5 校验器的短路• 使用 short-circuit属性可以将校验器设置为短路,也就是

如果此校验无法通过立刻结束校验过程,不再执行以下的其他校验器,如代码所示。

• 代码 字段校验: *-validators.xml• <!-- 校验器的短路 -->• <field name="email">• <field-validator type="required" short-circuit="tru

e">• <message>You must enter a value for email.</mes

sage>• </field-validator>• <field-validator type="email" short-circuit="true">• <message>Not a valid e-mail.</message>• </field-validator>• </field>

7.2.6 客户端校验

• Struts2 提供了客户端校验的支持。 Struts2会生成校验的 JavaScript 发送到浏览器端。用户提交的时候,无需再传输数据到服务器端,直接在网页中检验结果,这种做法可以提高响应速度。如图所示,展示了服务器端校验和客户端校验流程的区别。

7.2.7 AJAX 校验实例

• 本节将以一个例子说明如何实现 AJAX 校验,首先看看基本的表单校验是如何做的。

• 如果实现 AJAX 校验,有以下几个步骤:• ( 1 )首先要把 form主题设为 AJAX。• ( 2 )还需要给应用程序配置好 DWR, DWR通

过位于 /WEB-INF/目录下的 dwr 配置( dwr.xm) 来设置 。

• ( 3 )需要注册一个 DWRServlet 到web 应用程序中。下面显示了一个典型的配置了 DWR Serlvet 的web.xml ,

7.3 国际化

• 开发国际化的 Web 应用是很艰苦的,本书不期望能涵盖这个挑战性的话题的所有方面。 Struts2提供了广泛的功能可以处理构建一个国际化应用程序的技术方面。 Struts 的国际化功能也可以用其他领域如格式化日期等数据。

7.3.1 信息的来源• 为了给不同的地区提供不同的字符串信息,需要一个应用程序的源文件来为给定的 locale找到正确的字符串信息。幸运的是, Java 以 Java.util.ResourceBundle抽象类的方式提供了这些功能。 JDK带来了资源包的实现: java.util.PropertyResouceBundle 和 java.util.ListResouceBundle 。这两个类会自动加载含有这些国际化信息的属性文件( *.properties文件)。当 ResourceBundle.getBundle()被调用的时候,会查找正确的 ResourceBundle 名字,如CreateLocalizedCategory.properties (缺省一般是英文)、 CreateLocalizedCategory_zh.properties (简体中文)或者 CreateLocalizedCategory_de.properties(德文)。

7.3.2 资源包搜索顺序• 资源包按照下面的顺序搜索:• ( 1 ) ActionClass.properties 。• ( 2 ) BaseClass.properties 。• ( 3 ) Interface.properties (每一个接口和子接口 ) 。• ( 4) ModelDriven 的model( 如果实现了 ModelDrive

n) ,对于model 对象从第一步重复执行。• ( 5) package.properties ( 类所在的目录和每个父目录直到根目录 ) 。

• ( 6)搜索 i18n message key 自己的层次关系。• ( 7 )全局资源属性 (struts.custom.i18n.resources) ,在

struts.properties里定义的。

7.3.3 添加默认的资源包• 经常会有一系列的信息用于应用程序中,并且经常要在得到一个

本地化的信息是让那个应用程序可以得到它们。 Struts 提供了几种方法来配置这些本地化信息的:

• ( 1 )通过配置属性或者编程的方式注册默认资源包的功能。修改 struts.properties文件中的: struts.custom.i18n.resources属性如:

• struts.custom.i18n.resources=org.hibernate.auction.i18n• ( 2 )资源包可以被添加到一个启动类中,例如 ServletContextL

isterner ,方法是通过对 com.LocaclizedTextUtil 类做调用:• LocaclizedTextUtil.addDefaultResouceBundle(“org.hibernat

e.auction.i18n”);• ( 3 ) <s:i18n>标签• 除了确定的默认资源包搜索之外,可以通过 <s:i18n>标签来是一

个资源包在 JSP页面里可用。

7.3.4 使用国际化信息

• 本节将介绍如何利用 Struts2 的几种技术配合使用来实现国际化。

• ( 1 )使用 <s:text>标签• ( 2 )在标签属性中使用 getText()• ( 3 )格式化日期和数字• ( 4)在校验里使用国际化信息• ( 5)类型转换中使用国际化信息

7.4 处理上传文件

• Struts2 是通过 Commons FileUpload文件上传。Commons FileUpload通过将 HTTP 的数据保存到临时文件夹,然后 Struts 使用 fileUpload拦截器将文件绑定到 Action 的实例中。就能够以本地文件方式的操作浏览器上传的文件。本节将以一个例子来介绍如何在 Struts2 中实现文件上传。

• 说明: Commons FileUpload也是开源组件Apache Commons 的一部分,专门解决 Java 在 http 中上传文件的问题,详细信息请参考它的源码网站“ http://commons.apache.org/fileupload/”

7.4.1 编写文件上传页面

• 首先,创建文件上传页面 FileUpload.Jsp ,内容如代码 7-17 所示。在 FileUpload.Jsp 中,先将表单的提交方式设为 POST ,然后将 enctype 设为multipart/form-data ,这并没有什么特别之处。接下来 <s:file/>标志将文件上传控件绑定到Action 的myFile属性。文件的界面如图 7.5所示。

7.4.2 构造处理上传的 Action

• 如代码所示,在 FileUploadAction 中分别包含 setMyFileContentType 、 setMyFileFileName 、 setMyFile 和 setCaption四个 Setter 方法,后两者很容易明白,分别对应 FileUpload.jsp 中的 <s:file/>和 <s:textfield/>标志。但是前两者并没有显式地与任何的页面标志绑定,那么它们的值又是从何而来的呢?其实, <s:file/>标志不仅仅是绑定到myFile ,还有myFileContentType (上传文件的 MIME 类型)和myFileFileName (上传文件的文件名,该文件名不包括文件的路径)。因此,<s:file name="xxx" />对应 Action 类里面的 xxx、xxxContentType 和 xxxFileName三个属性。

7.4.3 编写结果页面• 下面就来看看上传成功的页面: ShowUpload.jsp获得 imageFileName ,将其 Upl

oadImages 组成 URL ,从而将上传的图像显示出来,如代码所示。• 代码 上传文件结果: ShowUpload.jsp• <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>• <%@ taglib prefix="s" uri="/struts-tags" %>• <html>• <head> • <title> Struts2 File Upload </title> • </head> • <body > • <div style ="padding: 3px; border: solid 1px #cccccc; text-align: center"> • <img src ='UploadImages/<s:property value ="imageFileName" />'/>• <br/> • <s:property value ="caption"/> • </div> • </body> • </html>

7.4.4 更多配置• 在运行上述例子,会发现服务器控制台有如下输出:• Mar 20 , 2007 4 : 08 : 43 PM org.apache.struts2.dispat

cher.Dispatcher getSaveDir• INFO: Unable to find 'struts.multipart.saveDir' property

setting. Defaulting to javax.servlet.context.tempdir• Mar 20 , 2007 4 : 08 : 43 PM org.apache.struts2.interc

eptor.FileUploadInterceptor intercept• INFO: Removing file myFile C:\Program Files\Tomcat 5.5 \work \Catalina \localhost \Struts2_Fileupload \upload_251447c2_1116e355841 __ 7ff7_00000006.tmp

7.4.5 错误处理• 上述例子实现的图片上传的功能,实际中可能需要阻止用户上传非图片类型的文件。

在 Struts2 中如何实现这点呢?其实这也很简单,对上述例子作如下修改即可。• 上传文件页面: FileUpload.jsp• <%@ page language ="java" contentType = "text/html; charset=utf-8"%> • <%@ taglib prefix="s" uri="/struts-tags" %>• <html>• <head> • <title> Struts2 File Upload </title> • </head> • <body> • <s:form action ="fileUpload" method ="POST" enctype ="multipart/form-dat

a" > • <s:file name ="myFile" label ="Image File" /> • <s:textfield name ="caption" label ="Caption" /> • <s:submit /> • </s:form >• <s:fielderror/> • </body> • </html>•

7.4.6 多文件上传• 与单文件上传相似, Struts2 实现多文件上传也很简单。可

以将多个 <s:file/> 绑定 Action 的数组或列表。• 代码 多文件上传: FileUpload.jsp• < s:form action ="doMultipleUploadUsingList" method ="POST" enctype ="multipart/form-data" >

• < s:file label ="File (1)" name ="upload" /> • < s:file label ="File (2)" name ="upload" /> • < s:file label ="FIle (3)" name ="upload" /> • < s:submit /> • </ s:form >

7.5 防止重复提交

• 作为一个 Web 应用的开发程序员, Web 应用的无限制特性会给应用带来很多问题。这里的问题是客户端控制着访问 Web 应用的请求,可能得到非预期的不同顺序的 Web 应用请求,或者多次得到相同的多请求。当用户不止一次单击Web 表单的提交按钮时候就会发生这个问题。因为在每次单击的时候浏览器都会发送表单请求信息,或者表单提交成功并显示新页面以后,单击重新加载按钮也会发生问题。这种情况相当危险,因为表单提交时要求服务器完成某种操作信号,并且许多 action 不应该执行多次。

7.5.1 使用 <s:token>标签增加标记

• <s:token>标签创建一个新的表单标记,并用 token做关键值把 token 的值保存到 session 中。这个 token 的值是一个经过加密的、非常安全的UUID,所以不用担心出现重复的 token值或者用户推测出 token值的情况。代码 7-26演示了一个使用 token 标签的例子。

7.5.2 使用 tokeninterceptor验证

• 在配置文件中为 action 定义 com.opensymphony.webwork.interceptor.TokenInterceptor ,就可以根据 Web请求参数来验证标记了。想要保证 interceptor 正常工作,首先应该确保以下条件:

7.5.3 使用 TokenSessionStoreInterceptor 重新输出结果页面

• com.opensymphony.webwork.interceptor.TokenSessionStoreInterceptor扩展了 Token- Interceptor ,重写了 handleInvalidToken() 方法和 handleValidToken() 方法。在 TokenSession - StoreInterceptor 中, handleValidToken() 在 session 中保存了一个包含 actionInvocation 和标记 )值的对象。如果有重复提交并且重用了同样的标记,这个对象会被从 session 中取出,并根据保存的 action 的状态重新 输出结果页面,这里的 action 不需要再被执行一次。这样可以效地防止重复提交引发的 action重复执行,这可以给用户带来更好的用户体验:他 们重新看到一个相同的结果页面,这比得到一个错误页面要好很多。

7.6 自动等待页面

• 等待页面是一个很常见的用例,比较耗时的操作都需要提提供一个等待页面给用户。 Struts2 提供了 execAndWati interceptor 可以为用户返回一个等待页面时,在另外一个线程中运行 action 。

• 首先需要使用 struts 实现一个正常的处理流程。如图所示,显示了用户请求的流程,必须在 ation 的所有工作完成之后,才能包页面返回给用户。无论如何, action执行的时间有多长,用户等待反馈页面的时间就有多长。

7.7 控制反转 (IoC)• Ioc 是 Inversion of Control 的缩写,即反转模式。这里有著名的好莱坞理论:“你呆着别动,到时我会找你”。 Ioc又名为 Dependency Injection 中文是依赖注射,也就是将类之间的关系通过第三方进行注射,不需要类自己去解决调用关系。(关于 Ioc 的详细解释和优点将在本书的 Spring篇重点解释,本节仅介绍如何在 Struts2 中采用 Ioc )

• 众所周知, Struts2 是以 Webwork作为基础发展出来。而在 Webwork 2.2 之前的 Webwork版本,其自身有一套控制反转的实现, Webwork 2.2 在 Spring 框架的如火如荼发展的背景下,决定放弃控制反转功能的开发,转由 Spring 实现。值得一提的是,因为有越来越多的开源组件(如 iBATIS等)都放弃与 Spring 重叠的功能的开发。因此 Struts2推荐通过 Spring 实现控制反转。

7.7.1 配置 Spring• Struts2 对明确的实现了对 Spring 的支持的,所以在 Strut

s2 中配置 Spring 是非常容易的。• ( 1 )将所需的 Spring 的 jar文件加入到工程的 classpat

h 和应用程序的 WEB-INF/lib 下,这些 jar文件包括: spring-web-2x.jar 、 spring-beans-2.x.jar 、 spring-core-2.x.jar 、 spring-context-2.0.5.jar 、 struts2-spring-plugin-2.x.jar (这些文件都可以在 struts2 的发布包中找到)。( 2 )在 Web 应用中加入 Spring 的 ContextLoaderListener监听器,方便 Spring 与 Web容器交互。( 3 ) struts.properties文件,告知 Struts2运行时使用 Spring 来创建对象(如 Action等),内容如下(这也是缺省配置): struts.objectFactory = spring

7.7.2 实现接口• 遵循 Spring 的面向接口原则编程,创建接口 ChatService

和默认的默认实现 ChatServiceImpl 类,• 代码 Ioc接口实现: ChatService.java• package tutorial;• import java.util.Set;• /*• * Ioc接口实现• */• public interface ChatService {• Set < String > getUserNames();• }

7.7.3 创建 Action

• 接下来就该新建 Action 了,如代码所示。 ChatAction 类使用属性( Getter/Setter )注入法取得ChatService 对象。

• 代码 反转控制配置: struts.xml• < package name ="Struts2_IoC" extends ="str

uts-default" > • < action name ="Chat" class ="chatAction" > • < result > /UserList.jsp </ result > • </ action > • </ package >

7.7.4 结果页面

• 最后编写 UserList.jsp ,内容如所示。输入 http://localhost:8080/Chat.action 。

7.8 用 Annotation 配置 Struts• 在已经发布的 JDK1.5(tiger) 中增加新的特色叫 Annotatio

n 。 Annotation 提供一种机制,将程序的元素如:类,方法,属性,参数,本地变量,包和元数据联系起来。这样编译器可以将元数据( Meta Data )存储在 Class文件中。这样虚拟机和其它对象可以根据这些元数据来决定如何使用这些程序元素或改变它们的行为。

• 在 Struts2 中引入 Annotation 就可以把很多配置以 Annotation 方式实现,减少配置的工作量也减少错误。如果与类或者方法的配置,可以直接加入的类和方法注释中去,在查看代码的时候也就知道了配置的情况,如每一个方法或者属性都应该用 Annotation 实现的配置,新添加一项属性或方法,也就顺便添加了配置。而在类与配置文件分离的情况下,有时候很难定位,而且容易遗漏和配置错误。

7.8.1 配置 Action

• 在 struts.xml文件中配置 action 可以使用 annotation 来代替,如表所示。使用 Java5的 annotation 可以把这些配置直接写到 action 类中。给出了一个例子如代码所示。

7.8.2 配置拦截器• 要使用这些 Annotation ,需要在拦截器栈中加入

AnnotationWorkflowInterceptor 。配置拦截器的 Annotation列表如表所示。给出了一个例子如代码所示。

7.8.3 配置验证器的

• 如果需要使用基于 annotation 的验证,必须用 Validation Annotation 标注类或者接口。表中列出 struts2 提供的验证器的 annotation 。

7.8.4 配置类型转换

• 如果要使用基于 annotation 的类型转换,必须用 Conversion Annotation 标注类或者接口。使用泛型集合而不是在 Type Conversion文档中指定集合和map 的类型。这就是说基本上不用*ClassName-conversion.properties* 文件了。类型转换的 annotation 如表所示。

7.9 小结

• 本章涵盖了很多主题,尽管它们之间看上去没有什么联系,但他们都有一个共同的特点:底层的Struts2 框架让这些高级的、独特的特性成为可能。本章介绍的内容都是在实际项目中经常遇到的问题, Struts2 为开发者提供了简化开发的方法,使发者更加有效率的工作,能把精力专注于业务的实现。这正是采用框架的初衷,使框架为实现业务服务。


Top Related