0x00 Struts2初始化主线
程序入口
查看StrutsPrepareAndExecuteFilter.java的init程序
public class StrutsPrepareAndExecuteFilter implements StrutsStatics, Filter {
// 进行HTTP请求预处理的操作类
protected PrepareOperations prepare;
// 进行HTTP请求的逻辑执行处理类
protected ExecuteOperations execute;
// 配置哪些形式的URL模式被排除在Struts2的处理之外
protected List<Pattern> excludedPatterns = null;
public StrutsPrepareAndExecuteFilter() {
}
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化操作了类
InitOperations init = new InitOperations();
Dispatcher dispatcher = null;
try {
FilterHostConfig config = new FilterHostConfig(filterConfig);
init.initLogging(config);
// 初始化核心分发器Dispatcher
dispatcher = init.initDispatcher(config);
// 初始化静态资源加载器
init.initStaticContentLoader(config, dispatcher);
// 初始化HTTP预处理的操作类
this.prepare = new PrepareOperations(dispatcher);
// 初始化进行HTTP请求处理的逻辑执行操作类
this.execute = new ExecuteOperations(dispatcher);
this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
// 自定义初始化过程,留作用户扩展
this.postInit(dispatcher, filterConfig);
} finally {
if (dispatcher != null) {
dispatcher.cleanUpAfterInit();
}
// 初始化中的清理工作
init.cleanup();
}
}
// 初始化中的核心扩展工作
protected void postInit(Dispatcher dispatcher, FilterConfig filterConfig) {
}
}
初始化过程中最重要的元素是:Dispatcher(核心分发器),Dispatcher是Struts2与XWork的分界点,也是将MVC与Web容器隔离的分界点
Dispatcher
核心驱动力:Struts2初始化过程的目的,是对各种配置形式所进行的一次统一的对象化管理
Dispatcher初始化的InitOperations.java
public class InitOperations {
public Dispatcher initDispatcher(HostConfig filterConfig) {
Dispatcher dispatcher = this.createDispatcher(filterConfig);
dispatcher.init();
return dispatcher;
}
private Dispatcher createDispatcher(HostConfig filterConfig) {
Map<String, String> params = new HashMap();
Iterator e = filterConfig.getInitParameterNames();
while(e.hasNext()) {
String name = (String)e.next();
String value = filterConfig.getInitParameter(name);
params.put(name, value);
}
return new Dispatcher(filterConfig.getServletContext(), params);
}
}
createDispatcher方法将filterConfig中初始化参数通过构造函数传入Dispatcher来创建一个新的Dispatcher实例并返回,filterConfig中所包含的参数来自web.xml中
看一看Dispatcher的初始化过程
public class Dispatcher {
private static ThreadLocal<Dispatcher> instance = new ThreadLocal();
public static Dispatcher getInstance() {
return (Dispatcher)instance.get();
}
public static void setInstance(Dispatcher instance) {
Dispatcher.instance.set(instance);
}
public void init() {
// 初始化configurationManager
if (this.configurationManager == null) {
this.configurationManager = this.createConfigurationManager("struts");
}
try {
// 初始化各种形式配置加载方式
this.init_FileManager();
this.init_DefaultProperties();
this.init_TraditionalXmlConfigurations();
this.init_LegacyStrutsProperties();
this.init_CustomConfigurationProviders();
this.init_FilterInitParameters();
this.init_AliasStandardObjects();
// 初始化容器
Container container = this.init_PreloadConfiguration();
container.inject(this);
// 初始化weblogic服务器的特殊设置和指定DispatcherListener的逻辑
this.init_CheckWebLogicWorkaround(container);
if (!dispatcherListeners.isEmpty()) {
Iterator i$ = dispatcherListeners.iterator();
while(i$.hasNext()) {
DispatcherListener l = (DispatcherListener)i$.next();
l.dispatcherInitialized(this);
}
}
this.errorHandler.init(this.servletContext);
} catch (Exception var4) {
LOG.error("Dispatcher initialization failed", var4);
throw new StrutsException(var4);
}
}
首先利用ThreadLocal承载实际类型是Dispatcher本身的instance,保证线程安全
初始化过程的步骤如下
- 创建ConfigurationManager
- 初始化各种配置加载器
- 初始化Struts2默认Properties配置文件加载器
- 初始化XML配置加载器
- 初始化Properties配置加载器
- 初始化用户自定义配置加载器
- 初始化由web.xml中传入的运行参数
- 初始化默认容器内置对象加载起
- 初始化容器(容器知识上一篇有讲)
- 创建容器
- 对容器进行依赖注入
- 执行额外的初始化工作
- 初始化Weblogic配置选项
- 执行DispatcherListener的逻辑
Dispatcher还有更多的功能作用,我们在下面说明
0x01 Struts2的请求处理主线
我们在前面的文章中说到过,Struts2的请求处理主线分成两个阶段
- 第一阶段:HTTP请求预处理,为真正的业务逻辑执行做必要的数据环境和运行环境的准备
- 第二阶段:XWork执行业务逻辑,Struts2完成HTTP请求预处理后将HTTP请求中的数据封装成普通的Java对象,程序控制权移交给了XWork,由XWord负责执行具体的业务逻辑,这一阶段完全不依赖于Web容器,是为了消除核心程序对运行环境(Web容器)的依赖
HTTP请求预处理阶段
public class StrutsPrepareAndExecuteFilter implements StrutsStatics, Filter {
// 执行HTTP请求预处理的操作类
protected PrepareOperations prepare;
// 执行HTTP请求处理的逻辑执行的操作类
protected ExecuteOperations execute;
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
try {
String uri = RequestUtils.getUri(request);
if (this.excludedPatterns != null && this.prepare.isUrlExcluded(request, this.excludedPatterns)) {
LOG.trace("Request {} is excluded from handling by Struts, passing request to other filters", new Object[]{uri});
chain.doFilter(request, response);
} else {
LOG.trace("Checking if {} is a static resource", new Object[]{uri});
boolean handled = this.execute.executeStaticResourceRequest(request, response);
if (!handled) {
LOG.trace("Assuming uri {} as a normal action", new Object[]{uri});
// 设置Encoding和Locale
this.prepare.setEncodingAndLocale(request, response);
// 创建Action执行所对应的ActionContext
this.prepare.createActionContext(request, response);
// 把核心分发器Dispatcher分配至当前线程
this.prepare.assignDispatcherToThread();
// 对request进行一定封装
request = this.prepare.wrapRequest(request);
// 根据request获取ActionMapping
ActionMapping mapping = this.prepare.findActionMapping(request, response, true);
if (mapping == null) {
LOG.trace("Cannot find mapping for {}, passing to other filters", new Object[]{uri});
chain.doFilter(request, response);
} else {
LOG.trace("Found mapping {} for {}", new Object[]{mapping, uri});
// 执行URL请求的处理
this.execute.executeAction(request, response, mapping);
}
}
}
} finally {
this.prepare.cleanupRequest(request);
}
}
}
- Dispatcher:核心分发类,执行Struts2处理逻辑的实际场所
- PrepareOperations:HTTP预处理类,对所有HTTP请求执行预处理
- ExecuteOperations:HTTP处理执行类,执行HTTP请求的场所
在进行URL处理时,doFilter方法会根据指责不同,将URL处理过程分配给PrepareOperations和ExecuteOperations来分别完成,不过在这里,PrepareOperations和ExecuteOperations实际上也只是一层薄薄的代理层,其中起决定性作用的还是Dispatcher
PrepareOperations(HTTP请求预处理类)
我们看PrepareOperations所执行的一些
public class PrepareOperations {
public void setEncodingAndLocale(HttpServletRequest request, HttpServletResponse response) {
this.dispatcher.prepare(request, response);
}
public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) {
Integer counter = 1;
Integer oldCounter = (Integer)request.getAttribute("__cleanup_recursion_counter");
if (oldCounter != null) {
counter = oldCounter + 1;
}
ActionContext oldContext = ActionContext.getContext();
ActionContext ctx;
if (oldContext != null) {
ctx = new ActionContext(new HashMap(oldContext.getContextMap()));
} else {
ValueStack stack = ((ValueStackFactory)this.dispatcher.getContainer().getInstance(ValueStackFactory.class)).createValueStack();
stack.getContext().putAll(this.dispatcher.createContextMap(request, response, (ActionMapping)null));
ctx = new ActionContext(stack.getContext());
}
request.setAttribute("__cleanup_recursion_counter", counter);
ActionContext.setContext(ctx);
return ctx;
}
public void assignDispatcherToThread() {
Dispatcher.setInstance(this.dispatcher);
}
public HttpServletRequest wrapRequest(HttpServletRequest oldRequest) throws ServletException {
HttpServletRequest request = oldRequest;
try {
request = this.dispatcher.wrapRequest(request);
ServletActionContext.setRequest(request);
return request;
} catch (IOException var4) {
throw new ServletException("Could not wrap servlet request with MultipartRequestWrapper!", var4);
}
}
public ActionMapping findActionMapping(HttpServletRequest request, HttpServletResponse response, boolean forceLookup) {
ActionMapping mapping = (ActionMapping)request.getAttribute("struts.actionMapping");
if (mapping == null || forceLookup) {
try {
mapping = ((ActionMapper)this.dispatcher.getContainer().getInstance(ActionMapper.class)).getMapping(request, this.dispatcher.getConfigurationManager());
if (mapping != null) {
request.setAttribute("struts.actionMapping", mapping);
}
} catch (Exception var6) {
this.dispatcher.sendError(request, response, 500, var6);
}
}
return mapping;
}
我们按照doFilter的顺序列出了上面代码
- setEncodingAndLocale:设置Encoding和Locale,封装了Dispatcher对HttpServletRequest的设置,只是做一个转发
- createActionContext:生成ActionContext和ValueStack,将HttpServletRequest、HttpServletResponse中的数据封装成普通的Java对象,这是Struts2将MVC实现与Web容器解耦的第一步
- assignDispatcher:将核心分发器Dispatcher绑定至当前线程,因为Dispatcher是ThreadLocal模式的,这一步是保证当前Dispatcher是线程安全实例,可以放心使用
- wrapRequest:对HttpServletRequest进行一定封装,HttpServletRequest经过这一步处理,已经不是Web容器默认的实现了,而是被装饰成一个包装类
- findActionMapping:根据HTTP请求查找ActionMapping,ActionMapping是URL Mapping和Java对象对应的一个组建类,实际是ActionMapper实现类在运行期间查找对应事件映射关系并生成ActionMapping对象
ExecuteOperations(HTTP请求的执行类)
public class ExecuteOperations {
private Dispatcher dispatcher;
public ExecuteOperations(Dispatcher dispatcher) {
this.dispatcher = dispatcher;
}
public boolean executeStaticResourceRequest(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
// 查找静态资源根路径
String resourcePath = RequestUtils.getServletPath(request);
if ("".equals(resourcePath) && null != request.getPathInfo()) {
resourcePath = request.getPathInfo();
}
// 获取静态资源的加载处理类
StaticContentLoader staticResourceLoader = (StaticContentLoader)this.dispatcher.getContainer().getInstance(StaticContentLoader.class);
// 判断静态资源加载处理类是否能处理URL对应的静态资源
if (staticResourceLoader.canHandle(resourcePath)) {
staticResourceLoader.findStaticResource(resourcePath, request, response);
return true;
} else {
return false;
}
}
public void executeAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws ServletException {
// 将所有参数传入dispatcher中执行逻辑
this.dispatcher.serviceAction(request, response, mapping);
}
}
ExecuteOperations的代码很简单,可以全部看一下
和PrepareOperations一样,ExecuteOperation基本只是一个执行句柄,所有逻辑还是围绕Dispatcher展开
我们看Dispatcher的serviceAction方法
public void serviceAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws ServletException {
// 创建MVC运行的数据环境
Map<String, Object> extraContext = this.createContextMap(request, response, mapping);
// 如果之前创建过ValueStack,则直接做一个ValueStack的复制
ValueStack stack = (ValueStack)request.getAttribute("struts.valueStack");
boolean nullStack = stack == null;
// 没有找到已存在的ValueStack则从ActionContext中获取当前线程的ValueStack
if (nullStack) {
ActionContext ctx = ActionContext.getContext();
if (ctx != null) {
// ActionContext是线程安全的,所以这里获取的是当前线程的ValueStack
stack = ctx.getValueStack();
}
}
// 将ValueStack设置到数据环境中
if (stack != null) {
extraContext.put("com.opensymphony.xwork2.util.ValueStack.ValueStack", this.valueStackFactory.createValueStack(stack));
}
String timerKey = "Handling request from Dispatcher";
try {
UtilTimerStack.push(timerKey);
// 从ActionMapping获得Action的三大要素
String namespace = mapping.getNamespace();
String name = mapping.getName();
String method = mapping.getMethod();
// 创建一个ActionProxy,这里就进入了XWork的世界了
ActionProxy proxy = ((ActionProxyFactory)this.getContainer().getInstance(ActionProxyFactory.class)).createActionProxy(namespace, name, method, extraContext, true, false);
request.setAttribute("struts.valueStack", proxy.getInvocation().getStack());
// 如果ActionMapping中包含Result对象,表明直接跳过Action执行Result
if (mapping.getResult() != null) {
// 执行ActionProxy,真正运行XWork中的MVC实现
Result result = mapping.getResult();
result.execute(proxy.getInvocation());
} else {
proxy.execute();
}
// 如果之前存在ValueStack,重新设回Reqeust对象中
if (!nullStack) {
request.setAttribute("struts.valueStack", stack);
}
} catch (ConfigurationException var17) {
this.logConfigurationException(request, var17);
this.sendError(request, response, 404, var17);
} catch (Exception var18) {
if (!this.handleException && !this.devMode) {
throw new ServletException(var18);
}
this.sendError(request, response, 500, var18);
} finally {
UtilTimerStack.pop(timerKey);
}
}
所以XWork框架的相关逻辑实际上由Dispatcher创建并负责驱动执行
Dispatcher负责HTTP请求不同处理阶段的数据转发工作,从而保证代码在不同执行阶段的无缝切换
在XWork框架调用接口ActionProxy中,我们将不再看到任何与Web容器相关的对象
因此,Dispatcher是真正将HTTP请求与MVC实现(XWork框架)分离的核心分发器,而Dispatcher的逻辑,被分散到PrepareOperations和ExecuteOperations两个代理类中执行调度,保证了程序的扩展性
到此为止,Struts2的工作完成,所有剩下的HTTP请求处理工作交给XWork
XWork处理阶段
Dispatcher的serviceAction方法是Dispatcher处理HTTP的场所,也是这个方法中,Struts2将处理执行权转交给XWork
Struts2的执行移交过程实际上围绕这ActionProxy来进行,并使用了Struts2的一贯方法,先初始化在执行逻辑
Dispatcher中ActionProxy proxy = ((ActionProxyFactory)this.getContainer().getInstance(ActionProxyFactory.class)).createActionProxy(namespace, name, method, extraContext, true, false);
一行带过了ActionProxy的初始化过程,其实这一过程,也是XWork执行环境的初始化
ActionProxy的输入是配置关系映射(namespace、actionName、methodName)和运行上下文环境(extraContext),也就是ActionProxy
从DefaultActionProxy中找到createActionProxy方法
public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map<String, Object> extraContext, boolean executeResult, boolean cleanupContext) {
ActionInvocation inv = createActionInvocation(extraContext, true);
container.inject(inv);
return createActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);
}
这里创建了ActionInvocation,ActionInvocation的init方法中有createContextMap
protected Map<String, Object> createContextMap() {
Map contextMap;
if (this.extraContext != null && this.extraContext.containsKey("com.opensymphony.xwork2.util.ValueStack.ValueStack")) {
this.stack = (ValueStack)this.extraContext.get("com.opensymphony.xwork2.util.ValueStack.ValueStack");
if (this.stack == null) {
throw new IllegalStateException("There was a null Stack set into the extra params.");
}
contextMap = this.stack.getContext();
} else {
this.stack = this.valueStackFactory.createValueStack();
contextMap = this.stack.getContext();
}
if (this.extraContext != null) {
contextMap.putAll(this.extraContext);
}
contextMap.put("com.opensymphony.xwork2.ActionContext.actionInvocation", this);
contextMap.put("com.opensymphony.xwork2.ActionContext.container", this.container);
return contextMap;
}
可以看到,ActionInvocation初始化时,对ActionProxy进行了一次检查并重置,这里考虑到Struts2作为外部调用,XWork独立的情况
再来看ActionInvocation的完整初始化过程
public void init(ActionProxy proxy) {
this.proxy = proxy;
// 创建上下文环境,contextMap和ActionContext一致
Map<String, Object> contextMap = this.createContextMap();
// 将ActionInvocation对象设置到ActionContext中,可以利用ActionContext的数据共享特性,将ActionInvocation在整个执行周期共享
ActionContext actionContext = ActionContext.getContext();
if (actionContext != null) {
actionContext.setActionInvocation(this);
}
// 创建Action对象
this.createAction(contextMap);
// 将Action对象置于ValueStack中,这是将XWork的数据流元素与控制流元素进行融合的关键步骤
if (this.pushAction) {
this.stack.push(this.action);
contextMap.put("action", this.action);
}
// 构建ActionInvocation的上下文环境
this.invocationContext = new ActionContext(contextMap);
this.invocationContext.setName(proxy.getActionName());
this.createInterceptors(proxy);
}
ActionInvocation初始化之后调用invoke执行,这里我们上一篇文章分析过了,最后调用invokeAction的时候
protected String invokeAction(Object action, ActionConfig actionConfig) throws Exception {
String methodName = proxy.getMethod();
LOG.debug("Executing action method = {}", methodName);
String timerKey = "invokeAction: " + proxy.getActionName();
try {
UtilTimerStack.push(timerKey);
Object methodResult;
try {
methodResult = ognlUtil.callMethod(methodName + "()", getStack().getContext(), action);
} catch (MethodFailedException e) {
// if reason is missing method, try checking UnknownHandlers
if (e.getReason() instanceof NoSuchMethodException) {
if (unknownHandlerManager.hasUnknownHandlers()) {
try {
methodResult = unknownHandlerManager.handleUnknownMethod(action, methodName);
} catch (NoSuchMethodException ignore) {
// throw the original one
throw e;
}
} else {
// throw the original one
throw e;
}
// throw the original exception as UnknownHandlers weren't able to handle invocation as well
if (methodResult == null) {
throw e;
}
} else {
// exception isn't related to missing action method, throw it
throw e;
}
}
return saveResult(actionConfig, methodResult);
} catch (NoSuchPropertyException e) {
throw new IllegalArgumentException("The " + methodName + "() is not defined in action " + getAction().getClass() + "");
} catch (MethodFailedException e) {
// We try to return the source exception.
Throwable t = e.getCause();
if (actionEventListener != null) {
String result = actionEventListener.handleException(t, getStack());
if (result != null) {
return result;
}
}
if (t instanceof Exception) {
throw (Exception) t;
} else {
throw e;
}
} finally {
UtilTimerStack.pop(timerKey);
}
}
执行Action的方法后执行Result的execute,生成Result