首先是简单的例子:
public class DirectAction {
public String execute(){
System.out.println("cross the street");
return "success";
}
}
<struts>
<constant name="struts.action.excludePattern" value="/beckham/.*"></constant>
<!-- 默认执行这个package -->
<package name="defaultfuck" namespace="/path" extends="struts-default">
<action name="direct" class="com.tb.struts2.sourceAnalysis.chap1.DirectAction">
<result name="success">/ok.jsp</result>
</action>
</package>
</struts>
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<filter>
<filter-name>struts2</filter-name>
<filter-class>
org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
******************************
1. 根据filter的url匹配模式过滤请求过来的每个地址,进入到StrutsPrepareAndExecuteFilter类的doFilter方法。
try {
/* 设置编码国际化信息 */
prepare.setEncodingAndLocale(request, response);
/* 为每个action设置对应的上下文环境 */
prepare.createActionContext(request, response);
/* 保证分发器dispatcher的线程安全,使用了ThreadLocal */
prepare.assignDispatcherToThread();
/* 在配置文件中声明了要struts2不去处理哪些路径
* 例如这样<constant name="struts.action.excludePattern" value="/beckham/.*"></constant>
* 意思是struts2将不负责处理这样的路径
*/
if ( excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
chain.doFilter(request, response);
} else {
request = prepare.wrapRequest(request);
/*
* 取得对应的请求在struts2配置文件中的对应信息
* 比如命名空间, action名称。 参数, 调用方法名称,后缀名等等。
*/
ActionMapping mapping = prepare.findActionMapping(request, response, true);
if (mapping == null) {
/*
* 这里有个问题,如果web.XML中配置成这样<url-pattern>/*</url-pattern> 那么其实所有的连接都会匹配
* 哪怕是简单的<a href="a.jsp" mce_href="a.jsp">aaa</a>, 有一定效率的影响啊。
*/
boolean handled = execute.executeStaticResourceRequest(request, response);
if (!handled) {
/* struts2不处理,直接交给后面的filter或者直接映射 */
chain.doFilter(request, response);
}
} else {
/* 一般的action都经过这里,进行下一步,重要!!!! */
execute.executeAction(request, response, mapping);
}
}
} finally {
/* 页面跳转完成,对请求信息进行清理。对应的action上下文环境清空,threadLocal清空。 */
prepare.cleanupRequest(request);
}
}
2. execute.executeAction(request, response, mapping);方法带路到了Dispatcher类。
/* 把所有的程序属性和servlet属性合并起来装到一个大的HashMap里边 */
Map<String, Object> extraContext = createContextMap(request, response, mapping, context);
/* 试着获取对应的值栈,如果没有获取到,则创建一个新的值栈 */
ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);
boolean nullStack = stack == null;
if (nullStack) {
ActionContext ctx = ActionContext.getContext();
if (ctx != null) {
stack = ctx.getValueStack();
}
}
if (stack != null) {
extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.createValueStack(stack));
}
String timerKey = "Handling request from Dispatcher";
try {
UtilTimerStack.push(timerKey);
/* 从命名空间获得对应的命名空间,action名称,方法名称 */
String namespace = mapping.getNamespace();
String name = mapping.getName();
String method = mapping.getMethod();
Configuration config = configurationManager.getConfiguration();
/* 这一步想当关键!!!!重要!!!!获得对应的action代理 标注1 */
ActionProxy proxy = config.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 {
/* 一般简单的页面跳转会走到这一步,获得代理,去调用execute执行,这个方法也很重要 标注2 */
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) {
// WW-2874 Only log error if in devMode
if(devMode) {
LOG.error("Could not find action or result", e);
}
else {
LOG.warn("Could not find action or result", e);
}
sendError(request, response, context, HttpServletResponse.SC_NOT_FOUND, e);
} catch (Exception e) {
sendError(request, response, context, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
} finally {
UtilTimerStack.pop(timerKey);
}
}
OK,这个方法很重要,有2个标注点。
标注1:
这个prepare方法也很重要。
if (config == null && unknownHandlerManager.hasUnknownHandlers()) {
config = unknownHandlerManager.handleUnknownAction(namespace, actionName);
}
if (config == null) {
String message;
if ((namespace != null) && (namespace.trim().length() > 0)) {
message = LocalizedTextUtil.findDefaultText(XWorkMessages.MISSING_PACKAGE_ACTION_EXCEPTION, Locale.getDefault(), new String[]{
namespace, actionName
});
} else {
message = LocalizedTextUtil.findDefaultText(XWorkMessages.MISSING_ACTION_EXCEPTION, Locale.getDefault(), new String[]{
actionName
});
}
throw new ConfigurationException(message);
}
/* 这里解析了应该去调用哪个方法。如果没有指定,则默认去调用execute方法, 记住,这里只是指定方法,并不是调用,真正的action还没有实例化 */
resolveMethod();
if (!config.isAllowedMethod(method)) {
throw new ConfigurationException("Invalid method: "+method+" for action "+actionName);
}
/* 涉及到另一个类DefaultActionInvocation,负责对代理类进行调用,处理 */
invocation.init(this);
} finally {
UtilTimerStack.pop(profileKey);
}
}
来看init方法:
// 获得对应的action环境
ActionContext actionContext = ActionContext.getContext();
if (actionContext != null) {
actionContext.setActionInvocation(this);
}
/* 实例化action!! */
createAction(contextMap);
if (pushAction) {
stack.push(action);
contextMap.put("action", action);
}
/* 设置 ActionContext */
invocationContext = new ActionContext(contextMap);
invocationContext.setName(proxy.getActionName());
/* 拦截器的信息处理 */
List<InterceptorMapping> interceptorList = new ArrayList<InterceptorMapping>(proxy.getConfig().getInterceptors());
interceptors = interceptorList.iterator();
}
标注2 , 代理类的execute执行然后到了DefaultActionInvokcation 的 invoke方法
if (executed) {
throw new IllegalStateException("Action has already executed");
}
if (interceptors.hasNext()) {
final InterceptorMapping interceptor = (InterceptorMapping) interceptors.next();
String interceptorMsg = "interceptor: " + interceptor.getName();
UtilTimerStack.push(interceptorMsg);
try {
/* 这块理解的不是很透彻,incerpet返回的是intercept.invoke(),所以看起来是if语句,其实做了循环,遍历完了所有的拦截器 */
resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
}
finally {
UtilTimerStack.pop(interceptorMsg);
}
} else {
/* 这个else最终还是会被执行到,当遍历完所有的拦截器后 */
resultCode = invokeActionOnly();
}
// this is needed because the result will be executed, then control will return to the Interceptor, which will
// return above and flow through again
if (!executed) {
if (preResultListeners != null) {
for (Object preResultListener : preResultListeners) {
PreResultListener listener = (PreResultListener) preResultListener;
String _profileKey = "preResultListener: ";
try {
UtilTimerStack.push(_profileKey);
listener.beforeResult(this, resultCode);
}
finally {
UtilTimerStack.pop(_profileKey);
}
}
}
// 处理结果,去向那个页面!
if (proxy.getExecuteResult()) {
executeResult();
}
executed = true;
}
return resultCode;
}
finally {
UtilTimerStack.pop(profileKey);
}
}
同个类下的invokeActionOnly方法经过一次中转到了invokeAction方法
if (LOG.isDebugEnabled()) {
LOG.debug("Executing action method = " + actionConfig.getMethodName());
}
String timerKey = "invokeAction: " + proxy.getActionName();
try {
UtilTimerStack.push(timerKey);
boolean methodCalled = false;
Object methodResult = null;
Method method = null;
try {
/* 反射获得对应的要执行的Method类 */
method = getAction().getClass().getMethod(methodName, EMPTY_CLASS_ARRAY);
} catch (NoSuchMethodException e) {
// hmm -- OK, try doXxx instead
try {
String altMethodName = "do" + methodName.substring(0, 1).toUpperCase() + methodName.substring(1);
method = getAction().getClass().getMethod(altMethodName, EMPTY_CLASS_ARRAY);
} catch (NoSuchMethodException e1) {
// well, give the unknown handler a shot
if (unknownHandlerManager.hasUnknownHandlers()) {
try {
methodResult = unknownHandlerManager.handleUnknownMethod(action, methodName);
methodCalled = true;
} catch (NoSuchMethodException e2) {
// throw the original one
throw e;
}
} else {
throw e;
}
}
}
if (!methodCalled) {
/* 执行action中的方法! */
methodResult = method.invoke(action, new Object[0]);
}
/* 如果是Result的子类,则对应的处理,一般都是JSP,且methodResult这个值一般会是SUCCESS,ERROR,INPUT什么的。所有转else */
if (methodResult instanceof Result) {
this.explicitResult = (Result) methodResult;
// Wire the result automatically
container.inject(explicitResult);
return null;
} else {
/* 返回 */
return (String) methodResult;
}
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException("The " + methodName + "() is not defined in action " + getAction().getClass() + "");
} catch (InvocationTargetException e) {
// We try to return the source exception.
Throwable t = e.getTargetException();
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);
}
}
一切都处理就绪,剩下的就是对于对应的页面进行跳转。通过多次调用到ServletDispatcherResult类,这个类主要负责基本的页面跳转
PageContext pageContext = ServletActionContext.getPageContext();
if (pageContext != null) {
pageContext.include(finalLocation);
} else {
HttpServletRequest request = ServletActionContext.getRequest();
HttpServletResponse response = ServletActionContext.getResponse();
/* 给他想了个名字 叫 请求分发器,也就是根据页面他负责把页面的路径拿来然后给你请求的页面 */
RequestDispatcher dispatcher = request.getRequestDispatcher(finalLocation);
/* 在有参数的情况下的处理方式,暂时第一个helloworld没带例子 以后再说 */
if (invocation != null && finalLocation != null && finalLocation.length() > 0
&& finalLocation.indexOf("?") > 0) {
String queryString = finalLocation.substring(finalLocation.indexOf("?") + 1);
Map parameters = (Map) invocation.getInvocationContext().getContextMap().get("parameters");
Map queryParams = UrlHelper.parseQueryString(queryString, true);
if (queryParams != null && !queryParams.isEmpty())
parameters.putAll(queryParams);
}
// 如果请求的页面不存在, 页面到404 你懂的
if (dispatcher == null) {
response.sendError(404, "result '" + finalLocation + "' not found");
return;
}
/* 分发页面, 该去哪儿你去哪儿 */
if (!response.isCommitted() && (request.getAttribute("javax.servlet.include.servlet_path") == null)) {
request.setAttribute("struts.view_uri", finalLocation);
request.setAttribute("struts.request_uri", request.getRequestURI());
dispatcher.forward(request, response);
} else {
dispatcher.include(request, response);
}
}
}
==============================================================
简略为如下流程:
--------------------------------------------------------
初始化信息
过滤器
值栈处理
获得Action代理
初始化代理类(StrutsActionProxy)
注入信息(@Inject 如果有)
准备信息
指定调用action中哪个方法
实例化action
执行代理
拦截器执行
执行action中已经指定好的方法
页面跳转
清空环境
------------------------------------------------------
到此一个页面的简单的跳转的过程就OVER了,暂时没涉及到过多的东西。在看代码时候发现2个问题,经常看到struts2的作者频繁取出action上下文环境或者又放进去。。。这块不是很明白。
还有对于将信息放在Map和request频繁取出读取的问题,这块也值得探究。不足之处肯定很多,欢迎各位指出,小弟感激不尽。共同学习。