Struts2之拦截器

Struts2之拦截器

  • 1、Struts2体系架构
    • 1.1、执行流程
    • 1.2、核心接口和类
    • 1.3、流程简图
  • 2、Struts2拦截器
    • 2.1、使用拦截器的目的
    • 2.2、拦截器的简介
    • 2.3、拦截器的工作原理
    • 2.4、拦截器的使用
      • 2.4.1、创建自定义拦截器
      • 2.4.2、struts.xml中定义和配置拦截器
      • 2.4.3、Struts2默认拦截器
      • 2.4.4、拦截器栈
    • 2.5、例子

1、Struts2体系架构

以下图示:

Struts2之拦截器_第1张图片

1.1、执行流程

  • 用户发送request请求,请求会先经过一系列的过滤器Filter。
  • 过滤器放行后,经过核心控制器FilterDispatcher(旧版本,新版本为StrutsPrepareAndExecuteFilter),核心控制器会使用Action代理对象读取struts.xml配置文件,然后创建struts2控制器Action的实例。
  • 进入Action控制器之前会经过一系列的拦截器。
  • 拦截器放行后进入Action,根据返回的结果字符串result会选择相应的视图,在响应到客户端之前也会经过一系列的拦截器。

比如用户登录的场景:

==> 填写账号密码后点击提交按钮,此时发送登录请求。

==> 请求会进入到核心控制器,进入核心控制器之前先经过一系列的过滤器进行过滤。

==> 核心控制器会通过Action代理对象读取请求(读取struts.xml文件)。

==> 进入Action之前会经过一系列的拦截器。

==> 进入Action控制器执行后,会根据返回的结果字符串选择相应的视图,登录成功,去到首页,登录失败,回到登录页面。

1.2、核心接口和类

Struts2之拦截器_第2张图片
(1)ActionMapper接口

此接口根据请求的URI查找是否存在对应的Action调用。org.apache.struts2.dispatcher.mapper.ActionMapper类源码如下:

public interface ActionMapper {

    /**
     * Expose the ActionMapping for the current request
     *
     * @param request The servlet request
     * @param configManager The current configuration manager
     * @return The appropriate action mapping or null if mapping cannot be determined
     */
    ActionMapping getMapping(HttpServletRequest request, ConfigurationManager configManager);

    /**
     * Expose the ActionMapping for the specified action name
     *
     * @param actionName The name of the action that may have other information embedded in it
     * @return The appropriate action mapping
     * @since 2.1.1
     */
    ActionMapping getMappingFromActionName(String actionName);

    /**
     * Convert an ActionMapping into a URI string
     *
     * @param mapping The action mapping
     * @return The URI string that represents this mapping
     */
    String getUriFromActionMapping(ActionMapping mapping);
}

(2)ActionMapping类

此类保存调用Action的映射信息,比如Action的name、namespace等。org.apache.struts2.dispatcher.mapper.ActionMapping类源码如下:

public class ActionMapping {

    private String name;
    private String namespace;
    private String method;
    private String extension;
    private Map<String, Object> params;
    private Result result;

    /**
     * Constructs an ActionMapping
     */
    public ActionMapping() {
        params = new HashMap<>();
    }

    /**
     * Constructs an ActionMapping with a default result
     *
     * @param result The default result
     */
    public ActionMapping(Result result) {
        this.result = result;
    }

    /**
     * Constructs an ActionMapping with its values
     *
     * @param name The action name
     * @param namespace The action namespace
     * @param method The method
     * @param params The extra parameters
     */
    public ActionMapping(String name, String namespace, String method, Map<String, Object> params) {
        this.name = name;
        this.namespace = namespace;
        this.method = method;
        this.params = params;
    }

    /**
     * @return The action name
     */
    public String getName() {
        return name;
    }

    /**
     * @return The action namespace
     */
    public String getNamespace() {
        return namespace;
    }

    /**
     * @return The extra parameters
     */
    public Map<String, Object> getParams() {
        return params;
    }

    /**
     * @return The method
     */
    public String getMethod() {
        if (null != method && "".equals(method)) {
            return null;
        } else {
            return method;
        }
    }

    /**
     * @return The default result
     */
    public Result getResult() {
        return result;
    }
    
    /**
     * @return The extension used during this request
     */
    public String getExtension() {
        return extension;
    }

    /**
     * @param result The result
     */
    public void setResult(Result result) {
        this.result = result;
    }

    /**
     * @param name The action name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * @param namespace The action namespace
     */
    public void setNamespace(String namespace) {
        this.namespace = namespace;
    }

    /**
     * @param method The method name to call on the action
     */
    public void setMethod(String method) {
        this.method = method;
    }

    /**
     * @param params The extra parameters for this mapping
     */
    public void setParams(Map<String, Object> params) {
        this.params = params;
    }
    
    /**
     * @param extension The extension used in the request
     */
    public void setExtension(String extension) {
        this.extension = extension;
    }

    @Override
    public String toString() {
        return "ActionMapping{" +
                "name='" + name + '\'' +
                ", namespace='" + namespace + '\'' +
                ", method='" + method + '\'' +
                ", extension='" + extension + '\'' +
                ", params=" + params +
                ", result=" + (result != null ? result.getClass().getName() : "null") +
                '}';
    }

}

这个类就是一个简单的实体类,保存Action的各种属性信息。

(3)ActionProxy接口

这是一个代理接口,在真正的XWork和Action之间充当代理。com.opensymphony.xwork2.ActionProxy类源码如下:

public interface ActionProxy {

    /**
     * Gets the Action instance for this Proxy.
     *
     * @return the Action instance
     */
    Object getAction();

    /**
     * Gets the alias name this ActionProxy is mapped to.
     *
     * @return the alias name
     */
    String getActionName();

    /**
     * Gets the ActionConfig this ActionProxy is built from.
     *
     * @return the ActionConfig
     */
    ActionConfig getConfig();

    /**
     * Sets whether this ActionProxy should also execute the Result after executing the Action.
     *
     * @param executeResult true to also execute the Result.
     */
    void setExecuteResult(boolean executeResult);

    /**
     * Gets the status of whether the ActionProxy is set to execute the Result after the Action is executed.
     *
     * @return the status
     */
    boolean getExecuteResult();

    /**
     * Gets the ActionInvocation associated with this ActionProxy.
     *
     * @return the ActionInvocation
     */
    ActionInvocation getInvocation();

    /**
     * Gets the namespace the ActionConfig for this ActionProxy is mapped to.
     *
     * @return the namespace
     */
    String getNamespace();

    /**
     * Execute this ActionProxy. This will set the ActionContext from the ActionInvocation into the ActionContext
     * ThreadLocal before invoking the ActionInvocation, then set the old ActionContext back into the ThreadLocal.
     *
     * @return the result code returned from executing the ActionInvocation
     * @throws Exception can be thrown.
     * @see ActionInvocation
     */
    String execute() throws Exception;

    /**
     * Gets the method name to execute, or null if no method has been specified (meaning execute will be invoked).
     *
     * @return the method to execute
     */
    String getMethod();

    /**
     * Gets status of the method value's initialization.
     *
     * @return true if the method returned by getMethod() is not a default initializer value.
     */
    boolean isMethodSpecified();
    
}

(4)ActionInvocation接口

此接口的作用是表示Action的执行状态,保存拦截器、Action的实例。com.opensymphony.xwork2.ActionInvocation类源码如下:

public interface ActionInvocation {

    /**
     * Get the Action associated with this ActionInvocation.
     *
     * @return the Action
     */
    Object getAction();

    /**
     * Gets whether this ActionInvocation has executed before.
     * This will be set after the Action and the Result have executed.
     *
     * @return true if this ActionInvocation has executed before.
     */
    boolean isExecuted();

    /**
     * Gets the ActionContext associated with this ActionInvocation. The ActionProxy is
     * responsible for setting this ActionContext onto the ThreadLocal before invoking
     * the ActionInvocation and resetting the old ActionContext afterwards.
     *
     * @return the ActionContext.
     */
    ActionContext getInvocationContext();

    /**
     * Get the ActionProxy holding this ActionInvocation.
     *
     * @return the ActionProxy.
     */
    ActionProxy getProxy();

    /**
     * If the ActionInvocation has been executed before and the Result is an instance of {@link ActionChainResult}, this method
     * will walk down the chain of ActionChainResults until it finds a non-chain result, which will be returned. If the
     * ActionInvocation's result has not been executed before, the Result instance will be created and populated with
     * the result params.
     *
     * @return the result.
     * @throws Exception can be thrown.
     */
    Result getResult() throws Exception;

    /**
     * Gets the result code returned from this ActionInvocation.
     *
     * @return the result code
     */
    String getResultCode();

    /**
     * Sets the result code, possibly overriding the one returned by the
     * action.
     *
     * 

* The "intended" purpose of this method is to allow PreResultListeners to * override the result code returned by the Action. *

* *

* If this method is used before the Action executes, the Action's returned * result code will override what was set. However the Action could (if * specifically coded to do so) inspect the ActionInvocation to see that * someone "upstream" (e.g. an Interceptor) had suggested a value as the * result, and it could therefore return the same value itself. *

* *

* If this method is called between the Action execution and the Result * execution, then the value set here will override the result code the * action had returned. Creating an Interceptor that implements * {@link PreResultListener} will give you this opportunity. *

* *

* If this method is called after the Result has been executed, it will * have the effect of raising an IllegalStateException. *

* * @param resultCode the result code. * @throws IllegalStateException if called after the Result has been executed. * @see #isExecuted() */
void setResultCode(String resultCode); /** * Gets the ValueStack associated with this ActionInvocation. * * @return the ValueStack */ ValueStack getStack(); /** * Register a {@link PreResultListener} to be notified after the Action is executed and * before the Result is executed. * *

* The ActionInvocation implementation must guarantee that listeners will be called in * the order in which they are registered. *

* *

* Listener registration and execution does not need to be thread-safe. *

* * @param listener the listener to add. */
void addPreResultListener(PreResultListener listener); /** * Invokes the next step in processing this ActionInvocation. * *

* If there are more Interceptors, this will call the next one. If Interceptors choose not to short-circuit * ActionInvocation processing and return their own return code, they will call invoke() to allow the next Interceptor * to execute. If there are no more Interceptors to be applied, the Action is executed. * If the {@link ActionProxy#getExecuteResult()} method returns true, the Result is also executed. *

* * @throws Exception can be thrown. * @return the return code. */
String invoke() throws Exception; /** * Invokes only the Action (not Interceptors or Results). * *

* This is useful in rare situations where advanced usage with the interceptor/action/result workflow is * being manipulated for certain functionality. *

* * @return the return code. * @throws Exception can be thrown. */
String invokeActionOnly() throws Exception; /** * Sets the action event listener to respond to key action events. * * @param listener the listener. */ void setActionEventListener(ActionEventListener listener); void init(ActionProxy proxy) ; }

(5) Interceptor接口

此接口是在请求处理之前或处理之后执行的组件。com.opensymphony.xwork2.interceptor.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;

}

1.3、流程简图

Struts2之拦截器_第3张图片

Struts2从请求到响应的基本流程如上图所示:

  • 请求首先经过Struts2的核心控制器。
  • 然后经过一系列的拦截器。
  • 经过拦截器之后,到达Action,由Actioh处理,返回Result视图。
  • 在响应之前,再次经过拦截器,注意,和请求进入时经过的拦截器顺序相反。比如进入时经过的是拦截器1 -> 拦截器2 -> 拦截器3,那么响应之前经过的顺序就是拦截器3 -> 拦截器2 -> 拦截器1。
  • 最后根据Result返回结果,响应视图,跳转页面。

2、Struts2拦截器

2.1、使用拦截器的目的

早期MVC框架将一些通用操作的硬编码放在核心控制器中,致使框架灵活性不足,可扩展降低。
Struts2将核心功能放到多个拦截器中实现,拦截器可自由选择和组合,增强了灵活性,有利于系统的解耦。

2.2、拦截器的简介

Struts2大多数核心功能都是通过拦截器实现的,每个拦截器都会完成某项特定的功能。

拦截器方法在Action执行之前和之后(顺序相反)执行。

拦截器栈:

==> 从结构上看,拦截器栈相当于多个拦截器的组合。
==> 在功能上看,拦截器栈也是拦截器。

拦截器和过滤器的原理很相似。

2.3、拦截器的工作原理

Struts2之拦截器_第4张图片

2.4、拦截器的使用

2.4.1、创建自定义拦截器

自定义拦截器可以通过继承AbstractInterceptpr或MethodFilterInterceptor(可进行方法级别的拦截)。如下:

/**   
 * @ClassName:  TestInterceptor   
 * @Description: 自定义拦截器
 * @author: yanchengzhi
 * @date:   2023年1月18日 下午3:07:10      
 * @Copyright:  
 */
public class TestInterceptor extends AbstractInterceptor {

	@Override
	public String intercept(ActionInvocation invocation) throws Exception {
		System.out.println("进入第一个拦截器!");
		// 放行
		String result = invocation.invoke();
		System.out.println("退出第一个拦截器!");
		return result;
	}

}

2.4.2、struts.xml中定义和配置拦截器

在核心配置文件struts.xml的package标签内进行配置,如下:

    <package name="test1" extends="default">
        
        <interceptors>
            <interceptor name="test1" class="com.ycz.web.interceptors.TestInterceptor" />
        interceptors>

        <action name="hello" class="com.ycz.web.HelloAction">
            <result>/success.jspresult>
            
            <interceptor-ref name="test1" />
            
            <interceptor-ref name="defaultStack" />
        action>
    package>

注意,拦截器要先定义后引用。

2.4.3、Struts2默认拦截器

Struts2之拦截器_第5张图片

在struts2中有一些已经定义好的默认拦截器,源码的struts-default.xml中有配置,如下:

            <interceptor-stack name="defaultStack">
                <interceptor-ref name="exception"/>
                <interceptor-ref name="alias"/>
                <interceptor-ref name="servletConfig"/>
                <interceptor-ref name="i18n"/>
                <interceptor-ref name="prepare"/>
                <interceptor-ref name="chain"/>
                <interceptor-ref name="scopedModelDriven"/>
                <interceptor-ref name="modelDriven"/>
                <interceptor-ref name="fileUpload"/>
                <interceptor-ref name="checkbox"/>
                <interceptor-ref name="datetime"/>
                <interceptor-ref name="multiselect"/>
                <interceptor-ref name="staticParams"/>
                <interceptor-ref name="actionMappingParams"/>
                <interceptor-ref name="params"/>
                <interceptor-ref name="conversionError"/>
                <interceptor-ref name="validation">
                    <param name="excludeMethods">input,back,cancel,browseparam>
                interceptor-ref>
                <interceptor-ref name="workflow">
                    <param name="excludeMethods">input,back,cancel,browseparam>
                interceptor-ref>
                <interceptor-ref name="debugging"/>
            interceptor-stack>

注意:Struts2的所有功能都包含在默认的defaultStack中,如果引用了自定义的拦截器,就必须引用默认的defaultStack拦截器,否则Struts2的所有功能都会失效。

2.4.4、拦截器栈

拦截器栈的作用是组合多个拦截器,还是先定义拦截器,再配置拦截器栈,最后再引用拦截器栈即可。

        <interceptors>
            
            <interceptor name="test1" class="com.ycz.web.interceptors.TestInterceptor" />

            
            <interceptor-stack name="myStack">
                <interceptor-ref name="test1" />
                <interceptor-ref name="defaultStack" />
            interceptor-stack>
        interceptors>

Struts2之拦截器_第6张图片

2.5、例子

下面定义一个拦截器,对请求进行拦截。

(1)创建拦截器

创建一个拦截器,继承MethodFilterInterceptor,对某些方法进行拦截,而某些方法不需要拦截。

public class LoginInterceptor extends MethodFilterInterceptor {

	private static final long serialVersionUID = 1L;

	@Override
	protected String doIntercept(ActionInvocation invocation) throws Exception {
		System.out.println("进入拦截器!");
		HttpSession session = ServletActionContext.getRequest().getSession();
		if(session.getAttribute("loginUser") == null) {
			return Action.LOGIN;
		}
		System.out.println("用户【" + session.getAttribute("loginUser") + "】已登录!");
		return invocation.invoke();
	}

}

(2)创建Action控制器

创建Login2Action控制器类,继承ActionSupport,如下:

public class Login2Action extends ActionSupport {
	
	private static final long serialVersionUID = 1L;

	private String username;
	
	private String password;
	
	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String loginRequest() {
		System.out.println(username + "===>" + password);
		if(username.equals("admin") && password.equals("admin123456")) {
			ServletActionContext.getRequest().getSession().setAttribute("loginUser", username);
			return SUCCESS;
		}
		return LOGIN;
	}
	
	public String registerRequest() {
		System.out.println("用户注册");
		return SUCCESS;
	}
	
	public String updateRequest() {
		System.out.println("用户更新");
		return SUCCESS;
	}
	
	public String deleteRequest() {
		System.out.println("用户删除");
		return SUCCESS;
	}

}

(3)struts.xml配置

        <struts>
             <constant name="struts.enable.DynamicMethodInvocation" value="true" />
             <package name="default" extends="struts-default" namespace="/user">
                 
                <interceptors>
                    
                    <interceptor name="myLoginInterceptor" class="com.ycz.struts01.interceptor.LoginInterceptor"  />
                    
                    <interceptor-stack name="myLoginInterceptorStack">
                          <interceptor-ref name="defaultStack" />
                          <interceptor-ref name="myLoginInterceptor">
                               
                               <param name="includeMethods">
                                  register*,update*,delete*
                               param>
                               
                               <param name="excludeMethods">login*param>
                          interceptor-ref>
                    interceptor-stack>
                interceptors>
                <global-allowed-methods>regex:.*global-allowed-methods>
                 <action name="user" class="com.ycz.struts01.action.Login2Action" >
                      <result>/success.jspresult>
                      <result name="login">/login2.jspresult>
                      <interceptor-ref name="myLoginInterceptorStack" />
                 action>
       
             package>
             
        struts>

注意:如果配置了拦截器栈来组合多个拦截器,那么拦截器栈一定要包含默认的defaultStack拦截器,如果不包含,那么struts2的所有默认拦截配置都会失效(比如无法接收参数)。

(4)页面

login2.jsp页面:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title heretitle>
head>
<body>
     <form action="user/user!loginRequest.action" method="post">
          <div>
             <label>用户名:label>
             <input type="text" name="username" />
          div>
          <div>
             <label>密码:label>
             <input type="password" name="password" />
          div>
          <div>
             <input type="submit" value="提交" />
          div>
     form>
body>
html>

success.jsp页面:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    <%@ taglib prefix="s" uri="/struts-tags" %>
DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>主页面title>
head>
<body>
   <h1>当前用户:【${sessionScope.loginUser }】h1>
   <a href="user/user!registerRequest.action">注册请求a><br/>
   <a href="user/user!updateRequest.action">更新请求a><br/>
   <a href="user/user!deleteRequest.action">删除请求a><br/>
body>
html>

(5)测试

启动项目,访问http://localhost:8081/struts01/login2.jsp:

Struts2之拦截器_第7张图片
填写错误的用户名密码,提交,控制台打印:

Struts2之拦截器_第8张图片
可以看到,控制器没有对loginRequest方法进行拦截,因为这里的配置:

在这里插入图片描述
然后输入正确的用户名密码进行登录,页面跳转:

Struts2之拦截器_第9张图片
控制台打印:

Struts2之拦截器_第10张图片
因为还是走的loginRequest方法,所以拦截器没有进行拦截。几个链接:
在这里插入图片描述
请求的这几个方法,刚好配置了拦截器进行拦截:

Struts2之拦截器_第11张图片
Struts2之拦截器_第12张图片
按照拦截器的逻辑,用户未登录直接跳回登录页,登录了打印信息。依次点击页面的三个链接:
Struts2之拦截器_第13张图片
控制台打印:

Struts2之拦截器_第14张图片
可以看到,拦截器对这三个方法拦截成功。

你可能感兴趣的:(Struts2框架,struts,servlet,java)