6.3 Struts 2自定义拦截器
尽管Struts 2提供了许多的拦截器,这些拦截器也能完成Struts 2的大部分功能。但是如果需要扩张Web应用还是需要通过自定义拦截器来实现。下面将介绍如何定义拦截器类以及如何配置和使用拦截器与默认拦截器的意义。
6.3.1 定义拦截器类
前面介绍了动态代理模式以及拦截器实现原理,现在开始本章的重头戏—自定义拦截器。
下面来看如何定义自己的拦截器。首先找到com.opensymphony.xwork2.ActionInvocation包下的Interceptor类,严格来说它是一个接口。打开该文件,源代码如下所示。
package com.opensymphony.xwork2.interceptor; import com.opensymphony.xwork2.ActionInvocation; import java.io.Serializable; public interface Interceptor extends Serializable { void destroy(); void init(); String intercept(ActionInvocation invocation) throws Exception; }
Interceptor接口中就3个方法,分别是destroy()方法、init()方法和intercept()方法。其中destroy()方法和init()方法与JSP中的过滤器类似,都会在初始化和销毁时调用该方法。intercept()方法是定义拦截器中最重要的一个方法。
❑ init()方法:在拦截器被初始化之后,系统将自动调用该方法。该方法只执行一次,一般在该方法中存放一些一次性资源,如存放数据库资源等。
❑ destroy()方法:在拦截器实例被销毁之前,系统将自动调用该方法。该方法和init()方法对应,一般用来销毁在init()方法中打开的资源。
❑ intercept()方法:该方法是定义拦截器中最重要的一个方法。在该方法中用户可以定义自己的拦截器动作。比如,可以在Action执行前加入自定义的方法,同样也可以在Action执行后加入自定义的方法。该方法中的ActionInvocation参数包含被拦截Action的引用。通过调用该参数的invoke()方法,系统将跳转到下一个拦截器(如果定义了多个拦截器)或者跳转到Action。
下面来定义一个自己的拦截器。首先找到以前的登录示例,将在那个示例上加上自定义的拦截器。新建自定义拦截器类MyInterceptor,代码如下所示。
package net.hncu.interceptor; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.interceptor.Interceptor; public class MyInterceptor implements Interceptor{ //销毁时调用方法 public void destroy() { System.out.println("destroy方法执行"); } //初始化时调用方法 public void init() { System.out.println("init方法执行"); } //实现拦截的方法 public String intercept(ActionInvocation invocation) throws Exception { //拦截器执行时首先打印该语句 System.out.println("拦截前操作"); //调用下一个拦截器或者直接调用Action的execute方法 String result = invocation.invoke(); //拦截器执行后打印该语句 System.out.println("拦截后操作"); return result; } }
在Struts 2中自定义拦截器类就这么简单。不过还需要配置这个拦截器并使用该拦截器,这个拦截器才能真正起作用。
6.3.2 另一种定义拦截器方式
前面是通过实现Interceptor接口来实现自定义拦截器类的。因为是实现接口,所以接口中的方法都必须实现。如果不需要复写其中的init()方法和destroy()方法,也不得不实现该方法,这样实现起来就比较麻烦。
下面再来看AbstractInterceptor类。该类是一个抽象类并可实现Interceptor接口,代码如下所示。
package com.opensymphony.xwork2.interceptor; import com.opensymphony.xwork2.ActionInvocation; public abstract class AbstractInterceptor implements Interceptor { public void init() { } public void destroy() { } public abstract String intercept(ActionInvocation invocation) throws Exception; }
在AbstractInterceptor类中实现了init()方法和destroy()方法,这样如果在不需要复写init()方法和destroy()方法时就可以继承该抽象方法了,代码如下所示。
package net.hncu.interceptor; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.interceptor.AbstractInterceptor; public class MyInterceptor2 extends AbstractInterceptor{ //实现拦截的方法 public String intercept(ActionInvocation invocation) throws Exception { //拦截器执行时首先打印该语句 System.out.println("拦截前操作"); //调用下一个拦截器或者直接调用Action的execute方法 String result = invocation.invoke(); System.out.println("拦截后操作"); //拦截器执行后打印该语句 return result; } }
由上面的介绍可知通过继承AbstractInterceptor类来定义拦截器类变得越来越简单,越来越轻松了。
6.3.3 配置拦截器
现在只自定义了拦截器类,但是拦截器还是不能使用,必须要配置该拦截器。
配置拦截器非常简单,只需在“struts.xml”文件中通过interceptor元素指定该拦截器的名称和实现类,代码如下所示。
<interceptors> <! -- 配置拦截器,并制定该拦截器实现类 --> <interceptor name="拦截器名称" class="拦截器实现类 "></interceptor> </interceptors>
如果需要为拦截器传递参数,则可以在interceptor元素中添加param子元素,并指定参数名和参数值,代码如下所示。
<interceptors> <! -- 配置拦截器,并制定该拦截器实现类 --> <interceptor name="拦截器名称" class="拦截器实现类 "> <param name="参数名">参数值</param> </interceptor> </interceptors>
如果要配置多个拦截器,则可以在interceptors元素中定义多个interceptor元素,代码如下所示。
<interceptors> <! -- 配置拦截器,并制定该拦截器实现类 --> <interceptor name="拦截器名称" class="拦截器实现类 "> <param name="参数名">参数值</param> </interceptor> <! -- 配置拦截器,并制定该拦截器实现类 --> <interceptor name="拦截器名称" class="拦截器实现类 "> <param name="参数名">参数值</param> </interceptor> <! -- 配置拦截器,并制定该拦截器实现类 --> <interceptor name="拦截器名称" class="拦截器实现类 "> <param name="参数名">参数值</param> </interceptor> <! -- 配置拦截器,并制定该拦截器实现类 --> <interceptor name="拦截器名称" class="拦截器实现类 "> <param name="参数名">参数值</param> </interceptor> </interceptors>
还可以把多个拦截器组合起来组成一个拦截器栈。使用interceptor-stack元素来配置一个拦截器,并使用interceptor-ref元素来定义拦截器引用。这样这个拦截器栈中就包含这多个拦截器。拦截器栈可以看成是一个大的拦截器,其使用方法和拦截器完全相同,代码如下所示。
<interceptors> <! -- 配置拦截器,并制定该拦截器实现类 --> <interceptor name="拦截器一" class="拦截器实现类 "> <param name="参数名">参数值</param> </interceptor> <! -- 配置拦截器,并制定该拦截器实现类 --> <interceptor name="拦截器二" class="拦截器实现类 "> <param name="参数名">参数值</param> </interceptor> <! -配置拦截器栈 --> <interceptor-stack name="拦截器栈"> <interceptor-ref name="拦截器一"/> <interceptor-ref name="拦截器二"/> </interceptor-stack> </interceptors>
可以在拦截器中引用拦截器时,为拦截器指定参数。这种定义参数的方式是在拦截器真正起作用的时候才会动态地调用该参数值,代码如下所示。
<interceptors> <! -配置拦截器栈 --> <interceptor-stack name="拦截器栈"> <! -为拦截器定义参数--> < interceptor-ref name="拦截器一"> <param name="参数名">参数值</param> </interceptor> <! -为拦截器定义参数--> < interceptor-ref name="拦截器二"> <param name="参数名">参数值</param> </interceptor> </interceptor-stack> </interceptors>
因为拦截器栈可以看作一个的拦截器,所以在拦截器栈中可以再引入拦截器栈,代码如下所示。
<! -配置拦截器栈 --> <interceptor-stack name="拦截器栈一"> <interceptor-ref name="拦截器一"/> <interceptor-ref name="拦截器二"/> </interceptor-stack> <interceptor-stack name="拦截器栈二"> <interceptor-ref name="拦截器三"/> <interceptor-ref name="拦截器四"/> </interceptor-stack> <interceptor-stack name="拦截器栈三"> <interceptor-ref name="拦截器栈一"/> <interceptor-ref name="拦截器栈二"/> </interceptor-stack>
前面介绍了那么多配置拦截器的方法,现在来配置前面定义的拦截器类MyInterceptor,代码如下所示。
<! -- struts为配置文件根元素--> <struts> <! -- Action必须放在指定的包名空间中--> <package name="struts2" extends="struts-default"> <! -- 配置所有的拦截器 --> <interceptors> <! -- 配置myInter拦截器,并制定该拦截器实现类 --> <interceptor name="myInter" class="net.hncu.interceptor.MyInterceptor"></interceptor> </interceptors> <! -- 定义login的Action,其实现类为net.hncu.struts2.action.LoginAction--> <action name="login" class="net.hncu.struts2.action.LoginAction"> <! -- 定义处理结果与视图资源之间的关系--> <result name="success">/login_success.jsp</result> <result name="error">/login_failure.jsp</result> <result name="input">login.jsp</result> </action> </package> <! -- 指定资源文件baseName为messageResource --> <constant name="struts.custom.i18n.resources" value="messageResource"></constant> </struts>
6.3.4 使用拦截器
现在已经配置好了拦截器,是不是就可以使用了呢?配置了还是不可以使用,因为Action还不知道要使用该拦截器,所以还必须在Action元素中通过添加interceptor-ref元素来指定使用哪个拦截器,代码如下所示。
<! -- struts为配置文件根元素--> <struts> <! -- Action必须放在指定的包名空间中--> <package name="struts2" extends="struts-default"> <! -- 配置所有的拦截器 --> <interceptors> <! -- 配置myInter拦截器,并制定该拦截器实现类 --> <interceptor name="myInter" class="net.hncu.interceptor.MyInterceptor"></interceptor> </interceptors> <! -- 定义login的Action,其实现类为net.hncu.struts2.action.LoginAction--> <action name="login" class="net.hncu.struts2.action.LoginAction"> <! -- 定义处理结果与视图资源之间的关系--> <result name="success">/login_success.jsp</result> <result name="error">/login_failure.jsp</result> <result name="input">login.jsp</result> <! -- 使用myInter拦截器 --> <interceptor-ref name="myInter"></interceptor-ref> </action> </package> <! -- 指定资源文件baseName为messageResource --> <constant name="struts.custom.i18n.resources" value="messageResource"></constant> </struts>
通过这一系列的配置和使用,自定义的拦截器才真正地起作用。现在部署好登录项目,然后重启Tomcat服务器。这时在Tomcat服务器控制台上看到输出了“nit方法执行”,如图6.3所示。
因为Struts 2会自动地初始化在“struts.xml”中配置的需要使用的拦截器。打开登录页面如图6.4所示,不输入任何用户信息直接单击“登录”按钮进行登录。
这时在控制台中输出了在拦截器中定义的两个拦截方法,如图6.5所示。
当用户单击“登录”按钮,就相当于向该Action发出请求。而拦截器会自动拦截该Action的execute方法,从而执行拦截器中定义的方法。
但这时又发现一个问题,以前的输入校验功能怎么没了,页面直接跳转到了登录失败页,如图6.6所示。
图6.3 Tomcat服务器控制台输出显示
图6.4 登录页面
图6.5 Tomcat服务器控制台输出显示
图6.6 登录失败页面
6.3.5 默认拦截器
为什么配置了自定义的拦截器,输入校验就不起作用了呢?难道输入校验和自定义的拦截器有冲突?其实不是这样的,因为Struts 2中的类型转换、输入校验和文件上传等操作都是通过调用特性的拦截器来完成的。拦截器是Struts 2的核心部分,但是在“struts.xml”文件中根本就没使用这些拦截器,它们怎么会执行呢?
因为在配置Action时,将该Action放到了struts 2包中,而这个包继承了struts-default。重点在于在struts-default中配置了默认的拦截器栈defaultStack,在该拦截器栈中定义了大量的拦截器。struts 2这个包继承struts-default,就相当于同样配置了默认的拦截器栈defaultStack,而可以不用手动配置和使用这些拦截器。既然是这样,那为什么现在这些拦截器不起作用了呢?
如果该包中的Action没有添加使用拦截器,则默认的拦截器将会起作用。但是当一旦为该包中的Action添加使用了某个拦截器,那么这个默认的拦截器就不会再起作用了。这时就要求手动地添加该默认拦截器的使用,修改代码如下所示。
<! -- 定义login的Action,其实现类为net.hncu.struts2.action.LoginAction--> <action name="login" class="net.hncu.struts2.action.LoginAction"> <! -- 定义处理结果与视图资源之间的关系--> <result name="success">/login_success.jsp</result> <result name="error">/login_failure.jsp</result> <result name="input">login.jsp</result> <! -- 使用myInter拦截器 --> <interceptor-ref name="myInter"></interceptor-ref> <! -- 加入Struts 2的默认拦截器栈 --> <interceptor-ref name="defaultStack"></interceptor-ref> </action>
这时再打开登录页面,不输入任何用户信息直接单击“登录”按钮进行登录。从页面效果可以看出,输入校验器重新起作用了,如图6.7所示。
图6.7 登录提示错误信息