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体系架构
以下图示:
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方法,所以拦截器没有进行拦截。几个链接:
请求的这几个方法,刚好配置了拦截器进行拦截:
按照拦截器的逻辑,用户未登录直接跳回登录页,登录了打印信息。依次点击页面的三个链接:
控制台打印:
可以看到,拦截器对这三个方法拦截成功。