Struts2
一、搭建环境
- 加入 jar 包: 复制 struts\apps\struts2-blank\WEB-INF\lib 下的所有 jar 包到当前 web 应用的 lib 目录下.
- 在 web.xml 文件中配置 struts2: 复制 struts\apps\struts2-blank1\WEB-INF\web.xml 文件中的过滤器的配置到当前 web 应用的 web.xml 文件中
- 在当前 web 应用的 classpath 下添加 struts2 的配置文件 struts.xml: 复制 struts1\apps\struts2-blank\WEB-INF\classes 下的 struts.xml 文件到当前 web 应用的 src 目录下.
二、访问 Web 资源
在 Action 中, 可以通过以下方式访问 web 的 HttpSession, HttpServletRequest, HttpServletResponse 等资源
1.与Servlet API解耦的访问方式 :
为了避免与 Servlet API 耦合在一起, 方便 Action 做单元测试, Struts2 对 HttpServletRequest, HttpSession 和 ServletContext 进行了封装, 构造了 3 个 Map 对象来替代这 3 个对象, 在 Action 中可以直接使用 HttpServletRequest, HttpServletSession, ServletContext 对应的 Map 对象来保存和读取数据.
public class TestContextAction extends ActionSupport {
private static final long serialVersionUID = 1L;
public String testContext(){
//ActionContext 是 Action 的上下文对象,可以从中获取到 Action 需要的一切信息
ActionContext actionContext = ActionContext.getContext();
//1.获取 application
Map<String, Object> applocationMap = actionContext.getApplication();
applocationMap.put("applicationKey", "applocationValue");
//2.获取 session
Map<String, Object> sessionMap = actionContext.getSession();
sessionMap.put("sessionKey", "sessionValue");
//3.获取 request
Map<String, Object> request = (Map<String, Object>) actionContext.get("request");
request.put("requestKey", "requestValue");
//4.获取请求参数对应的 Map
Map<String, Object> parametersMap = actionContext.getParameters();
String[] params = (String[]) parametersMap.get("name");
for (String string : params) {
System.out.print(string + " ");
}
return SUCCESS;
}
}
2.通过实现 Aware 接口访问 Web 资源 :
Action 类通过可以实现某些特定的接口, 让 Struts2 框架在运行时向 Action 实例注入 parameters, request, session 和 application 对应的 Map 对象:
public class TestAwareAction implements ApplicationAware,SessionAware,RequestAware
,ParameterAware{
private Map<String, Object> applicationMap;
private Map<String, String[]> parametersMap;
private Map<String, Object> requestMap;
private Map<String, Object> sessionMap;
public String execute(){
/*
* 通过实现对应接口来获取相应的域对象
*/
applicationMap.put("applicationKey","applicationValue");
requestMap.put("requestKey", "requestValue");
sessionMap.put("sessionKey", "sessionValue");
//获取参数
String[] params = parametersMap.get("name");
for (String string : params) {
System.out.println(string);
}
return "success";
}
@Override
public void setApplication(Map<String, Object> application) {
this.applicationMap = application;
}
@Override
public void setParameters(Map<String, String[]> parametersMap) {
this.parametersMap = parametersMap;
}
@Override
public void setRequest(Map<String, Object> requestMap) {
this.requestMap = requestMap;
}
@Override
public void setSession(Map<String, Object> sessionMap) {
this.sessionMap = sessionMap;
}
}
3.与 Servlet 耦合的访问方式 :
直接访问 Servlet API 将使 Action 与 Servlet 环境耦合在一起, 测试时需要有 Servlet 容器, 不便于对 Action 的单元测试.
直接获取 HttpServletRequest 对象:
ServletActionContext.getRequest()直接获取 HttpSession 对象
ServletActionContext.getRequest().getSession()直接获取 ServletContext 对象
ServletActionContext.getServletContext()通过实现 ServletRequestAware, ServletContextAware 等接口的方式
public class TestServletActionContextAction {
public String execute(){
//通过 ServletActionContext 来获取当前 Action 对象所需要的一切 Servlet API 相关的对象。
HttpServletRequest request = ServletActionContext.getRequest();
HttpServletResponse response = ServletActionContext.getResponse();
ServletContext servletContext = ServletActionContext.getServletContext();
HttpSession session = request.getSession();
return "success";
}
}
三、result
- 每个 action 方法都将返回一个 String 类型的值, Struts 将根据这个值来决定响应什么结果.
- 每个 action 声明都必须包含有数量足够多的 result 元素, 每个 result 元素分别对应着 action 方法的一个返回值.
- result 元素可以有下面两个属性
- name: 结果的名字, 必须与 Action 方法的返回值相匹配, 默认值为 success
- type: 响应结果的类型. 默认值为 dispatcher
结果类型:
1. dispatcher(默认):请求转发。
2. redirect:重定向。
3. redirect-action:重定向到另一个 Action。
<action name="TestRedirectAction">
<result type="redirectAction">
配置要重定向到的 Action 名称
<param name = "actionName">testAction</param>
配置要重定向到的 Action 的域名空间
<param name = "namespace">/test</param>
</result>
</action>
OR
<action name="TestRedirectAction">
<result type = "redirect">/test/testAction.do</result>
</action>
**4. chain:将Action的带着原来的状态请求转发到新的Action。
注意:不能通过 type = “dispatcher” 的方式转发到一个 Action.**
只能是:
<action name="TestRedirectAction">
<result type="chain">
配置要转发到的 Action 名称
<param name = "actionName">testAction</param>
配置要转发到的 Action 的域名空间
<param name = "namespace">/test</param>
</result>
</action>
四、通配符映射
- 一个 Web 应用可能有成百上千个 action 声明. 可以利用 struts 提供的通配符映射机制把多个彼此相似的映射关系简化为一个映射关系
- 通配符映射规则
- 若找到多个匹配, 没有通配符的那个将胜出
- 若指定的动作不存在, Struts 将会尝试把这个 URI 与任何一个包含着通配符 * 的动作名及进行匹配
- 被通配符匹配到的 URI 字符串的子串可以用 {1}, {2} 来引用. {1} 匹配第一个子串, {2} 匹配第二个子串…
- {0} 匹配整个 URI
- 若 Struts 找到的带有通配符的匹配不止一个, 则按先后顺序进行匹配
-
- 可以匹配零个或多个字符, 但不包括 / 字符. 如果想把 / 字符包括在内, 需要使用 **. 如果需要对某个字符进行转义, 需要使用 .
示例:
五、Struts2 标签
在使用标签之前,需要导入 Struts2 标签库 <%@ taglib prefix=”s” uri = “/struts-tags”%>
1.表单标签使用
<body>
<%
List<City> lits = new ArrayList<>();
lits.add(new City(1,"AA"));
lits.add(new City(2,"BB"));
lits.add(new City(3,"CC"));
lits.add(new City(4,"DD"));
request.setAttribute("cities", lits);
%>
<!--
表单标签:
1.可以重显:从栈顶对象开始匹配属性,并把匹配的属性值赋到对应的标签 value 中,若栈顶对象没有对应的属性,则依次向下找相对应的属性。
2.自动排版。
-->
<s:debug></s:debug>
<s:form action = "save.do">
<s:hidden name = "userId"></s:hidden>
<s:textfield name = "name" label = "UserName"></s:textfield>
<s:password name = "password" label = "PassWord" showPassword="true"></s:password>
<s:textarea name = "desc" label = "Desc"></s:textarea>
<!-- 需要指定 name 属性 ,value 表示默认值-->
<s:radio list="#{'男':'男','女':'女' }" label="Sex" name = "sex" ></s:radio>
<!-- 服务端用 boolean 来接收,选中为 true 否则为 false -->
<s:checkbox name="marry" label="Married"></s:checkbox>
<!--
1.从 request 中获取cities 对象 将cityId 作为键值,cityName 作为 value 值来构造
2.服务端需要使用集合类型来接收,以保证能够被正常的回显!
-->
<s:checkboxlist list="#request.cities"
listKey="cityId" listValue="cityName" label="City"
name = "city">
</s:checkboxlist>
<!-- list 也是一个 Map 只不过这里即作键,又作值 -->
<s:select list="{11,12,13,14,15,16,17,18}"
headerKey=""
headerValue="请选择"
name = "age"
label="Age">
<!--
s:optgroup 作为s:select 的子标签,用于显示更多的下拉框
注意:必须要有键值对,而不能使用一个集合即做键,又做值。
-->
<s:optgroup label = "21-30" list = "#{21:21,22:22,222:333 }"></s:optgroup>
<s:optgroup label = "31-40" list = "#{31:31,32:32,333:444 }"></s:optgroup>
</s:select>
<s:submit></s:submit>
</s:form>
</body>
2.其他标签使用
<body>
<%
Student stu = new Student("Tom",11);
request.setAttribute("stu", stu);
ValueStack valueStack = ActionContext.getContext().getValueStack();
valueStack.set("stu", stu);
%>
<s:debug></s:debug>
s:set,可以在page,request,session等域对象中保存一个值。
<br>
<s:set name = "name" value= 'Tom' scope="session"></s:set>
<br>
<%=session.getAttribute("name") %>
${sessScope.name }
<s:property value="#request.stu.age"/>
<br>
<s:if test="#request.stu.age > 18">
成年
</s:if>
<s:else>
未成年
</s:else>
<br>
<%
List<Student> stus = new ArrayList<>();
stus.add(new Student("EE",50));
stus.add(new Student("AA",10));
stus.add(new Student("CC",30));
stus.add(new Student("BB",20));
stus.add(new Student("DD",40));
request.setAttribute("stus",stus);
%>
//使用 '#' 进行 OGNL 解析
<table border="1" cellspacing="0">
<s:iterator value = "#request.stus">
<tr>
<td>${name}</td>
<td>${age }</td>
</tr>
</s:iterator>
</table>
<br>
<%
StudentComparator sc = new StudentComparator();
request.setAttribute("sc", sc);
%>
s:sort,可以对集合中的元素进行排序
<br><br>
<s:sort comparator="#request.sc" source="#request.stus" var = "stus2"></s:sort>
<s:iterator value = "#attr.stus2">
${name}:${age }<br>
</s:iterator>
<br><br>
s:date,对date对象进行排版
<br><br>
<%
Date date = new Date();
request.setAttribute("date", date);
%>
<s:date name="#request.date" format="yyyy/MM/dd HH:mm:ss" var="date2"/>
date2:${date2 }
</body>
3.回显情况
(1).如果需要回显的值可以在值栈中找到,那么表单会自动进行回显
(2).如果需要从域对象中获取属性值,此时需要进行强制性的 OGNL 解析:
<s:textfield name = "name" label="Name" value="%{#request.empolyee.name}">
</s:textfield>
需要使用 “%{}” 包起来,才能被 OGNL 解析。
六、Struts2 国际化
Struts2 国际化是建立在 Java 国际化基础上的:
- 为不同国家/语言提供对应的消息资源文件
- Struts2 框架会根据请求中包含的
- Locale 加载对应的资源文件
- 通过程序代码取得该资源文件中指定 key 对应的消息
1.配置国际化资源文件
2.加载资源文件的顺序
3.访问国际化消息
<body>
<s:debug></s:debug>
<s:form>
<!--
若使用 label 标签来获取资源文件中的 value 值,需要使用 %{getText('username')},
因为此时在值栈中有 .DefaultTextProvider 的一个实例,该对象中提供了访问国际化资源文件的 getText() 方法
同时需要使用 "%{}" 进行 OGNL 解析。
-->
<s:textfield name = "username" label="%{getText('username')}"></s:textfield>
<!-- key 的方式是直接从资源文件中获取 value 值 -->
<s:textfield name = "username" key="username"></s:textfield>
<s:password name = "password" key="password"></s:password>
<s:submit key="submit"></s:submit>
</s:form>
</body>
七、类型转换
从一个 HTML 表单到一个 Action 对象, 类型转换是从字符串到非字符串.
HTTP 没有 “类型” 的概念. 每一项表单输入只可能是一个字符串或一个字符串数组. 在服务器端, 必须把 String 转换为特定的数据类型.
类型转换错误消息的定制:
<body>
<!-- 类型转换错误消息的定制:
必须实现 ValidationAware 接口(也可以直接继承 ActionSupport)
1.覆盖默认的错误消息:
1).在对应的 Action 类所在的 包中新建 ActionClassName.properties
文件,ActionClassName 即为包含着输入字段的 Action 类名。
2).在属性文件中添加如下键值对:invalid.fieldvalue.fieldName=xxx
2.如果是 simple 主题,需要从值栈中的 fieldError 属性中获取,该属性的类型是 Map<String,List<String>>
键:字段(属性名),值:错误消息组成的 List.可以使用 EL 或 OGNL的方式来显示 错误消息。
1).${fieldError.age[0]}
2).还可以使用 s:fieldError 标签来显示,可以通过 fieldName 属性显示指定字段的错误。
-->
<s:form action="TestConversionError.do">
<s:textfield name = "age" label="Age"></s:textfield>
<s:submit value = "Submit"></s:submit>
</s:form>
</body>
八、数据验证
声明式验证
首先应该明白①哪些字段需要进行验证
,②使用什么验证规则
,③在验证失败时应该把什么样的出错消息发送到浏览器端
1.Struts2 内建的验证规则
- 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:复合属性验证器
2.验证程序的配置
(1).编写配置文件,basename-validation.xml,basename 要和Action的名称对应。
(2).编写验证规则,可参考帮助文档。
<field name="age">
<field-validator type="int">
<param name="min">18</param>
<param name="max">50</param>
<message>Age needs to be between ${min} and ${max}</message>
</field-validator>
</field>
(3).错误消息也可以国际化,利用 key。
<message key="error.int"></message>
(4).若验证失败,则转向 input 的那个 result ,所以需要配置 name = input 的 result。
3.Struts2 内建的验证程序
- required: 确保某给定字段的值不是空值 null
- requiredstring: 确保某给定字段的值既不是空值 null, 也不是空白.
- trim 参数. 默认为 true, 表示 struts 在验证该字段值之前先剔除前后空格.
- stringlength: 验证一个非空的字段值是不是有足够的长度.
- minLength: 相关字段的最小长度. 若没有给出这个参数, 该字段将没有最小长度限制
- maxLength:相关字段的最大长度. 若没有给出这个参数, 该字段将没有最大长度限制
- trim: 在验证之前是否去除前后空格
- date: 确保某给定日期字段的值落在一个给定的范围内
- max:相关字段的最大值. 若没给出这个参数, 该字段将没有最大值限制
- min:相关字段的最小值. 若没给出这个参数, 该字段将没有最小值限制
- email: 检查给定 String 值是否是一个合法的 email
- url: 检查给定 String 值是否是一个合法的 url
- regex: 检查某给定字段的值是否与一个给定的正则表达式模式相匹配.
- expresssion*: 用来匹配的正则表达式
- caseSensitive: 是否区分字母的大小写. 默认为 true
- trim: 是否去除前后空格. 默认为 true
- int: 检查给定整数字段值是否在某一个范围内
- min: 相关字段的最小值. 若没给出这个参数, 该字段将没有最小值限制
- max: 相关字段的最大值. 若没给出这个参数, 该字段将没有最大值限制
- conversion: 检查对给定 Action 属性进行的类型转换是否会导致一个转换错误. 该验证程序还可以在默认的类型转换消息的基础上添加一条自定义的消息
- expression 和 fieldexpression: 用来验证给定字段是否满足一个 OGNL 表达式.
- 前者是一个非字段验证程序, 后者是一个字段验证程序.
- 前者在验证失败时将生成一个 action 错误, 而后者在验证失败时会生成一个字段错误
- expression*: 用来进行验证的 OGNL 表达式
4.短路验证
<field name="age">
<!-- 设置短路验证:若当前验证没有通过,就不在进行下面的验证 -->
<field-validator type="conversion" short-circuit="true">
<message>Conversion Error Occurrec</message>
</field-validator>
<field-validator type="int">
<param name="min">18</param>
<param name="max">50</param>
<!-- Age needs to be between ${min} and ${max} -->
<message key="error.int"></message>
</field-validator>
</field>
5.非字段验证
<validator type="expression">
<param name="expression"><![CDATA[password1 == password2]]></param>
<message>两次密码不一致</message>
</validator>
6.错误消息的重用性
不同的字段使用相同的验证规则,可以使用相同的相应消息,只需要将错误消息的 key 值设置一样。
error.int=${getText(fieldName)} \u5927\u4E8E ${min} \u5C0F\u4E8E ${max}
age=\u5E74\u9F84
count=\u6570\u91CF
7.显示错误消息
(1).如果使用的是非 simple 主题,则可以自动显示。
(2).使用simple主题可以使用El表达式从值栈中获取 ${fieldErrors.age[0] };或者使用标签,
(3).对于非字段验证,错误消息放在 值栈的 actionErrors 中,可通过 EL 表达式获取。
<body>
<s:debug/>
<s:form action = "TestValidateAction1.do" theme="simple" >
Age:<s:textfield name="age" label="Age"></s:textfield>
<font color="red">${fieldErrors.age[0] }</font>
<br><br>
<!--<s:fielderror name="age"></s:fielderror>!-->
Password1:<s:password name = "password1" label="Password1"></s:password>
<br>
Password2:<s:password name = "password2" label="Password2"></s:password>
<font color="red">${actionErrors }</font><br>
Count:<s:textfield name="count" label="Count"></s:textfield>
<font color="red">${fieldErrors.count[0] }</font>
<br><br>
<s:submit value="Submit"></s:submit>
</s:form>
</body>
九、文件上传和下载
1.文件上传
(1).表单方面:表单的 enctype 属性值为 multipart/form-data.
(2).Action方面:在 Action 中设置file, fileFileName, fileContentType 三个属性并提供 get,set 方面,就可以获取到上传的文件;需要注意的是 这里的 file 必须和表单里面的文件框的 name 属性值一样,如果在表单中 name=’file1’ 那么在 Action 中需要这样设置 file1, file1FileName, fileContentType1.
(3).如果是上传的多个文件,将上传三个属性设置为集合,就可以获取到上传文件的集合对象。
表单中:
Action 中:
JSP代码:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix = "s" uri="/struts-tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<s:form action = "upload.do" method="post" enctype = "multipart/form-data" theme="simple">
File1:<s:file name = "file"></s:file><br>
Desc1:<s:textfield name = "desc[0]"></s:textfield>
<br><br>
File1:<s:file name = "file"></s:file><br>
Desc1:<s:textfield name = "desc[1]"></s:textfield>
<br><br>
File1:<s:file name = "file"></s:file><br>
Desc1:<s:textfield name = "desc[2]"></s:textfield>
<br><br>
<s:submit></s:submit>
</s:form>
</body>
</html>
Action代码
import java.io.File;
import java.io.IOException;
import java.util.List;
import javax.servlet.ServletContext;
import org.apache.commons.io.FileUtils;
import org.apache.struts2.ServletActionContext;
import com.opensymphony.xwork2.ActionSupport;
public class UploadAction extends ActionSupport {
private static final long serialVersionUID = 1L;
private List<File> file;
private List<String> fileContentType;
private List<String> fileFileName;
private List<String> desc;
public List<File> getFile() {
return file;
}
public void setFile(List<File> file) {
this.file = file;
}
public List<String> getFileContentType() {
return fileContentType;
}
public void setFileContentType(List<String> fileContentType) {
this.fileContentType = fileContentType;
}
public List<String> getFileFileName() {
return fileFileName;
}
public void setFileFileName(List<String> fileFileName) {
this.fileFileName = fileFileName;
}
public List<String> getDesc() {
return desc;
}
public void setDesc(List<String> desc) {
this.desc = desc;
}
public String execute() throws Exception{
String path = "d:\\Files";
for(int i = 0;i < file.size();i++){
File newFile = new File(path, fileFileName.get(i));
FileUtils.copyFile(file.get(i), newFile);
}
return SUCCESS;
}
}
2.文件下载
通过 result 的子元素 stream 可以实现文件的下载。
stream 内部参数及作用
Struts.xml中的配置
<action name="Download" class = "cn.edu.pzhu.cg.download.DownloadAction">
<!-- 服务端带下载的文件路径 -->
<param name="filePath">/Files</param>
<!-- 处理文件下载时,一定要将返回result的类型设置为stream -->
<result name = "success" type = "stream">
<!-- 指定下载文件的类型,全部的二进制文件 -->
<param name="contentType">application/octet-stream</param>
<!-- 指定有当前Action 中的哪个方法返回数据流 -->
<param name="inputName">inputStream</param>
<!-- 指定文件下载时,浏览器处理文件的方式 -->
<param name="contentDisposition">attachment;filename="${filename}"</param>
<!-- 设置缓冲区的大小 -->
<param name="bufferSize">2048</param>
</result>
</action>
Action代码
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import javax.servlet.ServletContext;
import org.apache.struts2.ServletActionContext;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
import com.sun.xml.internal.ws.api.policy.PolicyResolver.ServerContext;
public class DownloadAction extends ActionSupport {
private static final long serialVersionUID = 1L;
private String filename;
private String filePath;
private InputStream inputStream;
public String getFilename() {
return filename;
}
public void setFilename(String filename) {
this.filename = filename;
}
public InputStream getInputStream() {
return inputStream;
}
public void setInputStream(InputStream inputStream) {
this.inputStream = inputStream;
}
public String getFilePath() {
return filePath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
@Override
public String execute() throws Exception {
ServletContext servletContext = ServletActionContext.getServletContext();
//获取相对路径 filePath 在硬盘中的绝对路径,
//如果 filePath 已经是绝对路径,则不需要此步,直接从 filePath 中获取文件。
String path = servletContext.getRealPath(filePath);
System.out.println("path:" + path);
inputStream = new FileInputStream(new File(path, filename));
return SUCCESS;
}
}
JSP代码
JSP 中只需通过超链接拼接文件名即可实现下载.
<body>
<a href="Download.do?filename=1113154.jpg">Download</a>
<br>
<a href="Download.do?filename=test.txt">download</a>
</body>
十、防止表单重复提交
使用Struts 2标签中的token标签和Token、Token Session拦截器可以防止表单的重复提交。
原理:在用户要提交的表单中,加入一个标签,这样,当浏览器第一次访问这个带有标签的页面时,在服务器中,解析标签的类(TokenTag.class),会生成一个随机的字符串(这个字符串,查看网页的源代码可以看到),并且发送给客户端的浏览器,同时,在服务器中,会把这个随机字符串保存到用户的session对象中。当第一次提交表单时,在服务器中,会比较客户端和服务器中分别保存的这个随机字符串,因为是第一次提交,所以这两个字符串相等,然后进行正常的业务处理。第一次提交后,在服务器中的session中保存的这个随机字符串,会改变为其他的随机值,注意,这是很重要的一步!此时,地址栏停留在处理用户提交数据的Action中,客户端中保存的随机字符串没有改变,若是刷新页面,即重复提交,服务器再进行两个字符串的比较,不相等,不再提交,这样就会防止表单重复提交了。
1.使用token拦截器
struts.xml 配置:
<action name="test" class="struts2.action.AvoidAction">
<interceptor-ref name="token"></interceptor-ref>
<interceptor-ref name="defaultStack"></interceptor-ref>
<result name="invalid.token">/error.jsp</result>
<result name="input">/input.jsp</result>
<result name="success">/success.jsp</result>
</action>
需要在动作的声明中,为动作添加token拦截器,因为token拦截器不在defaultStack拦截器栈中,注意,需要将拦截器放在拦截器栈的第一位,这是因为判断表单是否被重复提交的逻辑应该在表单处理前。
2.使用tokenSession拦截器
struts.xml 配置:
<action name="testSubmit" class = "cn.edu.pzhu.cg.action.TestAction">
<interceptor-ref name="tokenSession"></interceptor-ref>
<interceptor-ref name="defaultStack"></interceptor-ref>
<result name = "success">/success.jsp</result>
</action>
使用 tokenSession 拦截器不同的是: 在发生重复提交时 token 拦截器需要配置 name=”invalid.token” 的 result;而 tokenSession 不需要配置,当发生重复提交时,直接不会执行后序拦截器。
JSP:
<body>
<s:form action = "testSubmit.do">
<s:token></s:token>
<s:textfield name = "username" label="UserName"></s:textfield>
<s:submit></s:submit>
</s:form>
</body>
需要在表单内加入 标签