十、 拦截器
1. 拦截器在struts2的作用(引用轻量级JavaEE企业应用实战第四版)
a. 连接器把大部分核心控制器需要完成的工作按功能分开定义,每个拦截器完成一个功能。而这些拦截器可以自由选择,灵活组合,甚至不用任何拦截器,开发者需要使用哪些拦截器,只需要在struts.xml文件中进行配置即可。
b. struts2框架的绝大部分功能都是通过拦截器完成的。当StrutsPrepareAndExecuteFilter拦截到用户请求后,大量拦截器会对用户请求进行处理,然后调用用户开发的Action实例来处理请求。
c. struts2默认开启大量通用功能拦截器,只要继承了struts-default包即可起作用。
拦截器与Action关系如下:
2. 拦截器配置
a. 只需要为拦截器类指定一个拦截器名,配置如下
<interceptor/><interceptor name=”拦截器名” class=”拦截器实现类”/>
还可以通过<parm name=”参数名”>参数值</param>指定拦截器相应参数。
b. 拦截器栈,配置方法如下:
<interceptor-stack name=”拦截器栈名称”>
<interceptor-ref name=”拦截器一”>
<interceptor-ref name=”拦截器二”>
<interceptor-ref name=”拦截器三”>
…
</interceptor-stack>
还可以在interceptor-ref中配置参数,同样利用
<parm name=”参数名”>参数值</param>
的方式。另外,每个拦截器栈还可以继续引用别的拦截器栈。
<interceptor-stack name=”拦截器栈一”>
<interceptor-ref name=”拦截器一”>
<interceptor-ref name=”拦截器二”>
…
</interceptor-stack>
<interceptor-stack name=”拦截器栈二”>
<interceptor-ref name=”拦截器三”>
<interceptor-ref name=”拦截器栈一”>
…
</interceptor-stack>
c. 配置时需要interceptors标签将配置信息包裹。如下:
<interceptors>
<interceptor name=”拦截器一” class=”拦截器一实现类”/>
<interceptor-stack name=”拦截器栈一”>
<interceptor-ref name=”拦截器一”>
…
</interceptor-stack>
</interceptors>
d. 配置默认拦截器:<default-interceptor-ref name=”拦截器名或拦截器栈名”>
e. 上面的配置必须在package标签先进行的
- 实现拦截器
struts2所定义的拦截器接口,Interceptor接口
public interface Interceptor extends Serializable {
//销毁方法,在拦截器实例被销毁前调用,一般用于关闭init打开的资源
void destroy();
//初始化方法,在执行interceptor前,系统回调此方法,只执行一次,一般用//于初始化资源
void init();
//执行相应的拦截操作
String intercept(ActionInvocation invocation) throws Exception;
}
- 使用示例:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.1//EN" "http://struts.apache.org/dtds/struts-2.1.dtd">
<struts>
<constant name="struts.configuration.xml.reload" value="true"></constant>
<package name="interceptor" extends="struts-default" namespace="/">
<interceptors>
<interceptor name="loginInterce" class="com.struts.interceptor.LogInterceptor"></interceptor>
</interceptors>
<action method="{1}" name="login_*" class="com.struts.review1.LoginAction">
<result name="success">/success.jsp</result>
<interceptor-ref name="loginInterce"></interceptor-ref>
</action>
</package>
</struts>
package com.review.domain;
public class Admin {
private String loginId;
private String pw;
public String getLoginId() {
return loginId;
}
public void setLoginId(String loginId) {
this.loginId = loginId;
}
public String getPw() {
return pw;
}
public void setPw(String pw) {
this.pw = pw;
}
@Override
public String toString() {
return "Admin [loginId=" + loginId + ", pw=" + pw + "]";
}
}
package com.struts.review1;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ModelDriven;
import com.review.domain.Admin;
public class LoginAction extends ActionSupport implements ModelDriven<Admin>{
private Admin model = new Admin();
public String login() {
System.out.println(model);
if(model.getLoginId().equals("FiShelly.") && model.getPw().equals("110210")){
System.out.println("======= login success =======");
}
return SUCCESS;
}
@Override
public Admin getModel() {
// TODO Auto-generated method stub
return model;
}
}
<form method="post" action="login_login.action">
loginId:<input type="text" name="loginId"/><br>
password:<input type="password" name="pw"/><br>
<input type="submit" value="submit"/>
</form>
部署运行会发现出现异常。
显然是空指针异常,通过控制台的错误提示,我们发现model对象的各个属性没被赋值,同时拦截器是被执行了,其中mode是通过模型驱动赋值的,而模型驱动又是依靠默认拦截器栈实现的,那么我们不妨推断下由于执行了自定义的拦截器,而导致默认拦截器栈没有执行。这样我们实验一下,将引用拦截器的代码注释掉。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.1//EN" "http://struts.apache.org/dtds/struts-2.1.dtd">
<struts>
<constant name="struts.configuration.xml.reload" value="true"></constant>
<package name="interceptor" extends="struts-default" namespace="/">
<interceptors>
<interceptor name="loginInterce" class="com.struts.interceptor.LogInterceptor"></interceptor>
</interceptors>
<action method="{1}" name="login_*" class="com.struts.review1.LoginAction">
<result name="success">/success.jsp</result>
<!-- <interceptor-ref name="loginInterce"></interceptor-ref>-->
</action>
</package>
</struts>
发现,运行成功了。这表示我们的推断是正确的,那么进一步,是什么原因导致默认拦截器栈没有被执行呢?首先,我们知道,由于基础了struts-default,而struts的默认拦截器栈又是在struts-default中定义的,因此我们定义的package应该是继承了struts的默认拦截器栈的,但是呢,由于我们自己配置了自定义的拦截器栈,导致struts先执行我们的自定义拦截器了,而没有去执行struts的默认拦截器栈,这样一来将导致没有拦截到http请求中参数信息,而导致空指针异常。那么接下来对代码的修改方式有2种。
第一种是直接在原有的<interceptor-ref>
上在写多一个<interceptor-ref name=”defaultStack”>
这样一来,struts就会先执行默认拦截器栈,在执行自定义的拦截器。
<action method="{1}" name="login_*" class="com.struts.review1.LoginAction">
<result name="success">/success.jsp</result>
<interceptor-ref name="defaultStack"></interceptor-ref>
<interceptor-ref name="loginInterce"></interceptor-ref>
</action>
第二种是自定义一个拦截器栈,在自定义的拦截器栈中引用struts2的默认拦截器栈。同时将包的默认拦截器栈换成我们自定义的拦截器栈。
<interceptors>
<interceptor name="loginIn" class="com.struts.interceptor.LogInterceptor"></interceptor>
<interceptor-stack name="privilegeStack">
<interceptor-ref name="defaultStack"></interceptor-ref>
<interceptor-ref name="loginIn"></interceptor-ref>
<!--
<interceptor-ref name="aa"></interceptor-ref>
-->
</interceptor-stack>
</interceptors>
<default-interceptor-ref name="privilegeStack"></default-interceptor-ref>
这样一来,程序就可以成功执行。如下
在这里顺便一提拦截器的执行顺序:通常认为,先配置的拦截器,会得到先执行的机会。但是实际上则是,在invocation.invoke()方法执行之前是顺序执行的,在其执行完之后是逆序的。其实也可以通过栈先进后出的思想来考虑,拦截器拦截,排第一个的先进栈执行,当到invocation.invoke()后则停住让排在后面的进栈执行,又遇到invocation.invoke()时停住,让后面的进栈执行,不断重复这个过程,直到最后一个拦截器进栈为止,当最后一个拦截器进栈执行完invocation.invoke()方法后,相应的则是依次出栈,出栈则是后进先出,这样一来就逆序执行了,但注意,这里可以这么理解,但实际上invocation.invoke()方法只会执行一次,而非多次的。
十一、 属性驱动与模型驱动
1. 上面说拦截器的是提到了模型驱动,因此在这里就来讲解下相关的知识。
首先是在Servlet中表单填写后封装成对象是比较麻烦的,因为需要重复使用request.getParameter这个方法,或者使用第三方框架来实现。但是在struts中则将这个过程变得简单了。因为struts2框架会帮我们做好封装的事情,前提是你得遵循struts定义好的规则。而struts2为我们提供了常用的两种封装属性的方式,一种是属性驱动,另一种则是模型驱动。通过这两种方式能够简单的将填写表单的内容封装成相应的对象。而实现这样方式的原理则是通过之前讲的拦截器来实现的。
2. 属性驱动
可以实现页面字符串与基本数据类型的转换。只需要在Action定义好相关需要封装的对象实例,提供set/get方法后即可,但是这里需要在页面表单的name属性中加入相关的前缀。
页面表单:
<form method="post" action="admin_add.action">
loginId:<input type="text" name="admin.loginId"/><br>
password:<input type="password" name="admin.pw"/><br>
radio:<input type="radio" name="flag" value="0"/>否
<input type="radio" name="admin.flag" value="0"/>是<br>
checked:<input type="checkbox" name="admin.hobby" value="aaa">aaa
<input type="checkbox" name="admin.hobby" value="bbb">bbb
<input type="checkbox" name="admin.hobby" value="ccc">ccc
<input type="checkbox" name="admin.hobby" value="ddd">ddd
<input type="checkbox" name="admin.hobby"value="eee">eee<br>
<input type="submit" value="submit"/>
</form>
Admin.java:
package com.review.domain;
import java.util.Arrays;
public class Admin {
private String loginId;
private String pw;
private String[] hobby;
private int flag;
public String getLoginId() {
return loginId;
}
public void setLoginId(String loginId) {
this.loginId = loginId;
}
public String getPw() {
return pw;
}
public void setPw(String pw) {
this.pw = pw;
}
public String[] getHobby() {
return hobby;
}
public void setHobby(String[] hobby) {
this.hobby = hobby;
}
public int getFlag() {
return flag;
}
public void setFlag(int flag) {
this.flag = flag;
}
@Override
public String toString() {
return "Admin [loginId=" + loginId + ", pw=" + pw + ", hobby="
+ Arrays.toString(hobby) + ", flag=" + flag + "]";
}
}
AdminAction.java
private Admin admin ;
public Admin getAdmin() {
return admin;
}
public void setAdmin(Admin admin) {
this.admin = admin;
}
public String add(){
System.out.println(admin);
return "success";
}
表单显示:
控制台输出:
3. 模型驱动
使用模型驱动,则必须实现ModelDriven接口,并实现getmodel方法,返回model对象即可。用这种方式则可以在表单中省下填写前缀的步骤。
AdminAction.java:
public class AdminAction extends ActionSupport implements ModelDriven<Admin> {
private Admin model = new Admin();
@Override
public Admin getModel() {
return model;
}
public String add(){
System.out.println(model);
return "success";
}
}
<form method="post" action="admin_add.action">
loginId:<input type="text" name="loginId"/><br>
password:<input type="password" name="pw"/><br>
radio:<input type="radio" name="flag" value="2"/>否<input type="radio" name="flag" value="1"/>是<br>
checked:<input type="checkbox" name="hobby" value="aaa">aaa
<input type="checkbox" name="hobby" value="bbb">bbb
<input type="checkbox" name="hobby" value="ccc">ccc
<input type="checkbox" name="hobby" value="ddd">ddd
<input type="checkbox" name="hobby" value="eee">eee<br>
</form>
结果与属性驱动也是一样就不发出来了
4. 使用哪种属性驱动还是模型驱动
其实并没有特别的硬性要求一定要使用哪种,因此可以根据具体的要求具体而定,不过一般来说要么只是用属性驱动,要么就只使用模型驱动。