Struts2之拦截器

1、Struts2体系架构

以下图示:

在这里插入图片描述

1.1、执行流程

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

比如用户登录的场景:

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

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

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

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

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

1.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 <tt>true</tt> 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 <tt>null</tt> if no method has been specified (meaning <code>execute</code> 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 <tt>true</tt> 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 <code>ActionChainResult</code>s 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.
     *
     * <p>
     * The "intended" purpose of this method is to allow PreResultListeners to
     * override the result code returned by the Action.
     * </p>
     *
     * <p>
     * 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.
     * </p>
     *
     * <p>
     * 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.
     * </p>
     *
     * <p>
     * If this method is called after the Result has been executed, it will
     * have the effect of raising an IllegalStateException.
     * </p>
     *
     * @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.
     *
     * <p>
     * The ActionInvocation implementation must guarantee that listeners will be called in
     * the order in which they are registered.
     * </p>
     *
     * <p>
     * Listener registration and execution does not need to be thread-safe.
     * </p>
     *
     * @param listener the listener to add.
     */
    void addPreResultListener(PreResultListener listener);

    /**
     * Invokes the next step in processing this ActionInvocation.
     *
     * <p>
     * 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 <tt>true</tt>, the Result is also executed.
     * </p>
     *
     * @throws Exception can be thrown.
     * @return the return code.
     */
    String invoke() throws Exception;

    /**
     * Invokes only the Action (not Interceptors or Results).
     *
     * <p>
     * This is useful in rare situations where advanced usage with the interceptor/action/result workflow is
     * being manipulated for certain functionality.
     * </p>
     *
     * @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从请求到响应的基本流程如上图所示:

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

2、Struts2拦截器

2.1、使用拦截器的目的

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

2.2、拦截器的简介

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

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

拦截器栈:

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

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

2.3、拦截器的工作原理

在这里插入图片描述

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.jsp</result>
            <!-- 引用拦截器 -->
            <interceptor-ref name="test1" />
            <!-- 引用默认拦截器 -->
            <interceptor-ref name="defaultStack" />
        </action>
    </package>

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

2.4.3、Struts2默认拦截器

在这里插入图片描述

在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,browse</param>
                </interceptor-ref>
                <interceptor-ref name="workflow">
                    <param name="excludeMethods">input,back,cancel,browse</param>
                </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>

在这里插入图片描述

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.jsp</result>
                      <result name="login">/login2.jsp</result>
                      <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 here</title>
</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:

在这里插入图片描述
填写错误的用户名密码,提交,控制台打印:

在这里插入图片描述
可以看到,控制器没有对loginRequest方法进行拦截,因为这里的配置:

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

在这里插入图片描述
控制台打印:

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

在这里插入图片描述
在这里插入图片描述
按照拦截器的逻辑,用户未登录直接跳回登录页,登录了打印信息。依次点击页面的三个链接:
在这里插入图片描述
控制台打印:

在这里插入图片描述
可以看到,拦截器对这三个方法拦截成功。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值