首先给出struts2的原理图:
1)客户端初始化一个指向Servlet容器(例如Tomcat)的请求 (客户端提交一个HttpServletRequest请求。)
(2)请求被提交到一系列的过滤器(Filter)。
如(ActionContextCleanUp、其余过滤器(SiteMesh等)、 FilterDispatcher。注意:这里是有顺序的,先ActionContext CleanUp,再其余过滤器(Othter Filters、SiteMesh等),最后到FilterDispatcher。
FilterDispatcher是控制器的核心,就是MVC的Struts 2实现中控制层(Controller)的核心。
(3)FilterDispatcher询问ActionMapper是否须要调用某个Action来处理这个(HttpServlet Request)请求,若是ActionMapper决定须要调用某个Action,FilterDispatcher则把请求的处理交给ActionProxy。
(4) ActionProxy经过Configuration Manager(struts.xml)询问框架的配置文件,找到须要调用的Action类。
(5)ActionProxy建立一个ActionInvocation实例,同时ActionInvocation经过代理模式调用Action。但在调用以前,ActionInvocation会根据配置加载Action相关的全部Interceptor(拦截器)。
(6)Action执行完毕后,ActionInvocation负责根据struts.xml中的配置找到对应的返回结果result。
具体分析:
StrutsPrepareAndExecuteFilter过滤器的doFilter方法中的主要代码:java
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
try {
prepare.setEncodingAndLocale(request, response);
prepare.createActionContext(request, response);
prepare.assignDispatcherToThread();
if ( excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
chain.doFilter(request, response);
} else {
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);
}
}
}
}
在系统初始化的时候StrutsPrepareAndExecuteFilter的init方法被执行,实例化出了PrepareOperations和ExecuteOperations两个对象,第一个对象是对真正响应请求以前所做的一些准备操做封装,ExecuteOperations是对响应请求所做的封装,但其实这两个对象最终调用的都是核心分发器Dispatcher对象的方法。web
prepare.setEncodingAndLocale(request, response);这一句处理请求编码与响应Locale,其内部调用的就是Dipatcher的prepare方法;
if ( excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
chain.doFilter(request, response);
}该判断是由于struts2支持不包含的URL模式匹配,一个请求虽然进入了struts2过滤器,但若是该请求的URL在不包含之列的话sturts2只是单纯地调用chain.doFilter方法放行。其它状况就会进入else部分,调用Action处理请求。
request = prepare.wrapRequest(request);对HttpServletRequest进行包装,参看其源码就能够知道若是是文件上传把HttpServletRequest包装成了一个MultiPartRequestWrapper对象,该对象专门处理文件上传请求。若是不是文件上传进把HttpServletRequest包装成了StrutsRequestWrapper,StrutsRequestWrapper类覆盖了其父类的getAttribute方法,对该方法的行为做了一点修改,其父类即javax.servlet.http.HttpServletRequestWrapper中的getAttribute方法只是从request对象查找指定属性,而StrutsRequestWrapper的getAttribute方法是先在request对象中进行查找,若是没有找到还会去ValueStack中进行查找。浏览器
若是请求的静态页面就会执行execute.executeStaticResourceRequest(request, response);在方法内部会判断有无能力对该请求进行处理,若是有则处理没有则调用chain.doFilter放行,实现基本都同样就是原样返回给客户端了。缓存
真正会执行Action的是这一句:execute.executeAction(request, response, mapping);该方法调用的是Dispatcher的serviceAction方法,咱们进入该方法看看,下面是该方法中的重要代码:session
Configuration config = configurationManager.getConfiguration();
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 { 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); }
先获取Configuration对象,经过Configuration获得容器对象,再从容器中获取ActionProxyFactory,ActionProxy工厂类,而后建立ActionProxy,你们都知道struts2内部包装的是webwork框架,而ActionProxy正是sturts与webwork的分界线,ActionProxy是进行webwork框架的门户。app
struts2默认使用的是DefaultActionProxyFactory建立ActionProxy对象的,下面是其createActionProxy方法:框架
public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map<String, Object> extraContext, boolean executeResult, boolean cleanupContext) {
ActionInvocation inv = new DefaultActionInvocation(extraContext, true);
container.inject(inv);
return createActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);
}
public ActionProxy createActionProxy(ActionInvocation inv, String namespace, String actionName, String methodName, boolean executeResult, boolean cleanupContext) {
DefaultActionProxy proxy = new DefaultActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);
container.inject(proxy);
proxy.prepare();
return proxy;
}
这是两个重载的方法,第一个方法调用了第二个方法,在第一个方法中建立了ActionInvocation对象,并对其依赖的对象使用容器进行注入,紧接着就建立了ActionProxy对象,也对其依赖对象进行了注入,如ObjectFctory,Configuration对象等。而后调用proxy.prepare()方法,其中有一个resolveMethod();的方法,该方法很简单,就是若是在配置Action的时候没有指定method属性的时候会把method属性值赋为execute,这就是为何execute是Action默认的执行方法的缘由。还有一个很重要的方法叫invocation.init(this);即调用ActionInvocation的init方法,把ActionProxy本身传了进行,固然ActionInvocation中会对ActionProxy进行缓存。分布式
struts2对ActionInvocation的默认实现是DefaultActionInvocation类,进放该类的init方法,下面是该方法中重要的代码:svg
createAction(contextMap);
if (pushAction) {
stack.push(action);
contextMap.put("action", action);
}
invocationContext = new ActionContext(contextMap);
invocationContext.setName(proxy.getActionName());
// get a new List so we don't get problems with the iterator if someone changes the list
List<InterceptorMapping> interceptorList = new ArrayList<InterceptorMapping>(proxy.getConfig().getInterceptors());
interceptors = interceptorList.iterator();
第一句即建立Action,若是你们对struts2的对象建立有所了解的话就知道,Action,Result,Intercepter的建立都是由ObjectFactory的buildBean方法来建立的,其内部就是调用Class.newInstance();建立的,因此Action必定要有一个无参构造方法。
注意这句:stack.push(action);这里把建立的Action压进了ValuesStack中,这就是为何默认Action在栈顶的缘由。下面就是获取该Action配置的全部拦截器并进行缓存在ActionInvocation中,Action也是如此,由于Action与Interceptor的执行调度就是由ActionInvocation实现的。ui
proxy.execute();这一句执行ActionProxy的execute方法,struts2中ActionProxy的默认实现是StrutsActionProxy,下面进行该类的execute方法,源码以下:
public String execute() throws Exception {
ActionContext previous = ActionContext.getContext();
ActionContext.setContext(invocation.getInvocationContext());
try {
return invocation.invoke();
} finally {
if (cleanupContext)
ActionContext.setContext(previous);
}
}
代码很简单,就是调用ActionInvocation的invoke方法,执行拦截器与Action,下面是invoke方法的源码,因该方法中附属代码较多,这里只捡出了重要代码:
if (interceptors.hasNext()) {
final InterceptorMapping interceptor = (InterceptorMapping) interceptors.next();
String interceptorMsg = "interceptor: " + interceptor.getName();
UtilTimerStack.push(interceptorMsg);
try {
resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
}
finally {
UtilTimerStack.pop(interceptorMsg);
}
} else {
resultCode = invokeActionOnly();
}
//这里省略了一些代码...
if (proxy.getExecuteResult()) {
executeResult();
}
显示这里就是在执行拦截器栈中的全部拦截器,当拦截器执行完后就会根据配置执行Action中相应的方法,执行完后获得了一个resultCode字符串,系统就是根据这个字符串去查找相应的Result。
紧凑着就是执行executeResult方法,该方法中就是根据Result配置建立出相应的Result对象,而后执行Result的execute方法,例如用得最多的ServletDispatcherResult,其execute方法主要就是调用dispatcher.forward(request, response)方法,返回一个页面给Tomcat进行解析,而后将解析后的内容呈现给客户端浏览器。
//实例化Action并建立valueStack对象
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) {
// 由于项目通常都不会是分布式应用,也就不会执行这里
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, servletContext));
ctx = new ActionContext(stack.getContext());
}
request.setAttribute(CLEANUP_RECURSION_COUNTER, counter);
ActionContext.setContext(ctx);
return ctx;
}
ActionContext.setContext(ctx);把建立的ActionContext对象放进ThreadLocal中,绑定到当前线程以在其它地方方便获取ActionContext对象。
这时候建立createContextMap:
public Map<String,Object> createContextMap(HttpServletRequest request, HttpServletResponse response,
ActionMapping mapping, ServletContext context) {
// request map wrapping the http request objects
Map requestMap = new RequestMap(request);
// parameters map wrapping the http parameters. ActionMapping parameters are now handled and applied separately
Map params = new HashMap(request.getParameterMap());
// session map wrapping the http session
Map session = new SessionMap(request);
// application map wrapping the ServletContext
Map application = new ApplicationMap(context);
Map<String,Object> extraContext = createContextMap(requestMap, params, session, application, request, response, context);
if (mapping != null) {
extraContext.put(ServletActionContext.ACTION_MAPPING, mapping);
}
return extraContext;
}
该方法中把Servlet原生的Request,Parameters,Session,ServletContext都转换成了Map,而后将转换后的Map与原生的Servlet对象都传进了Dispatcher另外一个重载的createContextMap方法。
到这里,struts2的整个执行流程基本上就分析完毕了,若有错误之处,请指出。