第六节 struts2 拦截器 interceptor

Posted JAVA的学习之路

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第六节 struts2 拦截器 interceptor相关的知识,希望对你有一定的参考价值。

1. 拦截器简介

       在上节的 Struts 文件上传与下载中,我们使用了一个 fileUpload 拦截器,用来拦截不允许的文件类型。这节课我们就详细介绍一下拦截器。

      拦截器(Interceptor),在AOP(Aspect-Oriented Programming,面向切面编程)中它提供了一种机制,可以在 Action 执行之前或之后被调用执行,也可以在 Action 执行前阻止此 Action 运行,以完成特定功能。拦截器是 AOP的一种实现策略,它动态拦截 Action 调用的对象。

      Action 并不是直接和 Interceptor拦截器 直接发生关联作用,而是通过代理 ActionProxy 协同工作的:

第六节 struts2 拦截器 interceptor

可以看到,Action 被 Interceptor 拦截器一层一层的包裹起来,上图的结构有一些特点如下:

  • Interceptor 和 Action 的结构就如同一个堆栈(Stack),除了 Action 以外,堆栈中的其他元素是Interceptor;

  • Action 位于堆栈的底部。由于堆栈 “后进先出” 的特性,如果我们试图把 Action 拿出来执行,我们必须首先把位于 Action 上端的 Interceptor 拿出来。这样,整个执行就形成了一个递归调用;

  • 每个位于堆栈中的 Interceptor,除了需要完成它自身的逻辑,还需要完成一个特殊的执行职责。这个执行职责有3种选择:

    >

    (1) 中止整个执行,直接返回一个字符串作为 resultCode;

    >

    (2) 通过递归调用负责调用堆栈中下一个 Interceptor 的执行;

    >

    (3) 如果在堆栈内已经不存在任何的 Interceptor,则调用 Action;

2. 拦截器源码分析

      Interceptor 接口的定义如下:

public interface Interceptor extends Serializable {    /**     * Called to let an interceptor clean up any resources it has allocated.     */
    void destroy();    /**     * Called after an interceptor is created, but before any requests are processed using     * {@link #intercept(com.opensymphony.xwork2.ActionInvocation) intercept} , giving     * the Interceptor a chance to initialize any needed resources.     */
    void init();    /**     * Allows the Interceptor to do some processing on the request before and/or after the rest of the processing of the     * request by the {@link ActionInvocation} or to short-circuit the processing and just return a String return code.     *     * @param invocation the action invocation     * @return the return code, either returned from {@link ActionInvocation#invoke()}, or from the interceptor itself.     * @throws Exception any system-level error, as defined in {@link com.opensymphony.xwork2.Action#execute()}.     */
    String intercept(ActionInvocation invocation) throws Exception;

}

可以看到该接口包括 init()、destory()和intercept()方法,其中,intercept()方法是核心方法,它的参数类型是 ActionInvocation(Action调度者)。

抽象类 AbstractInterceptor 实现了 Interceptor 接口(路径如上):

public abstract class AbstractInterceptor implements Interceptor {    /**     * Does nothing     */
    public void init() {
    }    /**     * Does nothing     */
    public void destroy() {
    }    /**     * Override to handle interception     */
    public abstract String intercept(ActionInvocation invocation) throws Exception;
}

我们选一个继承了抽象类 AbstractInterceptor 的示例 ChainingInterceptor.java 看看它是怎么实现 intercept() 方法的:

    @Override    public String intercept(ActionInvocation invocation) throws Exception {
        ValueStack stack = invocation.getStack();
        CompoundRoot root = stack.getRoot();        if (shouldCopyStack(invocation, root)) {
            copyStack(invocation, root);
        }        return invocation.invoke();
    }

可以看到最后返回的 invocation.invoke(),invoke() 是什么方法呢?它是 ActionInvocation 接口 中的方法,ActionInvocation 是 Action调度者,此方法包含两层含义:

  • 如果拦截器堆栈中还有其他的 Interceptor,那么 invocation.invoke() 将调用堆栈中下一个 Interceptor 并执行。

  • 如果拦截器堆栈中没有其它 Interceptor 只有 Action 了,那么 invocation.invoke() 将调用 Action 执行。

既然是 ActionInvocation 接口中的方法,我们就照样找一个示例类,看看它是如何实现 invoke() 方法的。例如 ActionInvocation.java 。

    /**     * @throws ConfigurationException If no result can be found with the returned code     */
    public String invoke() throws Exception {
        String profileKey = "invoke: ";        try {
            UtilTimerStack.push(profileKey);            if (executed) {                throw new IllegalStateException("Action has already executed");
            }            if (interceptors.hasNext()) {                final InterceptorMapping interceptor = interceptors.next();
                String interceptorMsg = "interceptor: " + interceptor.getName();
                UtilTimerStack.push(interceptorMsg);                try {
                                resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
                            }                finally {
                    UtilTimerStack.pop(interceptorMsg);
                }
            } else {
                resultCode = invokeActionOnly();
            }            // this is needed because the result will be executed, then control will return to the Interceptor, which will
            // return above and flow through again
            if (!executed) {                if (preResultListeners != null) {                    for (Object preResultListener : preResultListeners) {
                        PreResultListener listener = (PreResultListener) preResultListener;

                        String _profileKey = "preResultListener: ";                        try {
                            UtilTimerStack.push(_profileKey);
                            listener.beforeResult(this, resultCode);
                        }                        finally {
                            UtilTimerStack.pop(_profileKey);
                        }
                    }
                }                // now execute the result, if we're supposed to
                if (proxy.getExecuteResult()) {
                    executeResult();
                }

                executed = true;
            }            return resultCode;
        }        finally {
            UtilTimerStack.pop(profileKey);
        }
    }

interceptors.hasNext() 会依次调用拦截器堆栈中的拦截器,我们主要关注这句代码:

resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);

上面的代码,将实现 ActionInvocation 接口的 DefaultActionInvocation 作为参数,调用了 interceptor 中的intercept() 方法。发现了什么没有?再看看 intercept() 方法:

    @Override    public String intercept(ActionInvocation invocation) throws Exception {
        ValueStack stack = invocation.getStack();
        CompoundRoot root = stack.getRoot();        if (shouldCopyStack(invocation, root)) {
            copyStack(invocation, root);
        }        return invocation.invoke();
    }

不难看出,intercept() 方法中又调用了 invoke() 方法,这正是我们在前面提到的递归调用。

在 invoke() 方法中,还有一个 preResultListener,这个接口是用来在 Action 执行完之后,但是还没有回到视图层之前,做一些工作的。

public interface PreResultListener {    /**     * This callback method will be called after the {@link com.opensymphony.xwork2.Action} execution and     * before the {@link com.opensymphony.xwork2.Result} execution.     *     * @param invocation  the action invocation     * @param resultCode  the result code returned by the action (eg. <code>success</code>).     */
    void beforeResult(ActionInvocation invocation, String resultCode);

}

       总结起来,Struts2 对于整个执行的划分,从 Interceptor 到 Action 一直到 Result,每一层都职责明确。不仅如此,Struts2 还为每一个层次之前都设立了恰如其分的插入点。使得整个 Action 层的扩展性非常高。

3. 自定义拦截器

通过上面的源码分析,不难发现要自定义拦截器一般有以下2种方法:

(1)实现 Interceptor 接口;

(2)继承 AbbstractInterceptor 抽象类;

拦截器在默认情况下会拦截Action中所有的方法,但是在某些情况下,可能只需要拦截Action中的一个或多个方法,有时候也希望不拦截某个方法,这种情况就需要使用另一种方法:

(3)继承 MethodFilterInterceptor 抽象类;

MethodFilterInterceptor 抽象类也是继承自 AbstractInterceptor 抽象类的,MethodFilterInterceptor 抽象类主要内容如下:

    @Override
    public String intercept(ActionInvocation invocation) throws Exception {        if (applyInterceptor(invocation)) {            return doIntercept(invocation);
        } 
        return invocation.invoke();
    }

发现intercept()方法把任务交给 doIntercept()方法了:

    /**     * Subclasses must override to implement the interceptor logic.     *     * @param invocation the action invocation     * @return the result of invocation     * @throws Exception     */
    protected abstract String doIntercept(ActionInvocation invocation) throws Exception;

因此我们需要继承 MethodFilterInterceptor 抽象类来实现拦截某些方法的时候,需要实现 doIntercept()方法。

4. 自定义拦截器示例

这里我们以实现 Interceptor 接口为例,展示如何自定义拦截器。

我们模拟这样一个场景:用户登录,但是需要有邀请码才能登录,所以第一次登录必须先申请一个邀请码,然后输入正确的邀请码才可以登录进去。

首先我们新建一个项目,然后照例做好必要的工作:新建项目 InterceptorLogin,勾选生成 web.xml,导入 jar 包,复制 struts.xml 配置文件到自己的项目。 

4.1 后台逻辑

首先是新建 Login.java,作为实现登录 action 的类:

package javawl.test.interceptor;
import java.util.Map;
import com.opensymphony.xwork2.ActionContext;
public class Login {    // 登录成功后的消息message    private String msg;    // 输入的邀请码    private String inputCode;  
 public String getMsg() {    
    return msg;    }  
     public void setMsg(String msg) {    
        this.msg = msg;    }  
          public String getInputCode() {  
               return inputCode;    }  
                 public void setInputCode(String inputCode) {        this.inputCode = inputCode;    }    public String execute() throws Exception {        // 从 session 中通过 “code” 这个key获取生成的邀请码        Map map = ActionContext.getContext().getSession();        String code = (String) map.get("code");        // 注意code是Session中的,inputCode是用户输入的        if (code != null) {            if ( null == getInputCode() || getInputCode().equals("") )  {                // 向Session中提交,以便后面在页面中显示                map.put("msgSession",  "Empty code!");                return "login";        }else if ( !getInputCode().equals(code) ) {            map.put("msgSession",  "Invalid code!");            return "login";        }else {            msg = "Congratulations! You've logged in!";                        return "success";        }    }    msg = "Congratulations! You've logged in!";    return "success";    } }

再新建 ApplyCode.java,申请邀请码,作为拦截器的实现类:

package javawl.test.interceptor;
import java.util.Map;
import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.Interceptor;
public class ApplyCode  implements Interceptor {    /**     *     */    private static final long serialVersionUID = 1L;    @Override    public void destroy() {        // TODO Auto-generated method stub    }    @Override    public void init() {        // TODO Auto-generated method stub    }    @Override    public String intercept(ActionInvocation arg0) throws Exception {        // TODO Auto-generated method stub        // 同样是从 session 中获得邀请码        Map sessionMap = arg0.getInvocationContext().getSession();        String code = (String) sessionMap.get("code");        // 如果session中已经加入了这个邀请码,则交给 invoke() 函数处理        if ( null != code && code.equals("javawl") ) {            return arg0.invoke();        }        // 否则就提示先申请一个邀请码 注入到Session中,以便在页面中显示        sessionMap.put("msgSession",  "Apply a code first!");        return "login";    } }

4.2 JSP 页面

新建一个 index.jsp 作为登录页面:

<%@ page language="java" contentType="text/html; charset=UTF-8"    pageEncoding="UTF-8"%><%@ taglib prefix="s" uri="/struts-tags" %>
   <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head><title>Login Interceptor</title></head><body>

   <center><h2>${sessionScope.msgSession }</h2>  
    <s:form action="login" method="post">  
        <s:textfield label="INVITATION CODE" name="inputCode"></s:textfield>  
        <s:submit value="Login" />  
    </s:form>  

    <a href="/InterceptorLogin/applyCode.jsp" type="button" >Apply a code</a>

    </center>  </body></html>

${sessionScope.msgSession} 就是显示 Session中 msgSession 这个字段的消息,在拦截器验证失败后,输出session中的msgSsession不同时候的值,以给予用户提示信息。

Apply a code 这个链接会转向申请邀请码的页面,其本质就是把一个邀请码(字段)注入到 Session 中,使其具有权限。所以我们需要再新建一个页面作为申请邀请码(输入Session)的页面,applyCode.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"    pageEncoding="UTF-8"%><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>Apply a new code</title></head><body><%    request.getSession().setAttribute("code", "javawl");    %>your INVITATION CODE is :  <b>shiyanlou</b> <br/><a href="/InterceptorLogin/index.jsp" type="button" >Back to login</a></body></html>

request.getSession().setAttribute("code", "javawl") 表示直接把一个字符(邀请码)注入到了Session中的 code 字段key。

新建 success.jsp 作为登录成功的跳转页面。

<%@ page language="java" contentType="text/html; charset=UTF-8"    pageEncoding="UTF-8"%><%@ taglib prefix="s" uri="/struts-tags" %><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>Hello World</title></head><body>
     <s:property value="msg"/></body></html>

3.4.3 配置 struts.xml

最后配置一下 struts.xml 文件:

<struts>
    <package name="javawl.interceptor" extends="struts-default">

        <interceptors>
            <interceptor name="applyCode" class="javawl.test.interceptor.ApplyCode"></interceptor>
            <interceptor-stack name="interceptLogin">
                <interceptor-ref name="applyCode"></interceptor-ref>
                <interceptor-ref name="defaultStack"></interceptor-ref>
            </interceptor-stack>
        </interceptors> 

        <action name="login" class="javawl.test.interceptor.Login" method="execute">
            <result name="success">/success.jsp</result>
            <result name="login">/index.jsp</result>
            <interceptor-ref name="interceptLogin"></interceptor-ref>
        </action>

    </package>  </struts>

可以看到,首先定义了一个 applyCode 的拦截器 interceptor,对应其实现类为shiyanlou.test.interceptor.ApplyCode;然后定义了一个拦截器堆栈 interceptor-stack,包含刚刚我们定义的applyCode拦截器,以及默认的defaultStack拦截器,引入默认拦截器的原因在于我们可能还是会用到一些基本的默认拦截器,比如 params-解析请求参数 等等。

然后,给登录Action设置了我们的拦截器堆,名为 interceptLogin。

3.4.4 web.xml

在 web.xml 里配置 struts。

<?xml version="1.0" encoding="UTF-8"?><web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
  <display-name>InterceptorLogin</display-name>
  <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>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list></web-app>

3.4.5 运行测试

运行项目,用户首先没有申请一个邀请码的话,会提示:

第六节 struts2 拦截器 interceptor

然后,点击申请一个邀请码(这里为了方便,直接将一个固定的邀请码字符串加入到Session中):

第六节 struts2 拦截器 interceptor

再次登录,会验证邀请码:

当输入邀请码为空:

第六节 struts2 拦截器 interceptor

输入邀请码不正确:

第六节 struts2 拦截器 interceptor

输入正确邀请码  javawl

第六节 struts2 拦截器 interceptor


第六节 struts2 拦截器 interceptor



javawl

java学习之路






以上是关于第六节 struts2 拦截器 interceptor的主要内容,如果未能解决你的问题,请参考以下文章

struts2学习笔记拦截器

struts2文件上传和下载

如何配置struts2的过滤器

jquery第六节

第六节 自感现象及其应用

浅谈Struts