以下图示:
比如用户登录的场景:
==> 填写账号密码后点击提交按钮,此时发送登录请求。
==> 请求会进入到核心控制器,进入核心控制器之前先经过一系列的过滤器进行过滤。
==> 核心控制器会通过Action代理对象读取请求(读取struts.xml文件)。
==> 进入Action之前会经过一系列的拦截器。
==> 进入Action控制器执行后,会根据返回的结果字符串选择相应的视图,登录成功,去到首页,登录失败,回到登录页面。
此接口根据请求的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 ActionChainResult
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.
*
*
* 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;
}
Struts2从请求到响应的基本流程如上图所示:
早期MVC框架将一些通用操作的硬编码放在核心控制器中,致使框架灵活性不足,可扩展降低。
Struts2将核心功能放到多个拦截器中实现,拦截器可自由选择和组合,增强了灵活性,有利于系统的解耦。
Struts2大多数核心功能都是通过拦截器实现的,每个拦截器都会完成某项特定的功能。
拦截器方法在Action执行之前和之后(顺序相反)执行。
拦截器栈:
==> 从结构上看,拦截器栈相当于多个拦截器的组合。
==> 在功能上看,拦截器栈也是拦截器。
拦截器和过滤器的原理很相似。
自定义拦截器可以通过继承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;
}
}
在核心配置文件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>
注意,拦截器要先定义后引用。
在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的所有功能都会失效。
拦截器栈的作用是组合多个拦截器,还是先定义拦截器,再配置拦截器栈,最后再引用拦截器栈即可。
<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>
下面定义一个拦截器,对请求进行拦截。
(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:
可以看到,控制器没有对loginRequest方法进行拦截,因为这里的配置:
因为还是走的loginRequest方法,所以拦截器没有进行拦截。几个链接:
请求的这几个方法,刚好配置了拦截器进行拦截: