450 likes | 650 Views
第 7 章 Struts2 的高级应用. 本章将讲述一些 Struts2 的高级应用,有一些是 struts2 提供的特殊功能,大部分是由 struts2 提供的基本功能组合而来。这些方法与设计模式的思路类型,“面对同样的问题采用经典的、可靠的、易用的方法来解决”。还有一些问题是 Web 开发中经常遇到的,比如类型转换,此类问题工作量巨大。 Struts2 也提供了完美的解决方案,使开发者从这些重复劳动中解放出来。. 7.1 类型转换( Type Conversion ).
E N D
第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可以自动完成大多数常用的类型转换。这也是为什么在struts2中字符串对基本类型如int的转化是自动完成的,无需任何配置和代码。已支持的与字符串之间转换类型包括: • Struts2可以自动完成大多数常用的类型转换。这也是为什么在struts2中字符串对基本类型如int的转化是自动完成的,无需任何配置和代码。已支持的与字符串之间转换类型包括: • String • boolean/Boolean • char/Character • int/Integer、float/Float、long/Long、double/Double。 • dates:使用当前request指定的Locale信息对应的SHORT格式。 • arrays:假定每一个字符串都能够转换成对应的数组元素。 • collections:如果不能确定对象类型, 将假定集合元素类型为String, 并创建一个新的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="true"> • <message>You must enter a value for email.</message> • </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(如果实现了 ModelDriven),对于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)资源包可以被添加到一个启动类中,例如ServletContextListerner,方法是通过对com.LocaclizedTextUtil类做调用: • LocaclizedTextUtil.addDefaultResouceBundle(“org.hibernate.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,将其UploadImages组成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.dispatcher.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.interceptor.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-data" > • <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的支持的,所以在Struts2中配置Spring是非常容易的。 • (1)将所需的Spring的jar文件加入到工程的classpath和应用程序的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 ="struts-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)中增加新的特色叫 Annotation。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为开发者提供了简化开发的方法,使发者更加有效率的工作,能把精力专注于业务的实现。这正是采用框架的初衷,使框架为实现业务服务。