- 每一个请求都会建立一个Action,而每一个Action实例,都会拥有一个ActionContext实例,每一个ActionContext中都包含一个值栈。值栈会贯穿Action的整个生命周期,即与请求request的生命周期相同。为了保证这一点,就将值栈建立在request做用域中。即将值栈放入到request的一个属性中,当请求结束,request被销毁时,值栈空间被收回。
- 值栈的实质是request中的一个属性值,这个属性的名称为:struts.valueStack,保存在ServletActionContext的常量STRUTS_VALUESTACK_KEY中。

- 从以上的叙述中可知,从HttpServletRequest中获取,即从底层获取值栈对象的方式为:
HttpServletRequest request = ServletActionContext.getRequest();
ValueStack myValueStack = (ValueStack) request.getAttribute(
ServletActionContext.STRUTS_VALUESTACK_KEY);
2.3.2 从ActionContext中获取值栈对象
- 从以前对文档的阅读可知,在Struts2中OGNL上下文Context接口中的实现类为ActionContext,即ActionContext为Map结构。该Map中事先已经存放好了一组对象,这组对象中就包含根对象值栈。其他为非根对象。

- 由此可知,值栈也可从ActionContext中直接获取。
ValueStack myValueStack2 = ActionContext.getContext().getValueStack();
2.4 值栈操做
2.4.1 搭建测试环境
一、定义实体Student:java
package com.eason.valuestack.entity;
public class Student {
private String name;
private int age;
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
public Student() {
super();
// TODO Auto-generated constructor stub
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
二、定义Ation正则表达式
package com.eason.valuestack.entity;
import java.util.HashMap;
import java.util.Map;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.util.ValueStack;
public class SomeAction {
public String SomeAction() {
Student student = new Student("张三", 21);
//获取值栈对象
ValueStack valueStack = ActionContext.getContext().getValueStack();
return "success";
}
}
三、注册Actionsql
<struts>
<package name="demo" namespace="/test" extends="struts-default">
<action name="some" class="com.eason.valuestack.entity.SomeAction">
<result>/show.jsp</result>
</action>
</package>
</struts>
四、定义视图页面showexpress
<body>
<s:debug/>
</body>
2.4.2 向root中显式放入数据
- 向root中显式地放入数据,有五种方式。下面以向root中放入Student(name, age)对象为例。
一、经过操做值栈来添加无名称对象
- ValueStack即值栈,其实现类OgnlValueStack的对象即值栈对象。既然其称之为值栈,就应该有栈的相关操做,查看OgnlValueStack的源码,能够看到其确实有栈操做的方法。但,再看看这些方法的实现,其本质是调用了值栈对象的属性根对象root的栈操做,即对值栈的直接操做,本质上是对根对象root的栈操做。

- 经过对值栈的压栈操做向root中添加数据。因为root的本质是ArrayList,其特色就是向其中添加的对象时没法指定名称的。
public String execute() {
Student student = new Student("張三", 21);
//获取值栈对象
ValueStack stack = ActionContext.getContext().getValueStack();
stack.push(student);
return "success";
}
- OgnlValueStack值栈中有一个方法set(),可向其中的对象指定名称,其底层也是采用map来实现的。
public String execute() {
Student student = new Student("張三", 21);
//获取值栈对象
ValueStack stack = ActionContext.getContext().getValueStack();
stack.set("studentMap", student);
return "success";
}
五、将root做为ArrayList来添加对象浏览器
- 从前面对root的源码分析可知,root的本质是ArrayList,因此能够调用其add()方法来添加数据。
public String execute() {
Student student = new Student("張三", 21);
//获取值栈对象
ValueStack stack = ActionContext.getContext().getValueStack();
stack.getRoot().add(student);
return "success";
}
可是,须要注意的是,经过add()添加的数据,是将root做为ArrayList来使用,而ArrayList的add()方法会自动将数据放入到list的最后。就本例来讲,经过add()添加的数据,会放入到root栈的栈底。而经过前面栈的操做所添加的数据,是将数据放入到root栈顶。
缓存
2.4.3 向root中隐式放入数据
- 当Struts2接收到一个请求后,会立刻建立一个Action对象,而后为该Action对象建立一个ActionContext对象,那么也就建立了值栈对象。
- 值栈对象建立好以后,首先会将建立好的Aciton对象直接放入到值栈的栈顶。因而,JSP页面对于Action属性的访问,直接写上Action属性名称便可。
public class SomeAction {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String execute() {
......
return "success";
}
}
2.4.4 向context中显式放入数据
- 向context中放入数据,就是向map中放入数据,须要指定key。Struts2中已经定义好一些key,用于完成特定数据功能。访问context中的数据,即为非根数据,须要使用#。

一、向context中直接放入数据
- 向context中直接放入数据,至关于在context中添加了用户自定义的key和value,即map对象。
public String execute() {
Student student = new Student("張三", 21);
ActionContext.getContext().put("myStudent", student);
return "success";
}
- 向ActionContext中直接写入数据,即造成以下状况:

- 其访问方式即为:
<body>
<s:debug/>
---------------------context数据显示----------------------<br/>
name = <s:property value="#myStudent.name"/>
age = <s:property value="#myStudent.age"/>
</body>
- 固然,因为context自己是request范围,那么向context中直接添加数据,也即放入request范围中数据。此时,JSP页面可经过系统定义好的名称为request的key来访问。
<body>
<s:debug/>
---------------------context数据显示----------------------<br/>
name = <s:property value="#request.myStudent.name"/>
age = <s:property value="#request.myStudent.age"/>
</body>
二、向context的ssession中放入数据
- 向context的session中放入数据,即将对象放入到session范围。
public String execute() {
Student student = new Student("張三", 21);
ActionContext.getContext().getSession().put("myStudent", student);
return "success";
}
<body>
<s:debug/>
---------------------context数据显示----------------------<br/>
name = <s:property value="#session.myStudent.name"/>
age = <s:property value="#session.myStudent.age"/>
</body>
三、向context的application中放入数据
- 向context的application中放入数据,即将对象放入到application范围中。
public String execute() {
Student student = new Student("張三", 21);
ActionContext.getContext().getApplication().put("myStudent", student);
return "success";
}
<body>
<s:debug/>
---------------------context数据显示----------------------<br/>
name = <s:property value="#application.myStudent.name"/>
age = <s:property value="#application.myStudent.age"/>
</body>
四、JSP页面经过称之为attr的key读取数据
- 若JSP页面经过名称为attr的key读取数据,系统会依次从page、request、session、application范围来查找指定key的数据。若没有找到,则为其赋值为null。
<body>
<s:debug/>
---------------------context数据显示----------------------<br/>
name = <s:property value="#attr.myStudent.name"/>
age = <s:property value="#attr.myStudent.age"/>
</body>
2.4.5 向context中隐式放入数据
- 有两种数据是在用户不知情的状况下自动放入到context中的。
一、请求参数
- 在提交Action请求时所携带的参数,会自动存放到context的parameters属性中。

- 须要注意的是,提交action请求时所携带的参数,参数会写入到context中的parameters中。


- 固然,在struts配置文件中Action重定向时提交的参数,若重定向到Action,但Action中有属性用于接该参数,则是放入到root中的。若重定向到的Action中无属性接收该参数,或者直接重定向到页面,则是将数据放入到context中key为parameters的map中。
二、Action对象
- 当Action实例建立好后,不只会自动将该Action的属性放入到root的栈顶,并且还会将Action实例自己也放入到context的action属性中。

2.4.6 数据的加载顺序
一、root中数据的加载顺序
- 当在Action中仅仅向context放入某数据some后,页面不管是经过#从context中读取数据,仍是不使用#直接从root中读取数据,都可获得放入的值。
- 对于从root中读取数据,其底层调用了值栈的findValue()方法。查看ValueStack的实现类OgnlValueStack源码,方法间的调用关系以下:

- 从以上源码可知,<s:property /> 会先从root中加载数据,若不存在该数据,则会从context 中加载,而不会直接设值为null。
二、request中数据的加载顺序
- 当在Action中向context与root中分别为某数据some赋予不一样的值时,页面从context中与root中可获取其相应的值。但若从#request中获取some值,会发现其获取的与root中的值相同。

- Struts2将HttpServletRequest进行了再次包装,加强了其功能。在JSP页面中经过对表达式<%= request %>的输出,可看到其类型已经再也不是HttpServletRequest,而是org.apache.struts2.dispatcher.StrutsRequestWrapper。
- <s:property/>对于从request中读取数据,其底层调用了StrutsRequestWrapper的getAttribute()方法。查看StrutsRequestWrapper类的源码:

- 从以上源码可知,若request范围的属性值不空,则直接取该值;若为空,则再调用值栈的findValue()方法。即找到,则取root中的值;root中没有找到,再从context中查找。
- 从这个分析可知,以前所说的ActionContext.getContext()所获取的为request范围,并不许确,或者说,并不正确。由于如今从#request中获取的值并非context中的值,而是root中的值。
2.5 OGNL对于集合的操做
- Struts2中使用OGNL对于集合的操做主要涉及如下几种状况:
一、建立List与Map集合
二、遍历LIst与Map集合
三、集合元素的判断
四、集合投影
五、集合过滤 2.5.1 建立List与Map集合
- OGNL表达式中使用以下形式能够建立相应集合:
- List集合的建立:{元素1, 元素2, ...}
- Map集合的建立:#{'key1':value1, 'key2':value2, ...}
- 固然能够借助于Struts2标签<s:set/>建立一个有名称的集合对象:

- 此时,建立了一个名为userList的List<String>集合,其包含三个字符串元素,"zs","ls","ww"。

- 此时,建立一个名为userMap的Map<String, Object>集合,其包含两对元素:("name", "zs")与("pwd", 21)。
2.5.2 遍历List与Map集合
- <s:set/>标签还有一个属性 scope,用于指定要将此 List 或 Map 存放入哪一个范围。其值
能够为:application、session、request(放入 context 中)、action(放入 root 中)。
- 若不指定范围,则会将此变量直接放入 ActionContext 中,与值栈、application、session
等对象并列(以下图),造成非根对象。因此要对它们进行访问,须要加上#。如:#userList 或 #userMap。

一、对于List的遍历
- 若要对集合进行遍历,则须要借助<s:iterator/>标签。

- <s:iterator/>标签有个特色,会将当前迭代的对象临时放入值栈的栈顶。而<s:property/>
标签也有个特色,就是在不指定 value 属性时,会输出值栈栈顶元素的值。
二、对Map的遍历
- 方式一:直接输出栈顶的Map对象。

- 方式二:当前迭代的对象命名为变量 entry,该对象会被临时放入到值栈栈顶。因此对于该对象
的显示,无需使用#,直接输出便可。

- 方式三:Map 的元素为 Map.Entry 对象,而 Map.Entry 有两个属性:key 与 value。当迭代 Map
对象时,会将当前迭代的元素对象,即 Map.Entry 对象放入栈顶。即将 key 与 value 属性放
入了栈顶。
2.5.3 集合元素的判断
- 对于集合类型,可使用双目运算符 in 和 not in 组成表达式,来判断指定元素是否为
指定集合的元素。其两个运算数为 OGNL 表达式:e1 in e2 或 e1 not in e2。
- 其中,in用来判断e1是否在集合对象e2中;not in判断e1是否不在集合对象e2中。
其结果为 boolean 类型。如:

- 其输出结果为true。
2.5.4 集合投影
- 所谓集合投影,指对于集合中的全部数据(至关于行),只选择集合的某个属性值(字段)做为一个新的集合。即行数不变,只选择某列。使用:集合.{字段名}投影。例如,定义好了三个Bean:

2.5.4 集合查询
- 集合查询也称之为集合过滤,指的是对于集合中的全部数据,进行条件筛选,造成的结果集。即列数不变,只选择部分行。集合查询须要使用关键字this,表示当前检索的对象。
- 对于结果集的选择,可使用如下三个符号:
- ?:表示结果集的全部内容。
- ^:结果集的第一条内容。
- $:结果集的最后一条内容。
一、?的使用

- 查询结果:

二、^的使用

- 查询结果:
3 动态调用方法
- 若Action中存在多个方法,但在配置文件中注册该Action时,并未为每一个方法指定一个<action/>,而是只为这一个Action类注册一个<action/>。那么,当用户访问该<action/>时,到底执行哪一个方法,则是由用户发出的请求动态决定。即仅从配置文件中是看不出<action/>标签是对应哪一个方法的,只有在运行时根据具体的用户请求,才可以决定执行哪一个方法。这种状况称之为动态调用方法。动态调用方法有两种实现方式。
3.1 动态方法调用
- 动态方法调用是指,在地址栏提交请求时,直接在URL后跟上“!方法名”方式动态定义要执行的方法。
-不过,动态方法调用默认是关闭的,能够经过改变“动态方法调用”常量struts.enable.DynamicMethodInvocation的值来开启动态方法调用功能。


- 举例:dynamicMethodInvoke


- 地址栏中访问:
3.2 使用通配符定义的Action
- 使用通配符定义的action是指,在配置文件中定义action时,其name中包含通配符。请求URL中提交的action的具体值,将做为的真实值。而<action/>中的占位符将接收这个真实值。占位符通常出如今method属性中。
4 接收请求参数
4.1 属性驱动方式
- 所谓属性驱动方式是指服务器端接收来自客户端的离散数据的方式。用户提交的数据,Action 原封不动的进行逐个接收。该接收方式要求,在 Action 类中定义与请求参数同名的属性,即,要定义该属性的 set 方法。这样就可以使 Action 自动将请求参数的值赋予同名属性。
- 举例:receiveProperty



4.2 域驱动方式
- 所谓域驱动方式是指,服务器端以封装好的对象方式接收来自客户端的数据方式。将用
户提交的多个数据以封装对象的方式进行总体接收。该方式要求,表单提交时,参数以对象属性的方式提交。而 Action 中要将同名的对象定义为属性(为其赋予 getter and setter)。这样请求将会以封装好的对象数据形式提交给 Action。
- 举例:receiveObject





- 控制台输出状况:

- 对象类型数据接收的内部执行过程比较复杂:
一、当将表单提交给服务器后,服务器首先会解析出表单元素,并读取其中的一个元素name值,如读取以下:

二、执行Action的getStudent()方法以获取Student对象。判断Student对象是否为null,若为null,则服务器会经过反射建立一个Student对象。
三、因为此时的Student对象为null,因此系统会建立一个并调用setStudent()方法将student初始化。
四、此时的student已非空,会将刚才解析出的表单元素,经过调用其set方法,这里是setName()方法,将用户输入的值初始化该属性。
五、再解析下一个表单元素,并读取其name值。

六、再次执行Action的getStudent()方法以获取Student对象。此时的Student对象已非空。
七、将刚才解析出的表单元素,经过调用其set方法,这里是setAge()方法,将用户输入的值初始化该属性。 4.3 集合数据接收
- 所谓集合数据接收是指,以集合对象方式接收数据。此状况与域驱动数据原理是相同的。注意,集合与数组是不一样的。
- 举例:receiveCollection


- 此时用于接收集合数据的属性,不能定义为数组。由于数组长度是固定的,而集合长度是可扩展的。


4.4 ModelDriven方式
- ModelDriven接收请求参数运行背后使用了Struts的核心功能ValueStack。Struts2的默认拦截器中存在一个拦截器ModelDrivenInterceptor。当一个请求通过该拦截器时,在这个拦截器中,首先会判断当前要调用的Action对象是否实现了ModelDriven接口。若是实现了这个接口,则调用getModel()方法,并把返回值压入ValueStack栈顶。

- 举例:receiveModelDriven




4.5 Action对象是多例的
- action对象是多例的。对于包含name和age属性的RegisterAction,在Web中多个用户同时进行访问时,系统会为每一个用户建立一个RegisterAction实例,接收来自不一样用户的name和age。其值是各不相同,各不相干的。因此action也是线程安全的。
- 对于同一个业务,Web容器只会建立一个Servlet实例。这个实例容许多个请求共享,即容许多个线程共享。例如对于LoginServlet,只要用户登陆,不管几个用户,Web容器只会建立一个LoginServlet实例。若此时,将username与password定义为LoginServlet的成员变量,那么不一样的用户为其赋值是不一样的,后一个用户的值均会覆盖前一个用户的值,将引发并发问题,线程会不安全。因此,对于Servlet的使用,是不能定义成员变量的。
- 固然,也基于此,能够为Serlvet添加一个自增的成员变量做为该Servlet的访问计数器。
5 类型转换
5.1引入
- 在Struts2中,请求参数还能够是日期类型。如,定义一个请求参数brithday为Date类型,为其赋值为1949-10-01,则brithday接收到的不是字符串“1949-10-01”,而是日期类型 Sat Oct 01 00:00:00 CST 1949。
- 举例:typeconverter



5.2 默认类型转换器
- Struts2默认状况下能够将表单中输入的文本数据转换为相应的基本数据类型。这个功能的实现,主要是因为Struts2内置了类型转换器,这些转换器在struts-default.xml中能够看到其定义。

- 常见的类型,基本都可由String转换为相应类型。
- 如int和Integer,long和Long,float和Float,double和Double,char和Character,boolean和Boolean,Date(能够接收yyyy-MM-dd或者yyyy-MM-dd HH:mm:ss格式字符串),数组(能够将多个同名参数,存放到数组中),集合:能够将数据保存到List、Map中。
5.3 自定义类型转换器
- 在上面程序中,若输入非yyyy-MM-dd格式,如输入yyyy/MM/dd格式,在结果页面中还能够正常看到yyyy/MM/dd的日期输出。可是查看控制台,却给出了null值。其实底层已经发生了类型转换失败,抛出了类型转换异常TypeConversionException。若要使得系统能够接收其余格式的日期类型,则须要自定义类型转换器。
- 查看DateConverter、NumberConverter等系统定义好的类型转换器源码,能够看到它们都是继承自DefaultTypeConverter类。因此,咱们要定义本身的类型转换器,也要继承自该类。在使用时,通常须要覆盖其父类的方法convertValue(),用于完成类型转换。

- 定义convertValue方法时须要注意,其转换通常是定义为双向的。
- 就本例而言,从浏览器表单请求到服务器端action时,是由String到Date的转换,那么,第一个参数value为String类型的请求参数值,第二个参数toType为要转换为的Date类型。
- 当类型转换成功,可是服务器端其余部分出现问题后,须要返回原页面。此时用户填写过的数据应从新回显。回显则是由服务器端向浏览器端的运行,须要将转换过的Date类型从新转换为String。那么,此时的value则为Date类型,而toType则为String。
- 注意第一个参数value,若转换方向为从请求到action,则value为字符串数组。由于请求中是容许携带多个同名参数的,例以下面表单中的兴趣爱好项的name属性为hobby,其值就有可能为多个值。

- 这时的这个同名参数,其实就是数组。Struts2为了兼顾到这个多个同名参数的状况,就将从请求到action方向转换的value指定为String[],而非String。其底层使用的API为:String[] value = request.getParameterValues(...);。
- 注意,对于服务器端向浏览器端的转换,须要使用Struts2标签订义的表单才可演示出。演示时,age元素填入非数字字符,birthday填写正确。另外,向<action/>中添加input视图,Action类须要继承ActionSupport类。
- 之因此要继承自ActionSupport类,是由于ActionSupport类实现了Action接口,而该接口中定义INPUT字符串常量。一旦发生类型转换异常TypeConversionException,系统将自动转向input视图。
- 示例:
一、定义index页面:

二、定义Action

三、注册Action

四、定义自定义日期类型转换器
- 注意,Date在这里自动导入的是java.sql包,而咱们须要的是java.util包中的Date。

- 注意,此时定义的类型转换器,不只能够完成日期类型的转换,还能够实现如下的回显:age填写错误,birthday填写正确。但,若age填写正确,birthday填写错误的话,则birthday日期格式不正确,则没法回显。由于birthday值为null,此时发生的异常不是类型转换异常,而是格式解析异常ParseException。ParseException的发生不会使得页面跳转到input视图。因此若要使得日期格式不正确时跳转到input视图,则须要让其抛出TypeConversionException异常。

- 定义好类型转换器类后,须要注册该转换器,用于通知Struts2框架在遇到指定类型变量时,调用类型转换器。
- 根据注册方式的不一样以及应用范围的不一样,能够将类型转换器分为两类:局部类型转换器和全局类型转换器。
一、局部类型转换器
- 局部类型转换器,仅仅对指定Action的指定属性起做用。若注册方式为,在Action类所在的包下放置名称为以下格式的属性文件:ActionClassName-conversion.properties文件。其中ActionClassName是Action的类名,-conversion.properties是固定写法。
- 该属性文件的内容应该遵循以下格式:属性名称=类型转换器的全类名。

二、全局类型转换器
- 全局类型转换器,会对全部Action的指定类型的属性生效。其注册方式为,在src目录下放置名称为xwork-conversion.properties属性文件。该文件的内容格式为:待转换的类型=类型转换器的全类名。就本例而言,文件中的内容为:

- 注意,对于服务器端向浏览器端的转换,即数据的回显功能,须要使用Struts2标签订义的表单才可演示出。演示时,age元素填入非数字字符,birthday填写正确。
5.4 类型转换异常提示信息的修改
- 类型转换异常提示信息,是系统定义好的内容,若直接显示到用户页面,会使得页面显得不友好。可是,类型转换异常提示信息时能够修改。
- 类型转换异常提示信息的修改步骤:
一、Action所在包中添加名称为ActionClassName.properties的属性文件,其中ActionClassName为Action类的类名。
二、在该文件中写入内容:invalid.fieldvalue.变量名=异常提示信息 5.5 接收多种日期格式的类型转换器
- 对于前面定义的MyDateConverter类型转换器,不管是局部的仍是全局的,其转换的日期格式只能是在转换器中指定的格式:yyyy/MM/dd。不能是其余格式,原来默认的格式也不行。那么,如何使类型转换器能够处理多种日期格式的转换呢?
- 在以上项目的基础进行修改
一、获取日期格式
- 在类型转换器中定义一个 private 方法,用于判断并获取到日期的格式,若全部指定格
式均不符合,则直接抛出类型转换异常,跳转到表单页面。

二、保存日期格式对象
- 因为从页面到服务端方向是由String到Date类型的转换,value为待转换的数据,因此能够从这个String的数据中获取到日期格式,而后将String转换为 Date。
- 但若发生数据回显,则须要使用相同格式的日期格式对象,将Date转换为String。此时的value为没有格式的Date 数据,因此须要想办法获取到 String 到 Date 转换时的日期格式对象。也就是说,须要在 String 到 Date 转换时,就将用到的日期格式对象保存下来,以备Date 到 String 转换时使用。
- 那么,如何在 String 到 Date 转换时,保存日期格式对象呢?能够将其保存到 ServletAPI的域对象中。即保存到 request、session 或 application 中。
- 当在 String 到 Date 的转换时将日期格式对象保存在了 ServletAPI 的域对象中,在发生数据回显,由 Date 到 String 转换时,直接从相应的 ServletAPI 的域中获取日期格式对象便可。

三、最终的类型转换器定义

6 数据验证
- 在Web应用程序中,为了防止客户端传来的数据引起程序的异常,经常须要对数据进行验证。输入验证分为客户端验证与服务器端验证。客户端验证主要经过 JavaScript 脚本进行,而服务器端验证则主要是经过 Java 代码进行验证。
- 为了保证数据的安全性,通常状况下,客户端验证与服务器端验证都是要进行的。咱们这里所讲的是 Struts2 如何在服务端对输入数据进行验证的。Struts2 中的输入验证
有两种实现方式:手工编写代码实现,与基于 XML 配置方式实现。 6.1 手工编写代码实现数据验证
- 在Struts2中,验证代码是在Action中完成的。因为数据的使用者是Action方法,因此验证过程须要在Action方法执行以前进行。验证分为两类:对Action中全部方法执行前的验证;对Action中指定方法执行前的验证。
- 如下程序举例,均根据以下的需求来作:
- 登陆表单中提供用户名与手机号两个输入。要求,用户名和手机号不能为空,而且手机号要符合手机号码的格式:以1开头,后跟3/4/5或者8,最后9位数字。验证就是要验证用户名和手机号。
6.1.1 对Action中全部方法执行前的验证
- 首先,须要进行数据验证的Action类要继承自ActionSupport类。而后,重写validate()方法。Action中全部Action方法在执行以前,validate()方法均会被调用,以实现对数据的验证。
- 当某个数据验证失败时,Struts2会调用addFieldError()方法向系统的fieldErrors集合中添加验证失败信息。若是系统的fieldErrors集合中包含失败信息,struts2会将请求转发到名为input的Result。在input视图中能够经过<s:fielderror/>显示失败信息。
- ActionSupport类的addFieldError()方法含有两个参数:第一个参数必须为该Action的属性名的字符串,用于指定验证出错的属性名,即数据。第二个参数为字符串,是错误提示信息。

- 举例:validate1
一、在index.jsp中使用绝对路径,不然在input转向时会出错:
- 注意,这里使用基础路径将相对路径自动变为绝对路径,基础路径会自动加在本页面中全部的相对路径以前。将相对路径变为绝对路径。而基础路径的定义,在任何一个新建JSP文件中均有。

二、定义Action类:

三、对当前Ation中全部的方法被调用前均执行该验证方法:

四、若是系统的fieldErrors集合中包含失败信息,将请求转发到名为input的Result:
6.1.2 对Action中指定方法执行前的验证
- 经过在Action中定义public void validateXxx()方法来实现。validateXxx()方法只会验证action中方法名为xxx的方法。其中Xxx的第一个字母要大写。
- 当数据验证失败时,调用addFieldError()方法,也一样会向系统的fieldErrors集合中添加验证失败信息。
- 举例:validate2
6.2 基于XML配置方式实现输入数据验证
- 使用 XML 配置方式实现输入数据的验证,Action 类仍须要继承自 ActionSupport 类。验证仍分为两类:对 Action 中全部方法执行前的验证;对 Action 中指定方法执行前的验证。
- 该验证方式须要使用 XML 配置文件,该配置文件的约束,即文件头部,在xwork-core-2.3.24.jar 中的根下的 xwork-validatior-1.0.3.dtd 中能够找到。


- 验证配置文件的格式、内容以下:

- 验证器是由系统提供的,系统已经定义好了 16 种验证器。这些验证器的定义能够在
xwork-core-2.3.24.jar 中的最后一个包 com.opensymphony.xwork2.validator.validators 下的
default.xml 中查看到。

6.2.1 对Action中全部方法执行前的验证
- 在Action类所在的包中放入一个XML配置文件,该文件的取名应遵照ActionClassName-validation.xml规则。其中ActionClassName为Action的简单类名,-validation为固定写法。例如,Action类为com.abc.actions.UserAction,那么该文件的取名应为:UserAction-validation.xml。
- 举例:validate3
- 拷贝validate1,将Action中验证方法删除,并在Action所在包中添加验证配置文件。

6.2.2 对Action中指定方法执行前的验证
- 因为在struts.xml中,一个<action>标签通常状况下对应一个action方法的执行。因此,若要对Action类中指定方法进行执行前的验证,则须要按以下规则命名配置文件:ActionClassName-ActionName-validation.xml。其中ActionName指的是struts.xml中<action>标签的name属性值。
- 固然,该配置文件也是放在该Action的同一个包中的。
- 举例,validate4(拷贝validate3,在其基础上修改)


6.2.3 经常使用验证器用法
一、required:非空(必填)验证器
<field-validator type="required">
<message>性别不能为空!</message>
</field-validator>
二、requiredstring:非空字符串验证器
<field-validator type="requiredstring">
<param name="trim">true</param>
<message>用户名不能为空!</message>
</field-validator>
三、fieldexpression:字段表达式关系判断
<field-validator type="fieldexpression">
<param name=“expression”>pwd == repwd</param>
<message>确认密码与密码不一致</message>
</field-validator>
- 注意,假设pwd与repwd是表单中的两个元素的name属性值,且表达式就是pwd==repwd,而非pwd!=repwd。
四、stringlength:字符串长度验证器 <field-validator type="stringlength">
<param name="minLength">2</param>
<param name="maxLength">10</param>
<param name="trim">true</param>
<message>产品名称应在${minLength}-${maxLength}个字符之间</message>
</field-validator>
五、int:整数范围校验器
<field-validator type="int">
<param name="min">1</param>
<param name="max">150</param>
<message>年龄必须在 1-150 之间</message>
</field-validator>
- 注意,int、long、short、double与date校验器均继承自RangeValidatorSupport<T>类,是范围校验器,即不对数据类型进行验证,只对其有效范围进行校验。它们均由两个参数T min 和T max。
六、email:邮件地址校验器 <field-validator type="email">
<message>电子邮件地址无效</message>
</field-validator>
七、regex:正则表达式校验器
<field-validator type="regex">
<param name="regexExpression"><![CDATA[^1[3458]\d{9}$]]></param>
<message>手机号格式不正确!</message>
</field-validator>
- 注:<![CDATA[……]]>称为 cData 区,用于存放特殊表达式。
6.2.4 输入验证的执行流程
- 若以上四种输入验证方式均进行了设置,则其执行顺序以下:
一、首先执行基于XML的验证。系统按照下面顺序寻找校验文件:ActonClassName-validation.xml --> ActionClassName-ActionName-validation.xml
- 当系统寻找到第一个校验文件后,会继续搜索后面的校验文件。当搜索到全部校验文件后,会把校验文件中的全部校验规则汇总,而后所有应用于处理方法的校验。若是两个校验文件中指定的校验规则冲突,则使用指定方法的校验文件的校验规则。
二、执行Action中的validateXxx()方法。
三、执行Action中的validate()方法。
四、通过上面的执行,若是系统中的fieldErrors存在异常信息(即存放异常信息的集合的size大于0),系统自动将请求转发至名称为input的视图。若是系统中的fieldErrors没有任何异常信息。系统将执行action中的处理方法。 6.2.5 Action类的执行原理以及顺序
一、类型转换:类型转换失败是在Action调用相应属性的set方法以前发生的,类型转换失败,不影响程序的运行。
二、set方法:不管类型转换是否成功,都将执行该属性的set方法。只不过,类型转换失败,会设置该属性值为null。
三、数据验证:若对于类型转换失败的数据,程序中存在为null的验证,则会在向fieldErrors集合中加入类型转换异常信息的同时,将该属性为null的验证信息也加入fieldErrors集合。
四、Action方法:只有当fieldErrors集合的size为0,即没有异常信息时,才会执行Action方法
7 拦截器
- 拦截器是Struts2的一个重要特性。由于Struts2的大多数核心功能都是经过拦截器实现的。拦截器之因此称之为“拦截器”,是由于它能够在执行Action方法以前或者以后拦截下用户请求,执行一些操做。即在Action方法执行以前或者以后执行,以加强Action方法的功能。
- 例如,通常状况下,用户在打开某个页面以前,须要先登陆,不然是没法对资源进行访问的。这就是权限拦截器。
- Struts2内置了不少拦截器,每一个拦截器完成相对独立的功能,多个拦截器的组合体称之为拦截器栈。最为重要的拦截器栈是系统默认的拦截器栈DefaultStack。
- 拦截器定义在 struts2-core-2.3.24.jar!struts-default.xml中。
- 对workflow拦截器源码分析:
- 默认返回视图为Action.INPUT,即"input"

7.1 普通拦截器定义
- 一般状况下,自定义一个普通的拦截器类须要实现拦截器接口INterceptor。该接口中定义了三个方法:
- public void init():拦截器实例被建立以前被调用。
- public void destroy():拦截器实例被销毁以后被调用。
- public String intercept(ActionInvocation invocation) throws Exception:该方法在Action执行以前被调用,拦截器的附加功能在该方法中实现。执行参数invocation的invoke()方法,就是调用Action方法。
7.2 拦截器注册
- 拦截器类在定义好以后,须要在struts.xml配置文件中注册,以通知Struts框架。其注册方式有如下几种,可是不管哪一种,都须要引入Struts2默认的拦截器栈defaultStack。
7.2.1 拦截器栈方式注册

7.2.2 拦截器方式注册

7.2.3 默认拦截器栈方式注册
- 若该<package>中的全部Action均要使用该拦截器,则可指定默认的拦截器栈。不过,每一个包只能指定一个默认拦截器。可是,若为该包中的某个action显式地指定了某个拦截器,则默认拦截器将不起做用。
7.3 权限拦截器举例
- 只有通过登陆的用户才能够访问Action中的方法,不然,将返回“无权访问”提示。
- 本例的登陆,由一个JSP页面完成,即在该页面里将用户信息放入到session中,也就是说,只要访问过该页面,就说明登陆了;没有访问过,则为未登陆用户。
-
项目:permission_intercepter
7.3.1 项目建立
一、定义login.jsp
<body>
<% session.setAttribute("user", "beijing"); %>
登陆成功!
</body>
二、定义logout.jsp
<!-- 模拟用户退出 -->
<%
session.removeAttribute("user");
%>
用户退出系统!
三、定义Action
package com.eason.actions;
import com.opensymphony.xwork2.ActionContext;
public class SomeAction {
public String execute() {
//可以执行到Action,说明已经经过了拦截器验证,用户身份合法
ActionContext.getContext().getSession().put("message", "欢迎登陆");
return "success";
}
}
四、定义拦截器
package com.eason.interceptor;
import org.eclipse.jdt.internal.compiler.ast.Invocation;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.Interceptor;
public class PermissionInterceptor implements Interceptor {
public void destroy() {
}
public void init() {
}
@Override
public String intercept(ActionInvocation invocation) throws Exception {
String user = (String) ActionContext.getContext().getSession().get("user");
if("beijing".equals(user)) {
return invocation.invoke();
}
ActionContext.getContext().getSession().put("message", "您未登陆");
return "message";
}
}
五、配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
"http://struts.apache.org/dtds/struts-2.3.dtd">
<struts>
<package name="demo" namespace="/test" extends="struts-default">
<interceptors>
<interceptor name="permission" class="com.eason.interceptor.PermissionInterceptor"></interceptor>
<interceptor-stack name="permissionStack">
<interceptor-ref name="permission"></interceptor-ref>
<interceptor-ref name="defaultStack"></interceptor-ref>
</interceptor-stack>
</interceptors>
<action name="some" class="com.eason.actions.SomeAction">
<interceptor-ref name="permissionStack"></interceptor-ref>
<result>/success.jsp</result>
<result name="message">/message.jsp</result>
</action>
</package>
</struts>
六、定义success.jsp
<body>
进入系统!
</body>
七、定义message.jsp
<body>
message = ${message }
</body>
7.3.2 项目测试
一、在地址栏上直接提交some.action请求:

二、访问 login.jsp,进行用户登陆:

三、再次提交 some.action 请求:

四、访问 logout.jsp,进行用户退出:

五、第三次提交 some.action 请求:

7.4 方法过滤拦截器
- 直接实现Interceptor接口的过滤器存在一个问题:当将拦截器注册为<package/>中默认的拦截器栈<default-interceptor-ref/>时,会对该<package/>中全部<action/>指定的method进行拦截。此时,可以使用Interceptor接口的实现类MethodFilterInterceptor--方法过滤拦截器对指定方法进行过滤。
- 自定义方法过滤器,可继承MethodFilterInterceptor实现类,重写其方法doInterceptor()。workflow拦截器就是继承自该实现类:

- 项目:permission_intercepter2
一、定义Action:

二、定义拦截器类

三、修改配置文件

- MethodFilterInterceptor拦截器有两个参数可进行设置,用于指定或者排除要拦截的方法名。这两个属性不能同时进行设置:
8 国际化
8.1 国际化基础
8.1.1 获取系统支持的语言和国家简称
- 进行国际化编程和调试,须要了解系统支持的语言和国家简称,以及浏览器切换语言的位置。通常都是在浏览器的“工具 -> Internect 选项 ->语言"中可查看到。下面以360浏览器为例查看。

- 另外,在Java代码中也能够经过java.util.locale类中的静态方法获取Java支持的语言和国家简称。
8.1.2 Struts2的i18n拦截器
- Struts2的默认拦截器中包含了i18n拦截器。而i18n拦截器中包含了一个属性request_locale,专门用于设置浏览器的语言。

- 经过设置i18n拦截器的request_locale属性,可对被拦截Action所要转向页面的浏览器的语言进行设置。
8.2 全局范围资源文件
- 所谓全局范围资源文件是指,整个应用中的全部文件都可访问的资源文件。其命名要遵循如下格式:baseName_language_country.properties
- 其中baseName是资源文件的基本名,咱们能够自定义。可是language和country必须是Java支持的语言和国家简称。例如, login_en_US.properties。
- 对于全局资源文件,须要注意如下几点:
一、国家简称必须为大写;二、对于同一内容进行解释的资源文件,其基本名必须相同;三、资源文件须要在struts.xml中注册,注册其位置与基本名。 8.2.1 JSP 中普通文本的国际化
- 在 JSP 页面中,普通文本使用 Struts2 标签<s:text name=“”/>输出国际化信息。其中 name的值为资源文件中指定的 key。如:<s:text name=“formhead”/>将会在此显示“登陆表单”字样。
8.2.2 JSP 中表单元素的国际化
- 在 JSP 页面中,使用 Struts2 表单元素标签,经过 key 属性指定属性文件中的 key,如:<s:textfield name=“userName” key=“uname”/>,标签名称将显示“用户名”。<s:submit key=”submit”/>,提交按键上将显示“登陆”。
8.2.3 Action类中文本的国际化
- Action类中文本的国际化,可使用ActionSupport类的getText()方法,该方法的参数用于指定属性文件中的key。如,String message = this.getText("message");,此时message的实际值为“登陆成功“字符串。因此,Action类要继承ActionSupport类。
8.2.4 资源文件中的占位符
- 资源文件的value值中也能够包含动态参数,即在程序运行时才肯定资源文件中value的值。此时,资源文件的value值中的动态参数将以占位符的形式出现,如{0}、{1}等。
- 若在JSP页面中,参数的值可经过为<s:text/>添加<s:param/>来设置。

- 若在Action类中,则经过ActionSupport类的带两个参数的getText()进行赋值。要求第二个参数为String[]。
8.3 包范围资源文件
- 在一个大型应用中,整个应用有大量的内容须要实现国际化。若均放入全局资源文件中,则会使得全局资源文件过于庞大,因此能够针对不一样模块、不一样的action来组织国际化文件。
- 在要使用该资源文件的java的包下,放置按以下格式命名的资源文件:package_language_country.properties。
- 其中,package为固定写法。处于该包以及子包下的全部action均可以访问该资源。当查找指定key的消息时,系统会先从package资源文件查找,当找不到对应的key时,才会从全局资源文件中寻找。即包范围资源文件的优先级高于全局资源文件。
- 注意,非经Action跳转而至的JSP页面中读取的是全局资源文件中的内容。而经由Action跳转而来的JSP页面,其读取的为跳转而来的Action所在包的资源文件。
8.4 Action范围资源文件
- 能够单独为某个action指定资源文件。只须要在Action类所在的包中放置命名格式以下的资源文件:ActionClassName_language_country.properties。
- 其中ActionClassName为action类的简单名称。当查找指定key的消息时,系统会先从action范围资源文件查找,若是没有找到对应的key,接着会沿当前包往上查基本名为package的资源文件,一直找到最顶层包。若是尚未找到对应的key,最后会从全局资源文件中寻找。
8.5 JSP中访问指定资源文件
- 因为JSP页面所访问资源文件不是由JSP页面自己决定看,要么默认访问全局资源文件,要么访问其跳转来的Action中的资源文件。而这将引起项目在进行分工协做时的麻烦。Struts2的<s:i18n/>标签可让JSP访问指定资源文件。
- <s:i18n/>具备一个name属性,用于指定所要访问资源文件的路径以及基本名。
9 文件上传
- Struts2是经过拦截器实现文件上传的,而默认拦截器中包含了文件上传拦截器,故表单经过Struts2可直接将文件上传。其底层是经过apache的commons-fileupload完成的。


- 若要实现文件上传功能,表单的enctype属性值与method属性值必需要以下设置:
9.1 上传单个文件
-
举例:fileUploadSingle
一、新建index.jsp:
<form action="test/upload.action" method="post" enctype="multipart/form-data">
文件:<input type="file" name="img"/><br/>
<input type="submit" value="上传"/>
</form>
二、新建Action类
package com.eason.struts.fileupload;
import java.io.File;
import java.io.IOException;
import org.apache.commons.io.FileUtils;
import org.apache.struts2.ServletActionContext;
public class UploadAction {
private File img;
private String imgFileName;
public File getImg() {
return img;
}
public void setImg(File img) {
this.img = img;
}
public String getImgFileName() {
return imgFileName;
}
public void setImgFileName(String imgFileName) {
this.imgFileName = imgFileName;
}
public String execute() throws IOException {
if(img != null) {
String path = ServletActionContext.getServletContext().getRealPath("/images");
File file = new File(path, imgFileName);
FileUtils.copyFile(img, file);
return "success";
}
return "fail";
}
}
三、struts.xml中的配置:
<package name="demo" namespace="/test" extends="struts-default">
<action name="upload" class="com.eason.struts.fileupload.UploadAction">
<result>/success.jsp</result>
<result name="fail">/fail.jsp</result>
</action>
</package>
四、定义success.jsp和fail.jsp:
<body>
上传成功!
</body>
<body>
上传失败!
</body>
- <input type="file" name="img"/>中的name属性值为img,因此Action类中必须定义File img;和String imgFileName;两属性用以接收文件和文件名。
- 注意,在Action中想经过获取文件大小来控制文件上传是不行的。由于文件上传拦截器是在Action以前执行的,即执行到Action时,文件上传工做已经完成。即便超过限制大小,抛出异常,也是已经抛出异常后才执行到Action的。
9.2 上传多个文件
- 与上传单个文件相比较,发生了以下几个变化:
一、提交表单中出现多个文件上传栏,这多个的name属性名必须彻底相同。
二、Action中文件再也不为File类型,而是File类型的数组或者是List。固然,文件名也为相应的数组或者是List。
三、Action方法须要遍历这些数组来上传这些文件。
- 举例:fileuploadMultiple
-
拷贝fileuploadSingle,只须要修改上传页面以及Action便可。
一、修改上传页面:
<form action="test/upload.action" method="post" enctype="multipart/form-data">
文件1:<input type="file" name="img"/><br/>
文件2:<input type="file" name="img"/><br/>
文件3:<input type="file" name="img"/><br/>
<input type="submit" value="上传"/>
</form>
二、修改 Action:
package com.eason.struts.fileupload;
import java.io.File;
import java.io.IOException;
import org.apache.commons.io.FileUtils;
import org.apache.struts2.ServletActionContext;
public class UploadAction {
private File[] imgs;
private String[] imgsFileName;
public File[] getImgs() {
return imgs;
}
public void setImgs(File[] imgs) {
this.imgs = imgs;
}
public String[] getImgsFileName() {
return imgsFileName;
}
public void setImgsFileName(String[] imgsFileName) {
this.imgsFileName = imgsFileName;
}
public String execute() throws IOException {
if(imgs != null) {
for(int i = 0; i < imgs.length; i++) {
String path = ServletActionContext.getServletContext().getRealPath("/images");
File file = new File(path, imgsFileName[i]);
FileUtils.copyFile(imgs[i], file);
}
return "success";
}
return "fail";
}
}
9.3 设置上传文件大小的最高限
- Struts2默认上传文件的大小是不能超过2M的。若要想上传大于2M的内容,则须要Struts.xml中增长对上传最大值的常量设置。这是当前系统的上传文件大小的最高限。
9.4 限制上传文件的扩展名
- 查看文件上传拦截器FileUploadInterceptor源码,能够看到其有一个allowedExtensions属性,该属性可用于限制上传文件的扩展名。

- 查看setAllowedExtendsions()方法的源码,其调用了方法commaDelimitedStringToSet()即逗号分隔字符串到Set集合方法,用于解析出使用逗号分隔的多个扩展名。
- allowedExtensions属性的用法以下所示:
10 文件下载
- 服务端向客户端浏览器发送文件时,若是是浏览器支持的文件类型,通常会默认使用浏览器打开,好比txt,jpg等,会直接在浏览器中显示;若是须要用户以附件的形式保存,则称之为文件下载。
- 若是须要向浏览器提供文件下载功能,则须要设置HTTP响应头的Content-Disposition属性,即内容配置属性值为attachment(附件)。
- Action类中须要提供两个属性,一个为文件输入流,用于指定服务器向客户端所提供下载的文件资源;一个为文件名,即用户要下载的资源文件名。配置文件中Action的result类型,即type属性应该设置为stream。
-
举例:download
一、在页面提供文件下载连接,即用户在浏览器上提交文件下载请求的位置:
<body>
<a rel="nofollow" href="test/download.action">美图下载</a>
</body>
二、定义Aciton类:
package com.eason.struts2.action;
import java.io.InputStream;
import org.apache.struts2.ServletActionContext;
public class DownloadAction {
//服务器本地提供下载资源的输入流
private InputStream is;
//为用户所提供的下载资源的文件名
private String fileName;
public void setIs(InputStream is) {
this.is = is;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public InputStream getIs() {
return is;
}
public String getFileName() {
return fileName;
}
public String execute() {
//指定要下载的文件名
fileName = "timg.jpg";
is = ServletActionContext.getServletContext().getResourceAsStream("/images/" + fileName);
//在输入流后修改fileName的值,即为用户下载到客户端后的文件名
fileName = "beauty.jpg";
return "success";
}
}
三、设 置 action 的 视 图 类 型 为 stream , 并 为 其 设 置 两 个 参 数 inputName 与
contentDisposition:
<package name="demo" namespace="/test" extends="struts-default">
<action name="download" class="com.eason.struts2.action.DownloadAction">
<result type="stream">
<param name="inputName">is</param>
<param name="contentDisposition">
attachment;filename=${fileName}
</param>
</result>
</action>
</package>
- 查看struts-default.xml,能够看到Stream返回类型对应的类为 StreamResult。

- 查看StreamResult源码,相关属性以下:

- DEFAULT_PARAM:配置文件中<param/>标签name属性的默认值。
- contentType:文件的MIME类型,如image/jpeg。不管下载的文件是什么类型,客户端看到的均会是指定类型文件的扩展名,如均是jpg。
- contentLength:对服务器提供的下载文件大小的上限的限制。单位字节。当下载文件大于此大小时,下载仍会继承。可是,下载不全,只要规定的大小。
- contentDisposition:下载文件的显示形式。默认为“inline”,即在浏览器上直接显示。若要以附件形式展现给客户端,则其值须要设置为attachment,并经过filename指定其下载后的名称。
- inputName:输入流的名称,默认为inputStream。若Action中的inputStream对象的名字为inputStream,此时,<param name=”inputName”>inputStream</param>省略不写也可。由于DEFAULT_PARAM指定了默认的param为inputName,而默认的InputStream对象又为inputStream。省略不写,则均会使用默认值。
- bufferSize:提供下载是可使用的缓存大小。
- allowCaching:提供下载服务时是否容许使用缓存。
四、此时程序部署后就能够完成向客户端提供下载功能,可是有个问题,当指定文件下载到客户端的名称为中文时,浏览器下载会出现文件名称的乱码。解决方式以下: public String execute() throws Exception {
//指定要下载的文件名
fileName = "timg.jpg";
is = ServletActionContext.getServletContext().getResourceAsStream("/images/" + fileName);
//在输入流后修改fileName的值,即为用户下载到客户端后的文件名
fileName = "美女.jpg";
fileName = new String(fileName.getBytes("utf-8"), "ISO8859-1");
return "success";
}
- 乱码是如何产生的?为何使用new String(fileName.getBytes("utf-8"), "ISO8859-1")后,就解决乱码问题?
- 此时的乱码之因此会产生,是由于Http header报头要求其内容必须为ISO8859-1编码,而ISO8859-1编码不支持汉字。
- 使用以上方式之因此能够解决乱码问题,是由于:
一、fileName.getBytes("utf-8)的做用是,将fileName按照utf-8进行编码,并将编码结果存放到字节数组中。utf-8编码中文后为3个字节。
二、new String(fileName.getBytes("utf-8"), "ISO8859-1")的做用是,将编码后的3个字节解码为ISO8859-1串,即相似%3A56%59DC这样的串。而这样的串正是浏览器所须要的,Httpheader报头中数据的编码集为ISO8859-1。当数据传到浏览器端后,浏览器会自动将数据按照浏览器的字符集进行编码,若浏览器的为utf-8,则会正常显示汉字。 11 防止表单重复提交
- 在实际应用中,因为网速等缘由,用户在提交过表单后,可能很长时间内服务器未给出任何响应。此时,用户就可能会重复对表单进行提交。
- 或者是表单提交后,服务器也给出了响应,但用户在响应页面不断点击“刷新”按钮进行页面刷新。此时,用户也是在对表单进行重复提交。
11.1 令牌机制
- Struts2 中,使用令牌机制防止表单自动提交。
- 所谓令牌机制是指,当用户提交页面资源请求后,服务器会产生一个随机的字符串,该字符串即为令牌。并为该字符串保留两个副本:一个保留在服务器,一个随着响应返回给客户端浏览器。
- 当用户在页面中进行第一次提交时,会将副本字符串与服务器中的副本字符串进行比较。其比较结果必定是相同的,由于这是第一次提交。
- 当比较结果相同后,服务器会将其保留的副本数据修改。但此修改并不通知(影响)浏
览器端的原副本。
- 当用户在页面中进行重复提交时,在发出的请求中仍然携带有原副本字符串,服务器仍然会将该副本与本身的令牌字符串进行比较。但服务器端的令牌字符串已经发生改变,因此比较结果必定不一样。这就说明不是第一次提交该请求了,是重复提交了。
11.2 防止表单重复提交
一、使用<s:token />标签,该标签应放在<form/>表单中,以便在请求提交时,将令牌字符
串一块儿提交。
二、在定义 Action 时,要求 Action 必须继承 ActionSupport 类。以便在重复提交发生时会
返回”invalide.token”字符串。
三、Struts2 配置文件的<action/>中须要定义名称为”invalide.token”的视图。
四、Struts2 配置文件的<action/>中须要使用 token 拦截器。
11.3 防止重复提交的步骤
- 举例:token
一、在表单中加入<s:token/> <form action="test/login.action" method="post">
<s:token/>
用户:<input type="text" name="username"/><br/>
密码:<input type="password" name="password"/><br/>
<input type="submit" value="登陆">
</form>
-
当服务器发现用户提交的页面访问请求,所请求的页面中包含<s:token/>标签,则会生
成令牌字符串,并为令牌建立两个副本。一个放在服务器端,一个在给出的对用户页面请求响应的页面,以隐藏域的方式发送给客户端浏览器。

二、Action类要继承自ActionSupport类
package com.eason.struts.action;
import com.opensymphony.xwork2.ActionSupport;
public class LoginAction extends ActionSupport{
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String execute() {
return "success";
}
}
三、在action配置文件中加入token拦截器以及重复提交视图配置。注意,在加入token拦截器以前,不要忘记先要将核心拦截器栈加入,不然,Struts2的核心功能将没法使用。
<package name="demo" namespace="/test" extends="struts-default">
<action name="login" class="com.eason.struts.action.LoginAction">
<interceptor-ref name="token"></interceptor-ref>
<interceptor-ref name="defaultStack"></interceptor-ref>
<result>/success.jsp</result>
<result name="invalid.token">/message.jsp</result>
</action>
</package>
四、定义成功页面和重复提交提示页面。
<body>
提交成功!
</body>
<body>
发生了重复提交!
</body>