struts2流程及源码分析
struts 架构图
分析这个架构图,我们可以从4个部分,也就struts访问的4个阶段的流程来分析
这4个阶段包括:Action映射、Action转发、Action执行、结果返回
首先是Action映射阶段
当请求到来的时候,首先是struts的核心过滤器接收到请求,然后通过ActionMapper进行映射
我们以下图struts配置为例,查看一下struts在处理这个请求阶段的过程:
在StrutsPrepareAndExecuteFilter
源码中,它本质是一个过滤器,核心的代码在doFilter
部分,我们把这部分代码copy过来如下:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
try {
if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
chain.doFilter(request, response);
} else {
prepare.setEncodingAndLocale(request, response);
prepare.createActionContext(request, response);
prepare.assignDispatcherToThread();
request = prepare.wrapRequest(request);
ActionMapping mapping = prepare.findActionMapping(request, response, true);
if (mapping == null) {
boolean handled = execute.executeStaticResourceRequest(request, response);
if (!handled) {
chain.doFilter(request, response);
}
} else {
execute.executeAction(request, response, mapping);
}
}
} finally {
prepare.cleanupRequest(request);
}
}
判断是否需要struts处理
当请求过来的时候,首先判断是否应该由struts处理这个请求,如果不是那就应该放行
我们这个请求肯定是要经过struts处理的,所以应该走else部分:
创建数据中心ActionContext
接下来需要关注的是这行
prepare.createActionContext(request, response);
这是干什么呢?从名字上看,就是在创建一个ActionContext数据中心
还记得吗?这个就是那个包含原生servlet 11个域对象还有值栈的一个大容器(本质上就是map)
可以进去看下这部分的源码:
/**
* Creates the action context and initializes the thread local
*/
public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) {
ActionContext ctx;
Integer counter = 1;
Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER);
if (oldCounter != null) {
counter = oldCounter + 1;
}
ActionContext oldContext = ActionContext.getContext();
if (oldContext != null) {
// detected existing context, so we are probably in a forward
ctx = new ActionContext(new HashMap<String, Object>(oldContext.getContextMap()));
} else {
ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();
stack.getContext().putAll(dispatcher.createContextMap(request, response, null));
ctx = new ActionContext(stack.getContext());
}
request.setAttribute(CLEANUP_RECURSION_COUNTER, counter);
ActionContext.setContext(ctx);
return ctx;
}
在初始化阶段,关键在这里:
这三行代码,简单来看,第一步就是创建值栈,第二步就是往Context中准备一些数据
最后看第三步,可以看到ActionContext这个数据中心实际上就是ValueStack的Context
增强包装request对象
接下来关注的是91行的这句:
在还没运行阶段,这个request还是原生的org.apache.catalina.connector.RequestFacade
类型
执行完后就变成了org.apache.struts2.dispatcher.StrutsRequestWrapper
类型了,这个就是由struts包装的request对象
那struts包装是做了哪些事情呢,可以勘测一下StrutsRequestWrapper
源码
其实它就包装(增强)了一个方法getAttribute
可以看下这部分的代码:
/**
* Gets the object, looking in the value stack if not found
*
* @param key The attribute key
*/
public Object getAttribute(String key) {
if (key == null) {
throw new NullPointerException("You must specify a key value");
}
if (disableRequestAttributeValueStackLookup || key.startsWith("javax.servlet")) {
// don't bother with the standard javax.servlet attributes, we can short-circuit this
// see WW-953 and the forums post linked in that issue for more info
return super.getAttribute(key);
}
ActionContext ctx = ActionContext.getContext();
Object attribute = super.getAttribute(key);
if (ctx != null && attribute == null) {
boolean alreadyIn = isTrue((Boolean) ctx.get(REQUEST_WRAPPER_GET_ATTRIBUTE));
// note: we don't let # come through or else a request for
// #attr.foo or #request.foo could cause an endless loop
if (!alreadyIn && !key.contains("#")) {
try {
// If not found, then try the ValueStack
ctx.put(REQUEST_WRAPPER_GET_ATTRIBUTE, Boolean.TRUE);
ValueStack stack = ctx.getValueStack();
if (stack != null) {
attribute = stack.findValue(key);
}
} finally {
ctx.put(REQUEST_WRAPPER_GET_ATTRIBUTE, Boolean.FALSE);
}
}
}
return attribute;
}
内容很长,但简单来说就是注释上的那么一句话:
Gets the object, looking in the value stack if not found
翻译过来那就是:这个方法会先从原生request域中去找,如果找不到的话,就会往值栈中去找
前面说过ValueStack包含两个部分,一个是ROOT栈部分,一个是context部分(从源码上来看,这个部分就是ActionContext)。
那么拆解开来看,我们可以得到以下结论:
当我们在struts中调用原生的request.getAttribute()方法时:struts会帮我们从以下几个域中依次寻找属性
- 原生request域
- ValueStack的栈部分
- ValueStack的context部分(ActionContext)
我们看一下源码当中这个过程的体现:
很明显这一步就是从request域中去找,如果找不到就往下:
上面的那段注释告诉我们,使用request来寻找ActionContext内容是不需要和OGNL表达式一样带#来访问的,并且也不允许(否则会陷入死循环)
简单来说,原生的怎么写就怎么写,不需要管里边的具体实现
ActionMapping
最后它要做的操作就是把请求转换为ActionMapping对象,好方便接下来后期处理,
这个过程简单来说就是把我们访问的url地址
http://localhost/strutsLearn/customer/list
解析成
ActionMapping{name='list', namespace='/customer', method='null', extension='null', params=null, result=null}
这种形式的对象
上面的形式是可以解析出来的情况,但是我们知道每一次请求也肯定有很多静态资源文件,这些都是struts解析不了的,这时候mapping得到的结果就为null
到这里,也就是我们第一阶段所要完成的任务了,就是通过ActionMapper工具获取ActionMapping对象。
那么再往下,就是把ActionMapping交给下一个阶段往下执行了。
execute.executeAction(request, response, mapping);
核心代理转发阶段——创建ActionProxy
这个方法只是一个中间方法,它再具体调用下一个
dispatcher.serviceAction(request, response, mapping);
在这里,关键部分是try catch语句中:
try {
UtilTimerStack.push(timerKey);
String namespace = mapping.getNamespace();
String name = mapping.getName();
String method = mapping.getMethod();
ActionProxy proxy = getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
namespace, name, method, extraContext, true, false);
request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());
// if the ActionMapping says to go straight to a result, do it!
if (mapping.getResult() != null) {
Result result = mapping.getResult();
result.execute(proxy.getInvocation());
} else {
proxy.execute();
}
// If there was a previous value stack then set it back onto the request
if (!nullStack) {
request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
}
} catch (ConfigurationException e) {
logConfigurationException(request, e);
sendError(request, response, HttpServletResponse.SC_NOT_FOUND, e);
} catch (Exception e) {
if (handleException || devMode) {
sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
} else {
throw new ServletException(e);
}
} finally {
UtilTimerStack.pop(timerKey);
}
首先是提取ActionMapping中的各种信息:
接下来就是创建ActionProxy对象了:
ActionProxy proxy = getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
namespace, name, method, extraContext, true, false);
在这里如果深入去看proxy创建过程,其实就包含了根据actionMapping的信息去寻找配置文件struts.xml配置项的过程。这里就不再展开了。
可以看下ActionProxy大概长啥样
那么创建出来后就需要分派给人去执行了,但是执行分两种情况
这两种其实都一样,我们这里是第一次初始访问,没有结果返回,因此直接进入的是
proxy.execute();
public String execute() throws Exception {
ActionContext previous = ActionContext.getContext();
ActionContext.setContext(invocation.getInvocationContext());
try {
// This is for the new API:
// return RequestContextImpl.callInContext(invocation, new Callable<String>() {
// public String call() throws Exception {
// return invocation.invoke();
// }
// });
return invocation.invoke();
} finally {
if (cleanupContext)
ActionContext.setContext(previous);
}
}
这个方法里,即将进入的就是 invoke 方法了!
return invocation.invoke();
Action执行阶段——拦截器执行
调用这个方法,也就意味着进入第三阶段——Aciton执行阶段了
在这个阶段,我们知道它即将进入执行N多个拦截器,我们进入看看它里面的关键代码:
if (interceptors.hasNext()) {
final InterceptorMapping interceptor = interceptors.next();
String interceptorMsg = "interceptor: " + interceptor.getName();
UtilTimerStack.push(interceptorMsg);
try {
resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
}
finally {
UtilTimerStack.pop(interceptorMsg);
}
} else {
resultCode = invokeActionOnly();
}
其中interceptors被定义成拦截器容器的迭代器
protected Iterator<InterceptorMapping> interceptors;
这个拦截器容器就是Proxy阶段传过来的
这其中一行代码第一眼看上去确实让人奇怪:
if (interceptors.hasNext())
通常对迭代器的循环应该是while这里为什么变成if 呢
先不管,继续往下看:
首先是获取拦截器容器中的拦截器和拦截器名称:
final InterceptorMapping interceptor = interceptors.next();
String interceptorMsg = "interceptor: " + interceptor.getName();
UtilTimerStack.push(interceptorMsg);
这个拦截器的类型是:
Exception拦截器
和struts-default配置拦截器栈的第一个拦截器是一样的:
到这里,看似真的是要遍历
接下来断定肯定去执行这个拦截器里的内容
resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
获取到具体的拦截器,调用拦截器的intercept方法,注意到的是这里把this作为参数穿进去了。
我们看看这里面做了什么
结果一进来就做了这么一件事情:
result = invocation.invoke();
这时候就得出结论了:
invocation的invoke方法调用interceptor的intercept方法
接着反过来interceptor的intercept方法又调回invocation的invoke方法
这不就形成了另一种形式的递归吗?
所以接下来又回到invocation的invoke方法执行
那个迭代器的结束条件if
if (interceptors.hasNext())
就没什么奇怪了。
但是为什么要这么设计递归了,普通的遍历不是挺好的吗?接着往下看。
执行完一轮后,再看拦截器的名字:
就是Exception的下一个拦截器alias
这个拦截器的invoke方法进行了比较多的复杂处理,但是不管结果如何,最终都会调用:
return invocation.invoke();
@Override public String intercept(ActionInvocation invocation) throws Exception {
....... 此处省略n行代码
return invocation.invoke();
}
到这里,这下明白了这么设计的用意何在了吧。
每个拦截器的invoke方法执行的方式不一样,通过这种间接递归的方式就能把所有不同功能的器件全部执行一遍了。
看完后,不得不说,这种设计的精妙之处啊
Action执行
拦截器执行完成后接下来就到:
resultCode = invokeActionOnly();
只执行Action了
接下来可以推测,它就是去找到我们自己写的Action中的类去执行去了。
跳出这个过程,我们可以看到resultCode返回结果:
那么可以猜想,接下来就会拿着这个结果根据我们定义的配置文件处理去,也就是进入我们所说的第四阶段了
接下来就会去执行结果处理函数
// now execute the result, if we're supposed to
if (proxy.getExecuteResult()) {
executeResult();
}
好了,到这里先告一段落,关于后续的结果处理函数后期再补充吧