Struts2拦截器是使用AOP实现的,主要是针对action对象进行拦截,可以在访问action的某个方法、字段之前或之后实施拦截。
可以为action配置多个拦截器,Struts2会将这一组拦截器按照一定顺序组织成一个拦截器栈。action可以直接引用某个拦截器栈来实现配置多个拦截器的目的。
对于继承struts_default的package中的action,都会默认引用name=defaultStack的拦截器栈(在struts_default中定义了Struts2提供的各种拦截器),action对于拦截器的调用顺序参考下图:
二、拦截器实现
Struts2的拦截器都实现了com.opensymphony.xwork2.interceptor.Interceptor接口:
public interface Interceptor extends Serializable {
//销毁方法
void destroy();
//初始化方法
void init();
//拦截方法
String intercept(ActionInvocation invocation) throws Exception;
}
我们要实现一个自定义的拦截器,就要实现Interceptor接口,并主要实现intercept()方法。例如下面,我们实现一个简单的认证拦截器:
public class AuthorityInterceptor implements Interceptor {
public void init() {
}
public void destroy() {
}
@Override
public String intercept(ActionInvocation invocation) throws Exception {
Object user = ActionContext.getContext().getSession().get("user");
if (user == null) {
System.out.println("未登录,请先登录!");
return "login";
}
return invocation.invoke();
}
}
在自定义的拦截器中的intercept()中,我们可以返回一个字符串Result,此时请求会终止并跳转到对应的result。
invocation是一个ActionInvocation的引用,ActionInvocation负责调用该请求的action及其一系列拦截器,当我们调用invocation.invoke()时,它会判断此时拦截器栈是否还有拦截器,是的话会调用下一个拦截器的拦截方法,否则会调用action对应的请求方法。
以上,我们就实现了一个自定义的拦截器。
要让这个自定义的拦截器工作,就需要在struts.xml配置文件中进行配置:
首先,要在package元素下,对拦截器进行声明:
<interceptors>
<interceptor name="authorityInterceptor" class="com.boya.struts2.interceptor.AuthorityInterceptor"></interceptor>
</interceptors>
然后,在action元素中引用这个拦截器就可以了,如:
<action name="user" class="com.boya.struts2.web.UserAction">
<interceptor-ref name="authorityInterceptor" />
<result>/success.jsp
</result>
</action>
但是,需要注意的是:
当在action元素中设置了<interceptor-ref name="authorityInterceptor" />,它会覆盖默认的defaultStack拦截器栈的引用。假设自定义的拦截器仅仅是一个日志拦截,那么这样配置,defaultStack中的拦截器都不会调用,比如最重要的参数拦截器ParametersInterceptor,调用action时就会出现问题。因此在对action添加拦截器引用时,通常这样配置:
<action name="user" class="com.boya.struts2.web.UserAction">
<interceptor-ref name="defaultStack" />
<interceptor-ref name="authorityInterceptor" />
<result>/success.jsp</result>
</action>
三、使用拦截器实现文件上传
Struts 2是使用commons-fileupload进行文件上传的,首先使用commons-fileupload将文件上传到临时目录中,然后Struts2拦截器将文件信息封装入action对象,然后以操作本地文件的方式完成指定目录的上传。
文件上传实现步骤:
1、页面设置文件上传表单
<s:form action="doUpload" method="POST" enctype="multipart/form-data">
<s:file name="upload" label="File"/>
<s:submit/>
</s:form>
2、编写文件上传Action
public class FileUploadAction extends ActionSupport {
private static final long serialVersionUID = 5156288255337069381L;
private String contentType; //上传文件的类型
private File upload; //上传文件
private String fileName; //上传文件的名称
private String caption; //提交的相关信息
public String input() throws Exception {
return SUCCESS;
}
public String upload() throws Exception {
String path = "upload\\";
InputStream in = new FileInputStream(upload);
String contextPath = ServletActionContext.getServletContext().getRealPath("/");
File dir = new File(contextPath);
if (!dir.exists()) {
dir.mkdir();
}
String filePath = contextPath + path + fileName;
OutputStream out = new FileOutputStream(filePath);
int bytesRead;
byte[] buffer = new byte[8192];
while ((bytesRead = in.read(buffer, 0, 8192)) != -1)
{
out.write(buffer, 0, bytesRead);
}
out.close();
in.close();
return SUCCESS;
}
public String getUploadFileName() {
return fileName;
}
public void setUploadFileName(String fileName) {
this.fileName = fileName;
}
public String getUploadContentType() {
return contentType;
}
public void setUploadContentType(String contentType) {
this.contentType = contentType;
}
public File getUpload() {
return upload;
}
public void setUpload(File upload) {
this.upload = upload;
}
public String getCaption() {
return caption;
}
public void setCaption(String caption) {
this.caption = caption;
}
}
注意:拦截器会将文件名称及文件的编码类型封装入uploadFileName和uploadContentType属性中,其中upload是对应页面中file标签的name名称,FileName和ContentType是固定后缀,因此getter和setter方法要对应这两个名称,否则无法对文件信息进行封装。
3、在struts.xml配置文件中,配置action请求即可
<action name="upload" class="com.boya.ssh.web.FileUploadAction" method="input">
<result>/upload.jsp</result>
</action>
<action name="doUpload" class="com.boya.ssh.web.FileUploadAction" method="upload">
<result name="input">/upload.jsp</result>
<result>/upload-success.jsp</result>
</action>
注意:
a、Struts2是使用拦截器来完成文件上传的,而因为action默认引用的defaultStack拦截器栈中已经包含了fileUpload拦截器,因此无需再添加文件上传拦截器的引用。
b、Struts2默认上传文件最大为2M,可以通过<constant name="struts.multipart.maxSize" value="20971520" /> 来修改最大上传文件的限制(修改为20M,单位为字节)
四、重复提交拦截器
在Struts2中防止重复提交机制也是通过拦截器实现的,它的原理是:
1、在页面中添加<s:token />标签:
它会在session中生成一个key为struts.token的随机字符串,
同时在页面添加对应的隐藏于标签,如:
<input type="hidden" name="struts.token.name" value="token" />
<input type="hidden" name="token" value="448CL36HMFSEORYB9X12ZHU8B10W87U9" />
2、提交表单时,同时将隐藏域进行提交,在token拦截器中对提交的token值和session中保存的token值进行比较:
如果相等,执行后续请求处理,同时将session中的token清空。
如果不等,则拦截请求。
它正是通过这种方式对重复的请求进行拦截,以防止重复提交。
Struts2提供了两种重复提交拦截器实现;
token:出现重复提交后跳转到指定的页面,需要配置<result name="invalid.token">/input</result>
tokenSession:重复提交时按钮无效,页面不会调整,第一次的请求完成后跳转到成功页面
对比这两种拦截机制,tokenSession适用性更强些。
实现步骤:
1、页面添加<s:token />,放在form表单内即可。
Struts1的token标签必须使用<html:form />,而Struts2已经没有这个限制了,普通的html标签仍然可以使用<s:token />来实现防止重复提交。
2、在struts.xml中添加拦截器引用,defaultStack拦截器栈中没有token拦截器,因此需要添加引用
<action name="saveUser" class="com.boya.ssh.web.UserAction" method="save">
<interceptor-ref name="defaultStack" />
<interceptor-ref name="token" />
<result name="invalid.token">/user-input</result>
<result>/user.jsp</result>
</action>
使用tokenSession拦截器则无需配置invalid.token:
<action name="saveUser" class="com.boya.ssh.web.UserAction" method="save">
<interceptor-ref name="defaultStack" />
<interceptor-ref name="tokenSession" />
<result>/user.jsp</result>
</action>
以上就是使用拦截器实现的防止重复提交问题。