Struts 2 的拦截器(一)

 

一、 理解拦截器

拦截器可以动态地拦截发送到指定 Action 的请求,可以在 Action 执行的前后插入某些代码。

通过这种方式,就可以把多个 Action 中需要重复指定的代码提取出来,放在拦截器里定义,从而提供更好的代码重用性。

 

1.1 理解 DRY 规则

  在软件开发中,有一条非常重要的规则: don't repeat yourself ,就是所谓 DRY 规则,意思是不要书写重复代码

   在软件开发阶段,因为前期设计的不合理,或者缺乏预见性等原因,可能导致系统的多个地方需要使用相同的代码段。但对于许多刚开始从事软件开发的人而言,包含相同的代码段并不会给他们引起任何麻烦,一路复制粘帖。但如果有一天需要改程序中那些‘相同代码’时,有 1000 个地方,就要改 1000 个地方,工作量巨大。

   在这种情况下,一般会把‘相同的代码’定义成一个方法,直接调用这个方法,如果需要修改,只需要该一个方法就行了,这样大大降低了软件后期维护的复杂度。

 

1.2 拦截器的意义

 

 上面已经介绍了,把重复代码放在一个方法内(这里称为 方法1 ),这样程序要修改只要修改这个方法。但是这样也产生了另一个隐藏问题: 如果要弃用方法1 ,改用方法2 ,一样有 1000 个地方,就需要改 1000 个地方,造成这种结果是因为以硬编码方式调用了方法1

 

  Struts 2 的拦截器就可实现这种需求: 称某个实例是一个拦截器时,这是就其行为上而言: 从代码角度来看,拦截器就是一个类,这个类也包含方法,只是这个方法是个特殊方法,它会在目标方法调用之前 ”自动“ 执行

 

  对 直接调用方法 拦截器关键区别 在于 :  如果不使用拦截器,代码中需要显式通过代码来调用目标方法,如果使用拦截器,则该方法的调用是由系统完成的。

 

  通过以上对比,发现拦截器的优势 拦截器提供了更高层次的解耦,目标代码无需手动调用目标方法,而是由系统完成,从而将这种调用从代码层次上升到更高层次,从而提供了更高层次的解耦。

 

提示 : 计算机本身是无法 “自动” 调用拦截器方法,它甚至无法知道到底是应该调用拦截器方法一,还是调用拦截器方法二。对 Struts 2 框架而言,程序通过配置文件来管理拦截器,从而让系统明白: 何时应该调用哪个拦截器。

 

1.3 拦截器的实现原理

 

 下面用 JDK 动态代理为例来介绍如何调用拦截器方法

  因为 JDK 动态代理只能对实现了接口的实例来生成代理,因此要提供 Dog 接口

 

Dog

package js.a;

public interface Dog {
    // info方法声明
    public void info();

    // run方法声明
    public void run();
}

 DogImpl

package js.a;

public class DogImpl implements Dog {
    // info方法实现,仅仅打印一个字符串
    public void info() {
        System.out.println("我是一只猎狗");
    }

    // run方法实现,仅仅打印一个字符串
    public void run() {
        System.out.println("我奔跑迅速");
    }
}

 DogIntercepter

package js.a;

//定义系统拦截器类
public class DogIntercepter {
    // 第一个拦截器方法
    public void method1() {
        System.out.println("=====模拟通用方法一=====");
    }

    // 第二个拦截器方法
    public void method2() {
        System.out.println("=====模拟通用方法二=====");
    }
}

 ProxyHandler

package js.a;

import java.lang.reflect.*;
import java.util.*;

public class ProxyHandler implements InvocationHandler {
    // 需被代理的目标对象
    private Object target;

    // 创建拦截器实例
    DogIntercepter di = new DogIntercepter();

    // 执行代理的目标方法时,该invoke方法会被自动调用
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Exception {
        Object result = null;
        // 如果被调用方法的方法名为info
        if (method.getName().equals("info")) {
            // 调用拦截器方法1
            di.method1();
            result = method.invoke(target, args);
            // 调用拦截器方法2
            di.method2();
        } else {
            result = method.invoke(target, args);
        }
        return result;
    }

    // 用于设置传入目标对象的方法
    public void setTarget(Object o) {
        this.target = o;
    }
}

 通过 ProxyHandler ,系统实现了在执行 info 方法之前,调用拦截器的 method1() ; 在指定 info 方法之后,调用拦截器的 method2()

 

上面的类与被拦截对象没有丝毫耦合,更好的解耦了。但是还是 2 个地方耦合了

 ● 与拦截器的耦合: 使用 DogIntercepter

 ● 与被拦截的方法耦合 : info 方法

解决方法 : 通过配置文件来指定被拦截的目标方法和拦截器

 

MyProxyFactory

package js.a;

import java.lang.reflect.*;

public class MyProxyFactory {
    /**
     * 实例Service对象
     * @param serviceName String
     * @return Object
     */
    public static Object getProxy(Object object) {
        // 代理的处理类
        ProxyHandler handler = new ProxyHandler();
        // 把该Dog实例托付给代理操作
        handler.setTarget(object);
        // 第一个参数是用来创建动态代理的ClassLoader对象,
        // 只要该对象能访问Dog接口即可
        // 第二个参数是接口数组,正是代理该接口数组
        // 第三个参数是代理包含的处理实例
        return Proxy.newProxyInstance(DogImpl.class.getClassLoader(), object
            .getClass().getInterfaces(), handler);
    }
}
 

Proxy.newProxyInstance() 根据接口数组动态创建代理类实例,接口数组通过 object.getClass().getInterfaces() 获得,创建代理类是 JVM 在内存中动态创建,该类实现传入参数里接口数组中的全部接口。 因此, Dynamic Proxy 要求被代理的必须是接口的实现类,否则无法为其构造相应的动态实例。

 

TestDog

package js.a;

public class TestDog {
    public static void main(String[] args) {
        // 创建一个Dog实例,该实例将被作为代理的目标对象
        Dog targetObject = new DogImpl();
        Dog dog = null;
        // 以目标对象创建代理
        Object proxy = MyProxyFactory.getProxy(targetObject);
        if (proxy instanceof Dog) {
            dog = (Dog) proxy;
        }
        // 测试代理的方法
        dog.info();
        dog.run();
    }
}
 

1.4 拦截器和 AOP 的关系

 

 拦截器与 AOP (Aspect Orient Program ,面向切面编程 ) 是密切相关的, AOP 从程序运行角度来考虑程序的流程,取得业务处理过程的切面,在特定切面通过系统自动插入特定方法。 AOP 面向的是程序运行中各个步骤,以一种松耦合方式来组合业务处理的各个步骤。

 

AOP 3 个重要概念:

 ● 目标对象 : 包含被拦截方法的原始对象。

 ● 被插入的处理方法 : 定义在拦截器中,会在被拦截方法之前、之后自动执行的方法

 ● 代理对象 : 以目标对象为蓝本,由系统创建的新对象


   在上面 3 个概念中,被插入的处理方法不能独立存在,因为方法必须有一个存在的载体:这个载体就是拦截器,拦截器就是包含处理方法的特殊实例

 

    代理对象也称为 AOP 代理,就是由系统动态生成的一个对象,该对象将代替目标对象来使用, AOP 代理包含了目标对象的全部方法。 但 AOP 代理中的方法与目标对象的方法存在差异: AOP 方法在特定切面插入拦截器方法,在处理之间回调目标对象的方法,从而实现了在执行目标方法之前或者之后调用拦截器方法,仿佛拦截器拦截了原有的目标方法一样。

 

 

1.5 拦截器在 Struts 2 中的作用


   对于任何 MVC 框架来说,他们都会完成一些通用的控制逻辑: 例如解析请求参数,类型转换,将请求参数封装成 DTO (Data Transfer Object),执行输入校验,解析文件上传表单中的文件域,防止表单多次提交……这些操作又不是所有 Action 都需要实现的,所以需要动态的方式来自由组合。

 

 Struts 1 把这些动作写死在核心控制器里,这样有 2 个缺点:

 ● 灵活性差: 强制所有项目都必须使用该框架提供的全部功能,不管用户是否需要,核心控制器总是会完成这些操作

 ● 可扩展性差: 如果用户需要让核心控制器完成更多自定义的处理,这就比较困难了。在 Struts 1 时代都是通过扩展 Struts 1 的核心控制器来实现的。

 

  Struts 2 改变了这种做法,它把大部分核心控制器需要完成的工作按功能分开定义,每个拦截器完成一个功能。而这些拦截器可以自由选择,灵活组合(甚至不用 Struts 2 的任何拦截器),开发者需要使用哪些拦截器,只需要在 struts.xml 文件中指定使用该拦截器即可。

 

  拦截器的用法非常灵活,Struts 2 允许将完成各种 “小功能” 的方法定义成小粒度的拦截器,如果有一些拦截器经常需要固定在一起使用,又可以将这批小粒度的拦截器定义成大粒度的拦截器栈。从结构上来看,拦截器栈相当于多个拦截器的组合;从功能上来看,拦截器栈也是拦截器,只是一个功能更强大的拦截器

 

提示 : 一个拦截器栈可以组合多个小粒度的拦截器,一个小粒度的拦截器可以被多个拦截器栈组合。通过组合不同的拦截器,我们能以自己需要的方式来组合 Struts 2 框架的各种功能;通过扩展自己的拦截器,我们可以 “无限” 扩展 Struts 2 框架。


  Struts 2 框架的绝大部分功能都是通过拦截器完成的,当 FilterDispatcher 拦截到用户请求之后,大量拦截器将会对用户请求进行处理,然后才会调用用户的开发的 Action 实例的方法来处理请求。

 拦截器与 Action 的关系:

   SerlvetDispatch 初始化一个 ActionProxy 实例,并调用它的 execute 方法

   拦截器方法会先拦截、并处理用户请求,然后才到 Action 的 execute 方法处理用户请求

   返回一个逻辑视图名,系统负责将该逻辑视图对应的资源显示给用户

 

 

 对于 Struts 2 框架而言,有些通用功能对于所有的 Action 都是必须的,所以把这些拦截器组合成拦截器栈(Intercepter Stack), 并将其配置成默认的拦截器应用。

 因为拦截器是通过配置文件指定的,因此通过拦截器来引入通用操作的方式,完全是可插拔式的,当系统需要执行这些通用操作时,则配置文件引入这些拦截器即可;如果系统不需要执行这些通用操作,则在配置文件中取消引入即可。


注意 : Struts 2 已经默认启用了大量通用功能的拦截器,只要我们配置 Action 的 package 继承了 struts-default 包,这些拦截器就会起作用。可以到 struts2-core-2.2.jar 里看 struts-default.xml 文件,在该文件内可以看到大量拦截器、拦截器栈

 

 

二、拦截器配置初步

 struts 2 允许以一种可插拔式的方式来管理 Action 需要完成的通用操作,将这些通用操作定义成拦截器方法,然后在 Struts.xml 文件中配置 Action 时引入该 Action 即可。

 

2.1  配置拦截器


  在 struts.xml 文件中定义拦截器只需为拦截器类指定一个拦截器名,就完成了拦截器定义。如下:

<!-- 通过指定拦截器名和拦截器实现类来定义拦截器 -->
<interceptor name="拦截器名" class="拦截器实现类" />

 如果需要在配置拦截器时传入参数 ,则如下:

<interceptor name="拦截器名" class="拦截器实现类" >
	<param name="参数名">参数值</param>
</interceptor>  

 

还可以把多个拦截器连在一起组成拦截器栈

完全可以认为拦截器栈是一个更大的拦截器 ,如下:

<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>
 

系统为拦截器指定参数有 2 个时机:

 ● 定义拦截器时定义参数值: 这种参数值将作为拦截器参数的默认参数值

 ● 使用拦截器时指定参数值: 在配置 Action 时候为拦截器参数指定值。

如下 :

<interceptor-stack name="拦截器栈一">
	<interceptor-ref name="拦截器一">
		<param name="参数名">参数值</param>
	</interceptor-ref>
	<interceptor-ref name="拦截器二" />
</interceptor-stack>
<interceptor-stack name="拦截器栈二">
	<interceptor-ref name="拦截器三" />
	<interceptor-ref name="拦截器栈一" />
</interceptor-stack>
 

注意 : 有 2 个时机为拦截器指定参数值,一个是当定义拦截器 (通过 <intercepter .../> 来定义拦截器)时指定拦截器的参数值;另一个是当使用拦截器 (通过 <interceptor-ref .../> 使用拦截器)时指定拦截器的参数值。前者指定的是拦截器参数的默认值。

 

如果在两个时机为同一个参数指定了不同的参数值,则使用拦截器时指定的参数值将会覆盖默认的参数值。

 

2.2  使用拦截器

 

 一旦定义了拦截器和拦截器栈后,就可以使用这个拦截器或拦截器栈来拦截 Action 了,拦截器、拦截器栈的拦截行为将会在 Action execute() 方法执行之前被执行

 

 通过 <interceptor-ref .../> 元素可以在 Action 内使用拦截器:

<interceptors>
        <!-- 配置mysimple拦截器 -->
	<interceptor name="mysimple" class="lee.SimpleInterceptor">
		<!-- 为拦截器指定参数值 -->
		<param name="name">简单拦截器</param>
	</interceptor>
</interceptors>
 <action name="login" class="lee.LoginAction">
	<result name="error">/error.jsp</result>
	<result name="success">/welcome.jsp</result> 
	<!-- 拦截器一般配置在result元素之后! -->
	<!-- 配置系统的默认拦截器 -->
	<interceptor-ref name="defaultStack"/>
	<!-- 应用自定义的mysimple拦截器 -->
	<interceptor-ref name="mysimple">
		<param name="name">改名后的拦截器</param>
	</interceptor-ref>
</action>

在执行 LoginAction 之前,拦截器会起作用 

 

2.3  配置默认拦截器

   当配置一个包时,可以为其指定默认拦截器。一旦为某个包指定了默认的拦截器,如果该包中的 Action 没有显示指定拦截器,则默认的拦截器将会起作用。但值得注意的是 : 一旦我们为该包中的 Action 显示应用了某个拦截器,则默认的拦截器就不会起作用,如果该 Action 需要使用该默认拦截器,则必须手动配置该拦截器的引用

 

注意 : 只有当 Action 中没有显示应用拦截器时,该 Action 所在包的默认拦截器才会生效。

 

配置默认拦截器使用 <default-interceptor-ref .../> 元素,该元素作为 <package .../> 元素的子元素使用,为该包下的所有 Action 配置默认的拦截器

 

配置 <default-interceptor-ref .../> 元素时,需要指定一个 name 属性,该 name 属性值是一个已经存在拦截器(栈)的名字,表明将该拦截器(栈)配置成该包的默认拦截器。需要注意的是 : 每个 <package .../> 元素只能有一个 <default-interceptor-ref .../> 子元素,即每个包只能指定一个默认拦截器

 

<package name="包名">
	<interceptors>
		<!-- 定义拦截器 -->
		<interceptor></interceptor>
		<!-- 定义拦截器栈 -->
		<interceptor-stack ></interceptor-stack>
	</interceptors>
	<!-- 配置该包下的默认拦截器(栈) -->
	<default-interceptor-ref name="拦截器(栈)名">
		<!-- 在配置默认拦截器时为该拦截器指定参数值  -->
		<param name="参数名">参数值</param>
	</default-interceptor-ref>
	<!--配置 多个  Action  -->
	<action></action>
</package>
 

注意: 每个包只能指定一个默认拦截器。如果要指定多个拦截器,可以把多个拦截器定义成拦截器栈,然后把这个拦截器栈配置成默认拦截器即可。

 

三、Struts 2 内建的拦截器

   从 Struts 2 框架来看,拦截器几乎完成了70% 的工作,包括解析请求参数、将请求参数赋值给 Action 属性、执行数据校验、文件上传……, Struts 2 设计的灵巧性,更大程度地得益于拦截器设计,当需要扩展 Struts 2 功能时,只需要提供对应拦截器,并将它配置在 Struts 2 容器中即可;如果不需要该功能,也只需要取消该拦截器的配置即可。(可插拔式设计)

 

3.1  Struts 2 内建的拦截器

  Struts 2 内建了大量拦截器,这些拦截器以 name-class 对的形式配置在 struts-default.xml 中,如果我们定义的 package 继承了 Struts 2 的默认 struts-default 包,则可以自由使用下面定义的拦截器,否则必须自己定义这些拦截器

 

下面是 Struts 2 内建拦截器的简要介绍:

 ● alias : 实现在不同请求中相似参数别名的转换

 ● autowiring : 自动装配的拦截器,主要用于当 Struts 2 和 Spring 整合时,Struts 2可以使用自动装配的方式来访问 Spring 容器中的 Bean

 ● chain : 构建一个 Action 链,使当前 Action 可以访问前一个 Action 的属性,一般和 <result type="chain" .../> 一起使用

 ● conversionError : 这是一个负责处理类型转换错误的拦截器,它负责将类型转换错误从 ActionContext 中取出,并转换成 Action 的 FieldError 错误

 ● createSession : 负责创建一个 HttpSession 对象,主要用于那些需要有 HttpSession 对象才能正常工作的拦截器中。

 ● debugging : 当使用 Struts 2 的开发模式时,这个拦截器会提供更多的调试信息

 ● execAndWait : 后台执行 Action, 负责将等待画面发送给用户

 ● exception : 负责处理异常,将异常映射为结果

 ● fileUpload : 用于文件上传,负责解析表单中文件域的内容

 ● i18n : 支持国际化的拦截器,负责把所选的语言、区域放入用户 Session 中

 ● logger : 负责日志记录的拦截器,主要是输出 Action 名字

 ● modelDriven : 用于模型驱动的拦截器,当某个 Action 类实现了 ModelDriven 接口时,负责把 getModel() 方法的结果放入 ValueStack 中

 ● scopedModelDriven : 如果一个 Action 实现了一个 ScopedModelDriven 接口,该拦截器负责从指定生存范围中找出指定的 Model ,并将通过 setModel 方法将该 Model 传给 Action 实例

 ● params : 负责解析 HTTP 请求中的参数,并将参数值设置成 Action 对应的属性值

 ● prepare : 如果 action 实现了 Preparabel 接口,将会调用该拦截器的 prepare() 方法

 ● staticParams : 负责将 xml 中 <action> 标签下 <param> 标签中的参数值传入 action

 ● scope : 范围转换拦截器,可以将 Action 状态的信息保存到 HttpSession 范围,或者保存到 ServletContext 范围内

 ● servletConfig : 如果某个 Action 需要直接访问 Servlet API ,就是通过这个拦截器实现的

 ● timer : 负责输出 Action 的执行时间,这个拦截器在分析该 Action 的性能瓶颈时比较有用。

 ● token : 用于阻止重复提交,它检查传到 Action 中的 token,从而防止多次提交

 ● tokenSession : 这个拦截器的作用与前一个基本类似,只是它把 token 保存在 HttpSession 中

 ● validation : 通过执行在 xxxAction-validation.xml 中定义的校验器,从而完成数据校验

 ● workflow : 负责调用 Action 类中的 validate 方法,如果校验失败,则返回 input 逻辑视图

 ● store : 负责将 Action 的 messages、errors 、fieldErrors 保存到 session,也负责从 session 中读取 Action 的 messages、errors、fieldErrors

 ● checkbox : 负责检测那些没有勾选的复选项,为它们添加默认值(false)

 ● roles : 是一个 JAAS ( Java Authentication and Authorization Service, Java 授权和认证服务 ) 拦截器,只有当浏览者取得合适的授权后,才可以调用该拦截器拦截的 Action

 ● annotationWorkflow : 这是支持 Struts 2 “零配件” 特性的拦截器

 

大部分时候,开发者无需手动控制这些拦截器,因为 struts-default.xml 文件中已经配置了这些拦截器,只要我们定义的包继承了系统的 struts-default 包,就可以直接使用这些拦截器

 

3.2  struts-default.xml 里配置的拦截器

 

struts-default.xml 文件是 Struts 2 默认的配置文件,不管什么时候,肯定会加载

一般情况下,直接使用系统的 defaultStack 拦截器栈即可,虽然它可能会做一些额外的拦截工作,但对系统不会有太大的影响。如果开发者对每个拦截器所做的工作非常熟悉,而且可以灵活地组合必须的拦截器,对 Struts 2 性能优化是有帮助的,但是一般推荐直接使用 defaultStack

 

因为 Struts 2 的  struts-default 包中指定 defaultStack 拦截器栈是默认的拦截器,因此如果用户定义的包继承了 struts-default 包,则也会将 defaultStack 拦截器栈作为默认的拦截器栈。这意味着: 如果系统中的 Action 配置没有指定拦截器引用,系统会将 defaultStack 拦截器栈自动作用于该 Action

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值