JavaEE笔记(三)Struts2 拦截器

本文详细介绍了Struts2框架中的拦截器,包括拦截器的作用、执行流程、自定义拦截器配置和与过滤器的区别。Struts2的拦截器基于AOP思想,用于在Action调用前后实现横切关注点,如预处理和后置处理。拦截器链通过ActionInvocation实现,每个拦截器通过接口的intercept方法进行递归调用。自定义拦截器需要实现Interceptor接口或继承其抽象类,配置在struts.xml中。拦截器与过滤器的主要区别在于机制、依赖、作用范围、访问对象和调用时机。
摘要由CSDN通过智能技术生成

JavaEE笔记(三)Struts2 拦截器

Interceptors are conceptually the same as servlet filters or the JDKs Proxy class. Interceptors allow for crosscutting functionality to be implemented separately from the action as well as the framework. You can achieve the following using interceptors:

  • Providing pre-processing logic before the action is called.
  • Providing post-processing logic after the action is called.
  • Catching exceptions so that alternate processing can be performed.

Many of the features provided in the Struts2 framework are implemented using interceptors; examples include exception handling, file uploading, life cycle callbacks and validation etc. In fact, as Struts2 bases much of its functionality on interceptors, it is not unlikely to have 7 or 8 interceptors assigned per action.[1]

拦截器在概念上与Servlet过滤器或者JDK的代理类是相同的(当然底层实现原理以及应用范围截然不同)。拦截器允许对要实现的功能进行横切以与Action和框架本身产生分离。我们可以应用拦截器实现下列功能:

  • 在Action被调用之前提供预处理逻辑;
  • 在Action被调用之后提供后置处理逻辑;
  • 在异常被捕获后使得别的处理流程可以进行。

在Struts2框架中提供的许多特性都是由拦截器实现的,例如:异常处理、文件上传、生命周期回调以及校验等。事实上,由于Struts2的功能实现很大程度上基于拦截器,对于每一个Action,被分配到七八个拦截器将非常常见。


1. Struts2基础

1.1 Struts2的处理流程简述

在介绍拦截器之前,有必要先简述一下Struts2框架的处理流程。下图为Apache官方的Struts2的处理流程图。
Struts Architecture
1) 在客户端发出一个ServletRequest后,request将经过一系列标准过滤器链。最后到达FilterDispatcher。FilterDispatcher 是一个过滤器分配类,在Struts2.1.3之前为标准的分配器,2.1.3之后取而代之的是org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter (以下简称Dispatcher),这个类也是我们在Struts2的web.xml文件中申明的过滤器类。

<filter>
    <filter-name>struts2</filter-name>
    <filter-class> org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
    </filter-class>
</filter>
<filter-mapping>
    <filter-name>struts2</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

2) Dispatch 主要做2个工作,其一,它向ActionMapper询问收到的request是否需要访问某个Action对象,如果有则继续调用executeAction()方法继续处理对Action的访问,否则则将request当做静态资源的请求处理,进一步将请求静态资源或者如果request为普通的请求,则放行。

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        // 省略部分源码
        ......

                ActionMapping mapping = prepare.findActionMapping(request, response, true); // 得到Action的映射
                if (mapping == null) { //映射为空则当作静态资源请求处理
                    boolean handled = execute.executeStaticResourceRequest(request, response);
                    if (!handled) {
                        chain.doFilter(request, response);
                    }
                } else { //映射不为空则执行Action的处理方法。
                    execute.executeAction(request, response, mapping);
                }
        ......

    }

其二,当Action映射不为空,则针对请求做进一步处理,调用execute.executeAction()方法。在executeAction方法的调用本质上是调用了Dispatcher.serviceAction()方法。在这个方法里将会根据请求的Action对象以及通过configuration manager解析的struts.xml配置文件参数创建一个代理对象ActionProxy,其底层由动态代理实现。
关于动态代理,博主之前的一篇笔记可以供参考。JavaEE笔记(二)动态代理(Dynamic Proxy)

public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context, ActionMapping mapping) throws ServletException {

    // 省略部分源码
    ......
    Configuration config = configurationManager.getConfiguration(); 
    ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(namespace, name, method, extraContext, true, false);
    ......

3) 代理对象ActionProxy会创建一个ActionInvocation对象,其实为一个接口,我们会在后面详细介绍该接口的实现类DefaultActionInvocation,这是拦截器实现的核心。通过这个对象,相当于动态代理类中调用处理器类,在调用Action之前,调用拦截器对象进行预处理。
4) 当Action执行返回后, ActionInvocation负责通过struts.xml中的配置信息寻找合适的Result. Result通常会涉及浏览器客户端的JSP页面,我们可以在JSP中申明框架提供Struts标签并使用标签来回显一些执行信息,例如异常反馈信息等。
5)最后,拦截器将根据具体拦截器的行为有可能再次以反序执行并最后通过SevletResponse输出页面。
以上便是Struts2的大致执行处理流程。

1.2 Struts2的Action类创建

在这里稍带提一下关于Action一般对于Action类的创建,有三种方式:

  • POJO:Plain Old Java Objects
    指的是不实现任何接口或继承任何类(除Object),所有功能和返回均根据开发者自己定义,当然需要在struts.xml中进行全匹配配置。虽然耦合度低,但是由于工作量很大,所以很少采用。
  • 创建一个类,实现Action接口,这个类提供了5个标准视图作为Result的返回结果,每个视图定义了一个行为方法。
    // 数据处理成功 (Result name属性的缺省值),配置文件配置成功跳转页面
    public static final String SUCCESS = "success";

    // 不跳转   
    public static final String NONE = "none";

    // 处理错误,配置文件配置跳转的错误页面 等同于"null"
    public static final String ERROR = "error";

    // 用户输入表单数据校验有误,配置文件配置跳转的重新输入页面
    public static final String INPUT = "input";

    // 用户登录权限不足,配置文件登陆页面
    public static final String LOGIN = "login";
  • 创建一个类,继承自ActionSupport类,
    该类实现了Action接口,因此也具有预设视图常量。另外,实现这个类我们就可以利用Struts预设的拦截器实现表单校验(validation)、错误信息设置(workflow)、国际化(i18n)。虽然这种方法耦合度高,但是提供了极强大的拦截器功能,因此是开发中的主要方法。

2. Struts2拦截器

第一节介绍了整个Struts2对于Request的处理流程,中间很重要的一环就是Action代理对象通过ActionInvocation实现类在调用Action之前及之后加载拦截器。

2.1 拦截器简介

Struts2拦截器使用的是AOP思想,面向切面编程,通俗地将就是在流程的执行过程中,在不改变节点代码的前提下,运用代理对功能进行增强和预处理。 这样的定义非常好地解释了拦截器在Struts2框架中的作用。Action对象并未发生改变,但是其功能和额外特性均由其代理对象通过调用器加载的拦截器链实现。因此在概念上,拦截器与JavaWeb中的过滤器非常类似,但是需要指出的是,两者之间有着本质的区别,后文会介绍。

拦截器采用了责任链模式,在此模式下,很多拦截器对象由每一个对象对其下游的引用而连接起来形成一条链条。责任链中的每一个节点,都可以继续调用下一个节点,也可以阻止流程继续执行。Struts2中在struts-default.xml文件中声明了所有的拦截器。而框架默认使用的是defaultStack这个拦截器栈,栈中使用了18个默认拦截器。因此当我们的struts.xml配置文件在package标签中声明的了继承默认配置文件的情况下,我们的框架就加载了18个拦截器。

2.2 拦截器执行流程

前面提到,Action的代理对象ActionInvocation其实是一个接口,其实现类实际为DefaultActionInvocation,那么我们就来看看这个实现类中调用了什么方法实现了拦截器的调用以及如何维护拦截器链。

public class DefaultActionInvocation implements ActionInvocation {
    // 维护一个成员变量, 一个拦截器集合的迭代器
    protected Iterator<InterceptorMapping> interceptors; 

    // 初始化方法将得到一个Interceptor集合,并生成迭代器,这样的好处是即使集合被修改,迭代器中的数据仍然维持原样。
    public void init(ActionProxy proxy) {
        // ......省略部分源码     
        List<InterceptorMapping> interceptorList = new ArrayList<InterceptorMapping>(proxy.getConfig().getInterceptors());
        interceptors = interceptorList.iterator();
    }

    // invoke()方法遍历了迭代器,递归调用拦截器链中的拦截器对象。
    public String invoke() throws Exception {        

            if (interceptors.hasNext()) {
                final InterceptorMapping interceptor = interceptors.next();
                String interceptorMsg = "interceptor: " + interceptor.getName();
                UtilTimerStack.push(interceptorMsg);
                try {
                // intercept()方法的参数为本类对象。
                    resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
                            }
                finally {
                    UtilTimerStack.pop(interceptorMsg);
                }
            } else {
                resultCode = invokeActionOnly(); // 递归结束,返回结果视图字符串。
            }

在上述源码中似乎看不到递归操作,原因是,对于每一个拦截器必须实现Interceptor接口,或者继承Interceptor接口的抽象实现类MethodFilterInterceptor,但无论哪种方法,我们都要在真正实现拦截器功能的方法中通过参数调用invoke()方法,而参数就是ActionInvocation对象,因此我们看到在DefaultActionInvocation类的invoke()方法中在调用intercept()方法时,我们传入了本类对象,随后,当intercept()方法被调用,又回到了if(interceptors.hasNext())来判断原迭代器中是否还留有未被处理的拦截器对象,至此形成了递归调用。当迭代器中没有拦截器对象了,则跳出递归,执行Action,并返回result视图的字符串。

public interface Interceptor extends Serializable {

    String intercept(ActionInvocation invocation) throws Exception;
    // 重写方法中如果通过传参的invocation对象调用invoke()方法,则产生递归调用拦截器链。
}

总结: Struts2中拦截器的实现依靠ActionInvocation的invoke()方法实现,每一个拦截器都必须事先intercept接口,重写接口中的intercept方法进行递归调用,这样就实现了拦截器链中每个节点的环环紧扣的调用。

2.3 自定义拦截器及配置
2.3.1 拦截器配置文件规则

首先,之前已经提到我们的配置文件中继承了Struts2的默认配置文件,也就默认使用了默认拦截器栈中的18个拦截器。

<!-- Default interceptor stack declaration -->
<default-interceptor-ref name="defaultStack"/>

在没有特殊需求的情况下,我们无需再struts.xml中配置拦截器,当然框架本身还是提供了多种应用场合的拦截器栈,也定义在默认配置文件中,如果我们要使用这些预设栈就需要在struts.xml文件中声明。

<package name="default" namespace="/" extends="struts-default">
    <interceptors>
        <interceptor name="my" class="cn.itcast.intercept.MyInterceptor">
        </interceptor>
        <interceptor name="bookInterceptor" class="cn.itcast.intercept.BookInterceptor">
            <param name="includeMethods">add,update,delete</param>
        </interceptor>
        <interceptor-stack name="myStack">
            <interceptor-ref name="bookInterceptor"></interceptor-ref>
            <interceptor-ref name="defaultStack" />
        </interceptor-stack>
    </interceptors>

    <action name="demo1" class="cn.itcast.action.Demo1Action">
        <result name="login">/login.jsp</result>
        <!-- <interceptor-ref name="my" /> <interceptor-ref name="defaultStack"/> -->
        <interceptor-ref name="myStack" />
    </action>
</package>

上面的代码中,我们首先在<interceptors>标签内定义了两个自定义拦截器,后面一小节将介绍拦截器对象如何创建。在声明拦截器对象是,我们要明确拦截器的名称name以及拦截器类的全名class。声明可以带上参数标签<param>, includeMethod表示被声明的拦截器作用于Action类中的某些特定方法,除此外的方法将不受拦截器约束。这里我们在URI为:/demo1的Action类中设置了拦截器,而它将只对add(), update()以及delete()起作用。

<interceptor-stack>中规定了名字为myStack的拦截器栈应包含哪些拦截器,这里包含了自定义拦截器bookInterceptor以及默认的18个拦截器。 但需要注意一点的是,这里的声明需要注意顺序,因为拦截器以链的形式实现,因此拦截器顺序对于最终的功能至关重要。如果我们需要将拦截器插入默认的栈来实现某些功能,我们只能重新声明需要的拦截器并按照需要的顺序注意添加到栈声明中。这种特性也是Struts2一直被诟病的拦截器声明缺乏灵活性。

最后,在Action标签内,将需要加载的拦截器通过<interceptor-ref name="xxx">来声明。

2.3.2 拦截器类的创建

所有的Struts 2的拦截器都直接或间接实现接口com.opensymphony.xwork2.interceptor.Interceptor。除此之外,大家可能更喜欢继承类AbstractInterceptor。在这里我们用一个例子简单解释拦截器的创建,我们直接继承AbstractInterceptor的实现类MethodFilterInterceptor`。

import org.apache.struts2.ServletActionContext;
import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor;

public class BookInterceptor extends MethodFilterInterceptor {

    @Override
    protected String doIntercept(ActionInvocation invocation) throws Exception {

        // 1.得到session中的user
        User user = (User) ServletActionContext.getRequest().getSession()
                .getAttribute("user");

        if (user == null) {
            // 得到当前拦截的action对象。
            BookAction action = (BookAction) invocation.getAction(); 
            // 存储错误信息
            action.addActionError("权限不足,请先登录");
            return Action.LOGIN;
        }
        return invocation.invoke(); // 放行,递归调用拦截器链中下一个拦截器
    }
}

继承MethodFilterInterceptor来创建自定拦截器,相当于实现了Interceptor接口,然后需要重写doIntercept方法来实现拦截器逻辑。代码中的自定义拦截器主要实现检查登陆是否完成,如果登陆成功,即我们得到session域中存有登陆成功后设置的User类对象,则放行,在拦截器链中调用下一个拦截器。这个拦截器的任务也就结束了。反之,则跳转到LOGIN视图,即跳转到登陆的jsp页面并显示错误信息,这里我们用action.addActionError("msg")来存储错误信息,在login.jsp的页面可以通过导入struts标签,并用<s:actionerror/>来回显错误信息。关于如何得到ServletContext对象以及struts标签库的使用,就不在这里展开了。


3. 拦截器与过滤器的区别

关于这个话题,网上讨论得很多,百度搜索基本能概括为以下5条,博主简单阐述下我的理解:

1)拦截器是基于java反射机制的,而过滤器是基于函数回调的。
过滤器需要在web.xml中配置,当服务器收到Http请求,就会根据配置先调用声明的过滤器的doFilter()方法。根据方法中的行为,为request,response做预处理。这中间不涉及任何底层反射,完全基于doFilter()的重写和调用,因此是基于回调函数的。 另一方面,拦截器是由动态代理中的ActionInvocation来调用的,底层是由反射实现的,如果没有动态代理对象就无法实现拦截器的调用,因此拦截器是基于反射机制的。

2)过滤器依赖于servlet容器,而拦截器不依赖于servlet容器。
过滤器是JavaWeb的核心内容,它依赖于Servlet容器,没有servlet则无法应用过滤器。拦截器虽然应用在Struts2中,但也是其他MVC框架的主要功能,拦截器是一种责任链模式的体现,并不依赖于servlet。

3)拦截器只能对Action请求起作用,而过滤器则可以对几乎所有请求起作用。
由于拦截器由给予动态代理,而被代理对象就只是Action对象,因此拦截器只服务于Action。 过滤器可以对所有的请求起作用,不仅仅是servlet, action等动态资源,任何静态资源html, jsp也可以被过滤器过滤。

4)拦截器可以访问Action上下文、值栈里的对象,而过滤器不能。
ActionContext以及valueStack是struts维护的数据结构,保存了Action执行过程中的一些对象,拦截器作用于Action当然可以访问这些对象。而过滤器则不能。关于这个话题,将在之后专门开篇讨论。

5)在Action的生命周期中,拦截器可以多次调用,而过滤器只能在容器初始化时被调用一次。
Action是多实例的,对于每一个Action,在struts.xml中都配置了相应的拦截器栈,因此对于每一个对于Action实例的线程,拦截器将被调用一次,所以一个Action会经历多次拦截器调用。过滤器反之,配置在web.xml中,在一次Http请求中只有在servlet初始化的时候被调用一次。


总结:
Struts框架本身定义了众多的拦截器,而即使是默认拦截器栈也有18个拦截器之多,因此限于篇幅,这里不可能一一介绍,拦截器的应用广泛是Struts框架的核心,有些重要的拦截器也无法在一个专题里详解,例如参数封装、国际化、文件上传下载等都可能需要另开一篇专门介绍。
作为Struts框架的核心功能,拦截器提供了强大的预处理及后置处理功能,通过Action代理对象在调用处理器中利用递归实现整个拦截器链的遍历。而自定义拦截器也使得开发更加具有可扩展性。


以上

© 著作权归作者所有

引用:
[1] Struts 2 - Interceptors http://www.tutorialspoint.com/struts_2/struts_interceptors.htm
[2] The Struts 2 Request Flow http://struts.apache.org/docs/the-struts-2-request-flow.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值