搭建Struts2开发环境
以目前最新的版本struts2.5.5为例
-
拷贝struts2相关的jar包(可根据实际需求选择)到WEB-INF/lib下;
-
复制官方的示例工程中源码根路径(一般为src目录)下的struts.xml到自己项目的源码根路径下,并修改该文件以适应自己的项目;
-
在项目的web.xml配置文件中添加如下配置节:
<filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter</filter-class> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
至此,struts2的开发环境已搭好。
注意:如果需要进行某些配置,可参考struts2的核心包,struts2-core-2.5.5.jar,中的struts-default.xml文件。
struts.xml配置说明
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"http://struts.apache.org/dtds/struts-2.5.dtd">
<struts>
<!--
package: 包. struts2 使用 package 来组织模块.
name属性: 必须. 用于其它的包应用当前包.
extends属性: 当前包继承哪个包, 继承的, 即可以继承其中的所有的配置.
通常情况下继承 struts-default(在 struts-default.xml 文件中定义)。
namespace属性:可选, 如果它没有给出, 则以 / 为默认值.
若 namespace 有一个非默认值, 则要想调用这个包里的Action,
就必须把这个属性所定义的命名空间添加到有关的 URI 字符串里,例如:
http://localhost:8080/contextPath/namespace/actionName.action
-->
<package name="hellostruts2" extends="struts-default" namespace="/ldj">
<!--
action: 一个 struts2 的请求就是一个 action.
name: 对应一个 struts2 的请求的名字(或对一个 servletPath,但去除 / 和扩展名).
class: 处理该 action 的类, 默认值为: com.opensymphony.xwork2.ActionSupport.
method:处理该 action 的类的入口方法, 默认值为: execute.
-->
<action name="product-save" class="com.ldj.test.struts.hellostruts2.Product" method="save">
<!--
result: 结果,表示 action 方法执行后可能返回的一个结果.
所以一个 action 节点可能会有多个 result 子节点.
多个 result 子节点使用 name 来区分.
name: 标识一个 result,和 action 方法的返回值对应,默认值为 success.
type: 表示结果的类型,默认值为 dispatcher(转发到结果).
-->
<result name="saved" type="dispatcher">/WEB-INF/pages/product-saved.jsp</result>
</action>
<!-- 这是一个最简写的 action 配置, 其等同于:
<action name="product-input"
class="com.opensymphony.xwork2.ActionSupport"
method="execute">
<result>/WEB-INF/pages/product-input.jsp</result>
</action>
-->
<action name="product-input">
<result>/WEB-INF/pages/product-input.jsp</result>
</action>
</package>
</struts>
关于 Struts2 请求的扩展名问题
- org.apache.struts2 包(该包位于 struts2-core-2.5.5.jar 中)下的 default.properties 中配置了 Struts2 应用的一些常量。
- struts.action.extension 定义了当前 Struts2 应用可以接受的请求的扩展名。
- 可以在 struts.xml 文件中以常量配置的方式修改 default.properties 所配置的常量,
配置节是 配置节的一级子节点: <constant name="struts.action.extension" value="action,do,"></constant>
关于 <result>
配置节
-
result 是 action 节点的子节点。
-
result 代表 action 方法执行后,可能去到的一个目的地。
-
一个 action 节点可以配置多个 result 子节点。
-
result 的 name 属性值对应着 action 方法可能有的一个返回值:
<result name="index">/index.jsp</result>
-
result 一共有 2 个属性,还有一个是 type,表示结果的响应类型。
-
result 的 type 属性值在 struts-default 包的 result-types 节点的 name 属性中定义。常用的有:
- dispatcher(默认的): 转发. 同 Servlet 中的转发.
- redirect: 重定向
- redirectAction: 重定向到一个 Action
注意:通过 redirect 的响应类型也可以便捷的实现 redirectAction 的功能:
<result name="index" type="redirectAction"> <param name="actionName">testAction</param> <param name="namespace">/atguigu</param> </result>
或者:
<result name="index" type="redirect">/atguigu/testAction.do</result>
- chain: 转发到一个 Action
<!-- 注意: 不能通过 type=dispatcher 的方式转发到一个 Action 只能是: -->
<result name="test" type="chain">
<param name="actionName">testAction</param>
<param name="namespace">/atguigu</param>
</result>
<!-- 不能是:<result name="test">/atguigu/testAction.do</result> -->
action 和 Action
action: 代表一个 Struts2 的请求。
Action 类: 能够处理 Struts2 请求的类.
- 属性的名字必须遵守与 JavaBeans 属性名相同的命名规则。属性的类型可以是任意类型,从字符串到非字符串(基本数据类型)之间的数据转换可以自动发生。
- 必须有一个不带参的构造器: 通过反射创建实例。
- 至少有一个供 Struts2 在执行这个 action 时调用的方法(即 action 配直节中 method 属性指定的值)
- 同一个 Action 类可以包含多个 action 方法。
- Struts2 会为每一个 HTTP 请求创建一个新的 Action 实例, 即 Action 不是单例的, 是线程安全的.
在 Action 中访问 WEB 资源
- 什么是 WEB 资源?
HttpServletRequest、HttpSession、ServletContext 等原生的 Servlet API。
- 为什么访问 WEB 资源?
B\S 的应用的 Controller 中必然需要访问 WEB 资源:向域对象中读写属性,读写 Cookie,获取 realPath 等....
- 如何访问?
(1)和 Servlet API 解耦的方式:只能访问有限的 Servlet API 对象, 且只能访问其有限的方法(读取请求参数,读写域对象的属性,使 session 失效……)
- 使用 ActionContext
- 实现 XxxAware 接口
选用的建议: 若一个 Action 类中有多个 action 方法, 且多个方法都需要使用域对象的 Map 或 parameters, 则建议使用Aware 接口的方式。
session 对应的 Map 实际上是 SessionMap 类型的! 强转后若调用其 invalidate() 方法, 可以使其 session 失效!
(2)和 Servlet API 耦合的方式: 可以访问更多的 Servlet API 对象, 且可以调用其原生的方法。
- 使用 ServletActionContext
- 实现 ServletXxxAware 接口.只有org.apache.struts2.interceptor.ServletResponseAware 和org.apache.struts2.interceptor.ServletRequestAware两个
ActionSupport
ActionSupport 是默认的 Action 类: 若某个 action 节点没有配置 class 属性, 则 ActionSupport 即为待执行的 Action 类. 而 execute 方法即为要默认执行的 action 方法
<action name="testActionSupport">
<result>/testActionSupport.jsp</result>
</action>
等同于:
<action name="testActionSupport"
class="com.opensymphony.xwork2.ActionSupport"
method="execute">
<result>/testActionSupport.jsp</result>
</action>
在手工完成字段验证, 显示错误消息, 国际化等情况下, 推荐继承 ActionSupport。
通配符映射
一个 Web 应用可能有成百上千个 action 声明,可以利用 struts 提供的通配符映射机制把多个彼此相似的映射关系简化为一个映射关系。
通配符映射规则
- 若找到多个匹配,没有通配符的那个将胜出。
- 若指定的action不存在,Struts 将会尝试把这个 URI 与任何一个包含着通配符 * 的动作进行匹配。
- 被通配符匹配到的 URI 字符串的子串可以用 {1},{2} 来引用。{1} 匹配第一个子串,{2} 匹配第二个子串,以此类推。
- {0} 匹配整个 URI 。
- 若 Struts 找到的带有通配符的匹配不止一个,则按先后顺序进行匹配。
- 可以匹配零个或多个字符,但不包括 “ / ” 字符,如果想把 / 字符包括在内,需要使用 **。如果需要对某个字符进行转义,需要使用 \。
例子:在action配置节中可以使用通配符
<action name="emp-*-*" class="com.ldj.test.struts.hellostruts2.TestWildChar" method="{1}">
<result name="{2}">/WEB-INF/pages/wildchar/{2}.jsp</result>
<!-- 允许method调用的方法列表,struts2.3以后的版本适用 -->
<allowed-methods>dosave, doinput, delete</allowed-methods>
</action>
表示method和result中的name使用“emp--”中的第1个子串,result结果中使用第2个子串,{0}表示整个。
在struts2.3之前的版本,正常的配置就可以了,但在struts2.3版本之后,使用通配符调用方法时,内部会验证是否允许访问该方法,所以要加上 <allowed-methods>方法名1,方法名2…</allowed-methods>
代码。
动态方法调用
默认情况下动态方法调用是禁用的,需要配置使其打开,配置如下(在struts.xml文件的<struts>
配置节下)
<constant name="struts.enable.DynamicMethodInvocation" value="true"></constant>
使用语法:通过 url 动态调用 Action 中的方法。如:
/struts-app2/Product.action,Struts 调用 Product 类的 execute。
/struts-app2/Product!save.action,Struts 动态调用 Product 类的 save() 方法。
如果 Action 使用了通配符配置 action 则需要被动态调用的方法也需要添加到 <allowed-methods>
标签内。
值栈ValueStack
可以从 ActionContext 中获取值栈对象,值栈分为两个逻辑部分:
- Map 栈(ContextMap):实际上是 OgnlContext 类型,是个 Map,也是对 ActionContext 的一个引用。里边保存着各种 Map:requestMap, sessionMap, applicationMap, parametersMap, attr。
- 对象栈(Object栈):实际上是 CompoundRoot 类型,是一个使用 ArrayList 定义的栈。里边保存各种和当前 Action 实例相关的对象,是一个数据结构意义的栈。
注意:struts2 标签的使用
① 在 JSP 页面中引入:
<%@ taglib prefix="s" uri="/struts-tags" %>
② 在需要使用的地方使用,常用的形式有:<div> <s:debug></s:debug> </div>
OGNL
值栈中的属性值:
- 对于对象栈: 对象栈中某一个对象的属性值。
- Map 栈: request, session, application 的一个属性值或一个请求参数的值。
读取对象栈中对象的属性:
-
若想访问 Object Stack 里的某个对象的属性,可以使用以下几种形式之一:object.propertyName、object['propertyName']、object["propertyName"]。
-
ObjectStack 里的对象可以通过一个从零开始的下标来引用。ObjectStack 里的栈顶对象可以用 [0] 来引用,它下面的那个对象可以用 [1] 引用。
-
[n] 的含义是从第 n 个开始搜索,而不是只搜索第 n 个对象。
-
若从栈顶对象开始搜索,则可以省略下标部分:message。
-
结合 s:property 标签:<s:property value="[0].message" />(等价于 <s:property value="message" />)。
默认情况下,Action 对象会被 Struts2 自动的放到值栈的栈顶。
访问Map栈中的数据
需要给 OGNL 表达式加上一个前缀字符 #(如果没有前缀字符 #,搜索将在 ObjectStack 里进行):#object.propertyName、#object['propertyName']、#object["propertyName"]。
调用字段和方法:
-
可以利用OGNL调用:
- 任何一个Java类里的静态字段或方法;
- 被压入到值栈的对象上的公共字段和方法。
-
默认情况下,Struts2不允许调用任意Java类静态方法,需要重新设置struts.ognl.allowStaticMethodAccess,标记变量值为true。
-
调用静态字段或方法需要使用如下的语法:
- @fullyQualifiedClassName@fieldName: @java.util.Calendar@DECEMBER
- @fullyQualifiedClassName@methodName(argumentList): @app4.Util@now()
-
调用一个实例字段或方法的语法:其中 object 是 Object Stack 栈里的某个对象的引用
- object.fieldName: [0].datePattern
- object.methodName(argumentList): [0].repeat(3, “Hello”)
访问数组类型的属性
有些属性将返回一个对象数组而不是单个对象,可以像读取任何其他对象属性那样读取它们。这种数组型属性的各个元素以逗号分隔, 并且不带方括号。
-
可以使用下标访问数组中指定的元素:colors[0] 。
-
可以通过调用其 length 字段查出给定数组中有多少个元素:colors.length。
访问 List 类型的属性
有些属性将返回的类型是 java.util.List,可以像读取任何其他属性那样读取它们。这种 List 的各个元素是字符串,以逗号分隔,并且带方括号。
- 可以使用下标访问 List 中指定的元素:colors[0]。
- 可以通过调用其 size 方法或专用关键字 size 的方法查出给定List 的长度:colors.size 或 colors.size() 。
- 可以通过使用 isEmpty() 方法或专用关键字 isEmpty 来得知给定的 List 是不是空:colors.isEmpty 或 colors.isEmpty() 。
- 可以使用 OGNL 表达式来创建 List,创建一个 List 与声明一个 Java 数组是相同的: {“Red”, “Black”, “Green”}。
访问 Map 类型的属性
读取一个 Map 类型的属性将以如下所示的格式返回它所有的键值对:{ ke-1=value-1, key-2=value-2, ... , key-n=value-n }。
- 若希望检索出某个 Map 的值,需要使用如下格式: map[key] 。
- 可以使用 size 或 size() 得出某个给定的 Map 的键值对的个数。
- 可以使用 isEmpty 或 isEmpty() 检查某给定 Map 是不是空。
- 可以使用如下语法来创建一个 Map:#{ ke-1=value-1, key-2=value-2, ... , key-n=value-n }。
使用 EL 访问值栈中对象的属性
<s:property value=“fieldName”> 也可以通过 JSP EL 来达到目的:${fieldName}。
原理: Struts2 将 HttpServletRequest 包装后的对象 org.apache.struts2.dispatcher.StrutsRequestWrapper 传到页面上,而这个类重写了 getAttribute() 方法。
声明式异常
exception-mapping 元素:配置当前 action 的声明式异常处理。exception-mapping 元素中有 2 个属性:
- exception,指定需要捕获的的异常类型,即异常的全类名(可以是更一般的异常类型,例如java.lang.Exception);
- result,指定一个响应结果,该结果将在捕获到指定异常时被执行,既可以来自当前 action 的声明,也可以来自 global-results 声明。
可以通过 global-exception-mappings 元素为应用程序提供一个全局性的异常捕获映射。但在 global-exception-mappings 元素下声明的任何 exception-mapping 元素只能引用在 global-results 元素下声明的某个 result 元素。
声明式异常处理机制由 ExceptionMappingInterceptor 拦截器负责处理,当某个 exception-mapping 元素声明的异常被捕获到时,ExceptionMappingInterceptor 拦截器就会向 ValueStack 中添加两个对象:exception,表示被捕获异常的 Exception 对象;exceptionStack,包含着被捕获异常的栈。
可以在视图上通过 <s:property>
标签显示异常消息。
Struts2类型转换
从一个HTML表单到一个Action对象,类型转换是从字符串到非字符串,因为HTTP没有类型的概念,所有的表单输入都是字符串。
在 struts2 中,把请求参数映射到 action 属性的工作由 Parameters 拦截器负责,它是默认的 defaultStack 拦截器中的一员,Parameters 拦截器可以自动完成字符串和基本数据类型之间转换。
如果类型转换失败:
- 若 Action 类没有实现 ValidationAware 接口,Struts 在遇到类型转换错误时仍会继续调用其 Action 方法,就好像什么都没发生一样。
- 若 Action 类实现了 ValidationAware 接口,Struts 则不会继续调用其 Action 方法,而是在 struts.xml 中检查相关 action 元素的声明是否包含一个 name=input 的 result,如果有则把控制权转交到该 result 元素,若没有则抛出异常。
类型转换错误消息的定制
作为默认的 default 拦截器的一员,ConversionError 拦截器负责添加与类型转换有关的出错消息(前提是 Action 类必须实现了 ValidationAware 接口)和保存各请求参数的原始值。
若 jsp 页面中的字段标签使用的不是 simple 主题,则非法输入字段将导致一条如下格式的出错消息:“Invalid field value for field fieldName”。
覆盖默认的出错消息,步骤如下:
- 在对应的Action类所在包中新建[ActionClassName].properties文件
- 在属性文件中添加如下键值对:invalid.fieldvalue.[fieldname]=custom error message,例如 invalid.fieldvalue.age=输入非法,年龄必须是正整数
显示错误消息:
- 如果是simple主题,可以通过
<s:fielderror fieldName="filedname"></s:fielderror>
标签显示错误消息;
该消息在一个 ul,li,span中。如何去除ul,li,span呢?
在template.simple下面的fielderror.ftl定义了simple主题下,s:fielderror标签显示错误消息的样式,所以修改该 配置文件即可。在src下新建 template.simple包,新建fielderror.ftl文件,把原生的fielderror.ftl中的内容复制到新建的fielderror.ftl中,然后剔除ul,li,span部分即可。
- 通过debug标签,可知若转换出错,则在值栈的 Action(实现了ValidationAware 接口)对象中有一个 fieldErrors 属性。该属性的类型为
Map<String, List<String>>
键为字段(属性名),值为错误消息组成的 List。所以可以使用EL或OGNL的方式来显示错误消息:${fieldErrors.age[0]}
。
自定义类型转换器
步骤:
- 开发类型转换器类,即扩展StrutsTypeConverter类;
- 配置类型转换器。
其中,配置类型转换器又有两种方式:
(1)基于字段的配置
- 在字段所在的model(可能是Action也可能是个JavaBean)的包下,新建一个 [ModelClassName]-conversion.properties文件;
- 在该文件中输入键值对:[fieldname]=类型转换器的全类名;
- 第一次使用该转换器时创建实例;
- 类型转换器是单例的。
(2)基于类型的配置
- 在src下新建xwork-conversion.properties;
- 键入:待转换的类型=类型转换器的全类名;
- 在当前Struts2应用被加载时创建实例。
类型转换器与复杂属性
<struts:form>
标签的 name 属性可以被映射到一个属性的属性。
类型转换器与集合
Struts 还允许填充 Collection 里的对象,这常见于需要快速录入批量数据的场合。
Struts2 消息处理与国际化
配置国际化资源文件
依据作用范围的不同有以下配置方式。
(1)Action 范围资源文件:在 Action 类文件所在的路径建立名为 [ActionName][language][country].properties 的文件,例如:TestI18nAction_zh_CN.properties。
(2)包范围资源文件:在包的根路径下建立文件名为 package_[language]_[country].properties 的属性文件,一旦建立,处于该包下的所有 Action 都可以访问该资源文件。注意:包范围资源文件的 baseName 就是 package,不是 Action 所在的包名。
(3)全局资源文件
在src目录下建立属性文件,命名方式:[basename][language][country].properties
在struts.xml中进行如下配置:<constant name="struts.custom.i18n.resources" value="baseName"/>
或者在 struts.properties 中进行如下设置:struts.custom.i18n.resources=baseName
(4)临时指定资源文件:<s:i18n.../>
标签的 name 属性指定临时的国际化资源文件。
国际化资源文件加载顺序:原则是离当前Action较近的将被优先加载
假设我们在某个 ChildAction 中调用了 getText("username"):
(1) 加载和 ChildAction 的类文件在同一个包下的系列资源文件 ChildAction.properties
(2) 加载 ChildAction 实现的接口IChild,且和 IChild 在同一个包下 IChild.properties 系列资源文件。
(3) 加载 ChildAction 父类 Parent,且和 Parent 在同一个包下的 baseName 为 Parent.properties 系列资源文件。
(4) 若 ChildAction 实现 ModelDriven 接口,则对于 getModel() 方法返回的 model 对象,重新执行第(1)步操作。
(5) 查找当前包下 package.properties 系列资源文件。
(6) 沿着当前包上溯,直到最顶层包来查找 package.properties 的系列资源文件。
(7) 查找 struts.custom.i18n.resources 常量指定 baseName 的系列资源文件。
(8) 直接输出该key的字符串值。
在JSP页面上和Action类中访问国际化资源文件的 value值
(1)在 Action 类中
若 Action 实现了 TextProvider 接口,则可以调用其 getText() 方法获取 value 值,可以通过继承 ActionSupport 的方式来间接实现TextProvider 接口。getText()方法有重载的方法以获取带占位符的资源。
(2)JSP页面上
① 可以使用 s:text 标签配合其 name 属性来访问国际化资源文件里的 value 值;对于表单标签可以使用表单标签的 key 属性值,另外对于资源的定义:
- 若有占位符,则可以使用s:text标签的s:param子标签来填充占位符
- 可以利用标签和OGNL表达式直接访问值栈中的属性值(对象栈 和 Map 栈)
例1,资源文件中有如下资源定义:time=Time:{0}
JSP页面中可以如下访问
<s:text name="time">
<s:param value="date"></s:param>
</s:text>
例2,资源文件中有如下资源定义:time2=Time:${date}
JSP页面中可以如下访问,此时会对time2进行OGNL求值 <s:text name="time2"></s:text>
② 对于表单标签,若 label 标签使用 %{getText('username')} 的方式就也可以从国际化资源文件中获取 value 值。因为此时在对象栈中有 DefaultTextProvider 的一个实例,该对象中提供了访问国际化资源文件的 getText()方法,同时还需要通知 struts2 框架 label 中放入的不再是一个普通的字符串,而是一个 OGNL 表达式,所以使用 %{} 把 getText() 包装起来,以强制进行 OGNL 解析。
利用超链接实现动态加载国际化资源文件
(1)Struts2 使用 i18n 拦截器处理国际化,并且将其注册在默认的拦截器中。
(2)i18n拦截器在执行 Action 方法前,自动查找请求中一个名为 request_locale 的参数:
- 如果该参数存在,拦截器就将其作为参数,转换成 Locale 对象,并将其设为用户默认的 Locale(代表国家/语言环境)。并把其设置为 session 的 WW_TRANS_I18N_LOCALE 属性值。
- 若 request 没有名为 request_locale 的参数,则 i18n 拦截器会从 Session 中获取 WW_TRANS_I18N_LOCALE 的属性值,若该值不为空,则将该属性值设置为浏览者的默认 Locale 。
- 若 session 中的 WW_TRANS_I18N_LOCALE 的属性值为空,则从 ActionContext 中(即浏览器设置)获取 Locale 对象。
Struts2运行流程分析
- 请求发送给 StrutsPrepareAndExecuteFilter
- StrutsPrepareAndExecuteFilter 询问 ActionMapper: 该请求是否是一个 Struts2 请求(即是否返回一个非空的 ActionMapping 对象)
- 若 ActionMapper 认为该请求是一个 Struts2 请求,则 StrutsPrepareAndExecuteFilter 把请求的处理交给 ActionProxy
- ActionProxy 通过 Configuration Manager 询问框架的配置文件,确定需要调用的 Action 类及 Action 方法
- ActionProxy 创建一个 ActionInvocation 的实例,并进行初始化
- ActionInvocation 实例在调用Action的过程前后,涉及到相关拦截器(Intercepter)的调用。
- Action 执行完毕,ActionInvocation 负责根据 struts.xml 中的配置找到对应的返回结果。调用结果的 execute 方法,渲染结果。在渲染的过程中可以使用Struts2 框架中的标签。
- 执行各个拦截器 invocation.invoke() 之后的代码
- 把结果发送到客户端
相关的几个 API
- ActionMapping:Simple class that holds the action mapping information used to invoke a Struts action. The name and namespace are required
- ActionMapper:When given an HttpServletRequest, the ActionMapper may return null if no action invocation request matches, or it may return an ActionMapping that describes an action invocation for the framework to try
- ActionProxy:ActionProxy is an extra layer between XWork and the action so that different proxies are possible.
- ActionInvocation:An ActionInvocation represents the execution state of an Action. It holds the Interceptors and the Action instance. By repeated re-entrant execution of the invoke() method, initially by the ActionProxy, then by the Interceptors, the Interceptors are all executed, and then the Action and the Result.
输入验证
Struts2提供了两种输入验证:声明式验证(即进行配置即可)和编程验证。
基于 XWork Validation Framework 的声明式验证
Struts2 提供了一些基于 XWork Validation Framework 的内建验证程序,使用这些验证程序不需要编程,只要在一个XML文件里对验证程序应该如何工作作出声明即可。
需要声明的内容包括:
- 确定哪些 Action 字段需要验证;
- 编写一个验证程序配置文件,它的文件名必须是以下两种格式之一:
① 若一个 Action 类的多个 action 使用同样的验证规则:ActionClassName-validation.xml;
② 若一个 Action 类的多个 action 使用不同的验证规则:ActionClass-alias-validation.xml,例如 UserAction-User_create-validation.xml。
注意:别名 alias 是指 action 请求的名字,即 action 配置节的 name 属性值;优先使用带别名的规则,再继续使用不带别名的规则,所以可以把各个 action 公有的验证规则配置在不带别名的配置文件中。
- 确定验证失败时的响应页面:在 struts.xml 文件中相关的 action 节点下定义一个
<result name="input">
的元素.
编程验证:通过编写代码来验证用户输入。
声明式验证的 helloworld
(1)先明确对哪一个 Action 的哪一个字段进行验证: age
(2)编写配置文件:
-
在需要被验证的字段所属的 Action 类所在的包下新建 [ActionClassName]-validation.xml 文件。
-
编写验证规则,参见 struts-2.5.5/docs/docs/basic-validation.html 文档。
-
在配置文件中可以定义错误消息:
<field name="age">
<field-validator type="int">
<param name="min">20</param>
<param name="max">50</param>
<message>Age needs to be between ${min} and ${max}</message>
</field-validator>
</field>
- 该错误消息可以国际化:
<message key="error.int"></message>
,再在国际化资源文件中加入一个键值对:error.int=Age needs to be between ${min} and ${max}
(3)若验证失败,则转向input的那个result,所以需要配置name=input的result:<result name="input">/validation.jsp</result>
。
(4)如何显示错误消息?
- 若使用的是非 simple,则自动显示错误消息。
- 若使用的是simple主题,则需要 s:fielderror 标签或直接使用 EL 表达式(使用 OGNL ):${fieldErrors.age[0] }(或者:
<s:fielderror fieldName="age"></s:fielderror>
,推荐使用这种)。
声明式验证框架的原理
Struts2默认的拦截器栈中提供了一个validation拦截器,每个具体的验证规则都会对应具体的一个验证器类。在com.opensymphony.xwork2.validator.validators 下的 default.xml把验证规则名称和验证器关联起来,形如:<validator name="required" class="com.opensymphony.xwork2.validator.validators.RequiredFieldValidator"/>
Struts2内建了15个验证规则,分别是:
- conversion validator:转换验证器
- date validator:日期验证器
- double validator:浮点验证器
- email validator:email 验证器
- expression validator:表达式验证器
- fieldexpression validator:字段表达式验证器
- int validator:整型验证器
- regex validator:正则表达式验证器
- required validator:非空验证器
- requiredstring validator:非空字符串验证器
- stringlength validator:字符串长度验证器
- url validator:url 格式验证器
- visitor validator:复合属性验证器
短路验证
<validator …/>
元素和 <field-validator …/>
元素可以指定一个可选的 short-circuit 属性,该属性指定该验证器是否是短验证器,默认值为 false。
对同一个字段内的多个验证器,如果一个短路验证器验证失败,其他验证器不会继续校验。
<!-- 设置短路验证: 若当前验证没有通过, 则不再进行后续的int验证 -->
<field-validator type="conversion" short-circuit="true">
<message>^Conversion Error Occurred</message>
</field-validator>
<field-validator type="int">
<param name="min">20</param>
<param name="max">60</param>
<message key="error.int"></message>
</field-validator>
若类型转换失败,默认情况下还会执行后面的拦截器,还会进行验证。
可以通过修改 ConversionErrorInterceptor 源代码的方式使当类型转换失败时,不再执行后续的验证拦截器,而直接返回 input 的 result
Object action = invocation.getAction();
if (action instanceof ValidationAware) {
ValidationAware va = (ValidationAware) action;
if(va.hasFieldErrors() || va.hasActionErrors()){
return "input";
}
}
字段和非字段验证
(1)关于非字段验证:不是针对于某一个字段的验证。
<validator type="expression">
<param name="expression">
<![CDATA[password==password2]]>
</param>
<message>Password is not equals to password2</message>
</validator>
显示非字段验证的错误消息,使用 s:actionerror 标签。
(2)不同的字段使用同样的验证规则,而且使用同样的响应消息:“error.range=${getText(fieldName)} needs to be between ${min} and ${max}”,原理如下图:
自定义验证器
(1)定义一个验证器的类:
- 自定义的验证器都需要实现 Validator.
- 可以选择继承 ValidatorSupport 或 FieldValidatorSupport类.
- 若希望实现一个一般的验证器,则可以继承 ValidatorSupport.
- 若希望实现一个字段验证器,则可以继承 FieldValidatorSupport.
- 具体实现可以参考目前已经有的验证器.
- 若验证程序需要接受一个输入参数,需要为这个参数增加一个相应的属性.
(2)在配置文件中配置验证器:
- 默认情况下,Struts2 会对类路径根目录下的 validators.xml 文件(内容格式类似包 com.opensymphony.xwork2.validator.validators 下的 default.xml 文件)进行解析,加载该文件中指定的验证器.
- 若类路径下没有指定的验证器文件,则加载 com.opensymphony.xwork2.validator.validators 下的 default.xml 中指定的验证器.
(3)使用:和普通的验证器一样。
(4)示例代码:自定义一个 18 位身份证验证器。
编程验证
(1)Struts2 提供了一个 Validateable 接口,可以供 Action 类实现这个接口以提供编程验证功能。
(2)ActionSupport 类已经实现了 Validateable 接口,我们可以通过重写 validate 方法来实现编程验证。
文件的上传和下载
文件上传
(1)准备表单,要想使用HTML表单上传一个或多个文件,须把:
- HTML表单的enctype属性设置为 multipart/form-data。
- HTML表单的method属性设置为 post。
- 添加
<input type="file">
字段。
(2)Struts2 的文件上传是由 FileUpload 拦截器完成的,而它实际上使用的是 Commons FileUpload 组件,所以需要导入:“commons-fileupload-1.3.jar
commons-io-2.0.1.jar ”
(3)步骤:
①. 在JSP页面的文件上传表单里使用 file 标签。如果需要一次上传多个文件,就必须使用多个file标签,但它们的名字必须是相同的。
②. 在Action中新添加3个和文件上传相关的属性,这3个属性的名字必须是以下格式:
(FileName):File - 被上传的文件,例如:data;
(FileName)ContentType:String - 上传文件的文件类型,例如:dataContentType;
(FileName)FileName:String - 上传文件的文件名,例如:dataFileName。
如果上上传多个文件,则上述的3个属性,可以改为List类型,多个文件域的name属性值需要一致。
③. 使用IO流进行文件的上传即可。
(4)通过配置 FileUploadInterceptor 拦截器的参数可以对上传文件的可接受扩展名、内容类型、大小进行限制,还可以对出错消息进行定制。
maximumSize (optional) - 上传的单个文件的最大值(以字节为单位),默认的最大值为2M。
allowedTypes (optional) - 允许的上传文件的类型,多个类型使用 , 分割。
allowedExtensions (optional) - 允许的上传文件的扩展名,多个使用 , 分割。
注意:在 org.apache.struts2 下的 default.properties 中有对上传的文件总的大小的限制,可以使用常量的方式来修改该限制:
struts.multipart.maxSize=2097152
。
定制错误消息,可以在国际化资源文件中定义如下的消息:
struts.messages.error.uploading - 文件上传出错的消息.
struts.messages.error.file.too.large - 文件超过最大值的消息.
struts.messages.error.content.type.not.allowed - 文件内容类型不合法的消息.
struts.messages.error.file.extension.not.allowed - 文件扩展名不合法的消息.
问题:此种方式定制的消息并不完善,可以参考 org.apache.struts2 下的 struts-messages.properties,可以提供更多的定制信息。
文件下载
(1)Struts2 中使用 type="stream" 的 result 进行下载即可,在使用一个 Stream 结果时,不必准备一个 JSP 页面。
(2)具体使用细节参看 Struts2 文档中的 result 节。
(3)可以为 stream 的 result 设定如下参数:
-
contentType: 被下载的文件的MIME 类型,默认值为 text/plain。
-
contentLength: 被下载的文件的大小,以字节为单位。
-
contentDisposition: 可以设置下载文件名的ContentDispositon响应头,默认值为inline,通常设置为如下格式:attachment;filename="document.pdf" 。
-
inputName: Action中提供的文件输入流的名字,默认值为inputStream。
-
bufferSize: 缓存的大小,默认为 1024。
-
allowCaching: 是否允许使用缓存,默认值为true。
-
contentCharSet: 文件下载时的字符编码。
(4)注意:在 Struts2.5.2 之前,stream 结果类型的参数会自动的在值栈中搜索对应的值,但之后的版本中,需要在配置文件中指定而不会自动搜索值栈。
最佳实践是,在配置文件中利用 OGNL 为需要动态变化的参数进行配置,同时在对应 Action 类中添加对应的字段并提供 getter,示例代码如下图。
因为 inputName 参数需要的是输入流的名称(字符串),所以不需要再对 inputStream 进行OGNL解析了。
防止表单重复提交
表单重复提交的情形,在不刷新表单页面的前提下:
多次点击提交按钮;
已经提交成功,回退之后,再提交;
在控制器响应页面的形式为转发情况下,若已经提交成功,然后点击“刷新”。
注意:
- 若刷新表单页面,再提交表单不算重复提交;
- 若使用的是 redirect 的响应类型,已经提交成功后,再点击“刷新”,不是表单的重复提交。
Struts2 解决表单的重复提交问题
-
在 s:form 中添加 s:token 子标签。这会使得 Struts2:
- 生成一个隐藏域;
- 在 session 添加一个属性值;
- 隐藏域的值和 session 的属性值是一致的。
-
使用 Token 或 TokenSession 拦截器。注意:
- 这两个拦截器均不在默认的拦截器栈中,所以需要手工配置一下;
- 若使用Token拦截器,则需要配置一个token.valid的result;
- 若使用TokenSession拦截器,则不需要配置任何其它的result。
Token 和 TokenSession 区别
都是解决表单重复提交问题的。使用 token 拦截器会转到 token.valid 这个result;使用 tokenSession 拦截器则仍然会响应既定的目标页面,但不会执行 tokenSession 的后续拦截器,就像什么都没发生过一样。
可以使用 s:actionerror 标签来显示重复提交的错误消息。该错误消息可以在国际化资源文件中覆盖。该消息默认值可以在struts-messages.properties文件中找到:struts.messages.invalid.token=The form has already been processed or no token was supplied, please try again 。
自定义拦截器
自定义拦截器的具体步骤如下。
(1)定义一个拦截器的类
- 可以实现Interceptor接口(需要实现所有的方法);
- 继承AbstractInterceptor抽象类(只需实现intercept方法即可)。
(2)在 struts.xml 文件配置,配置节路径:struts(根元素) > package > interceptors > interceptor 。
<structs>
<package …>
<interceptors>
<interceptor name="hello" class="com.ldj.test.hellostruts2.MyInterceptor"></interceptor>
</interceptors>
<action name="testMyInterceptor" class="com.ldj.test.hellostruts2.TestMyInterceptor">
<interceptor-ref name="hello"></interceptor-ref>
<interceptor-ref name="defaultStack"></interceptor-ref>
<result>/success.jsp</result>
</action>
</package>
<structs>
注意:在自定义的拦截器中可以选择不调用 ActionInvocation 的 invoke() 方法。那么后续的拦截器和 Action 方法将不会被调用,Struts 会渲染自定义拦截器 intercept 方法返回值对应的 result。