一:tomcat刚启动时
当tomcat服务器启动时,加载web.xml配置文件,执行init()方法,可以看到StrutsPrepareAndExecuteFiter这个类只是实现filter的过滤器。
下面是整个init方法。
FilterHostConfig config = new FilterHostConfig(filterConfig);//把servlet容器与核心过滤器做了一个整合
Dispatcher dispatcher = init.initDispatcher(config);// 初始化所有的资源文件
public Dispatcher initDispatcher( HostConfig filterConfig ) { Dispatcher dispatcher = createDispatcher(filterConfig); dispatcher.init(); return dispatcher; }
|
public void init() { init_DefaultProperties(); // 加载default.properties文件 init_TraditionalXmlConfigurations(); //加载struts-default.xml,strut-plugin.xml,struts.xml } |
二.当客户端请求url时
具体流程:
当客户端发出一个请求时。如请求url:personAction_savePerson.action,那么会这么执行:
1.会执行StrutsPrepareAndExecuteFilter类的doFilter(ServletRequest req, ServletResponse res, FilterChain chain)方法,
以下是doFilter()方法的代码:
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); //创建ActionContext 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); } } } finally { prepare.cleanupRequest(request); } } |
首先会将会将request和respone对象转换为httt相关的对象,接着设置编码,然后创建ActionContext对象:
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); //把整个actionContext放入到了当前线程中,因为actionContext中有valueStack,所以valueStack也在当前线程中,这样就保证了数据的安全性 return ctx; } |
我们来看看怎么创建的ActionContext对象,上面部分看到当oldContext不存在时,利用类容器创建值栈。曾今我在Struts2+技术内幕这本书中看到,描述ActionContext和valueStack的关系是这么说:两者相互依存,形影不离。现在可以理解为什么会这么说了,因为ValueStack是伴随这ActionContext的创建而创建的,看这行代码: ctx = new ActionContext(stack.getContext());也就是说它把值栈放在了ActionContext里了。 然后在上面的代码中还有一个关键的地方,就是它把整个ActionContext放在了当前线程中了。这是Struts2的一大亮点,其实是Xwork的一大亮点(因为这是Xwork的思想)。传统的web开发都是基于Servlet的开发,Servlet是单利的,在web环境并发访问中会出现线程安全问题,而Servlet解决的方案其实是绕过去的思想。解决Servlet的线程安全是不定义全局变量(或者仅仅只能存在只读的全局变量),采用局部变量的机制,这样做可以绕过线程安全问题。我们看看Struts2把ActionContext放入当前线程中的具体实现:
public static void setContext(ActionContext context) { actionContext.set(context); } |
我们进一步跟踪源代码:
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } |
从createMap(t, value);这句代码我们可以知道,实际上是把ActionContext以当前线程作为键放在ThreadLocalMap中,所以要取ActionContext是以当前线程取得,因此不会出现线程安全问题。这是创建ActionContext的过程,我们回到doFilter()方法中,看看还做了些什么。
2.我们看到这句代码: prepare.assignDispatcherToThread(); 这段代码的作用其实是让整个过程都在当前线程中执行。我们跟源码会发现还会出现上面放入线程的那段代码。
3.继续回到doFilter方法往下看:request = prepare.wrapRequest(request);这段代码是对request的一个封装,最终变成 StrutsRequestWrapper 类型的request,所以才会出现神奇的request。
4.再接着往下看: execute.executeAction(request, response, mapping); 我们跟踪这段代码,会出现这段代码:
dispatcher.serviceAction(request, response, servletContext, mapping);
接着跟踪:以下那段源码比较长,我截取一部分:
serviceAction(){ ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY); /** * 因为在struts2容器中有太多的参数 request,response,valueStack,session,application,paramters 所以struts2容器对当前的请求中用到所有的数据封装在了ActionContext中的map中 */ extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.createValueStack(stack)); String namespace = mapping.getNamespace(); String name = mapping.getName(); String method = mapping.getMethod(); //创建actionProxy ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy( namespace, name, method, extraContext, true, false); proxy.execute()方法 prepare.cleanupRequest(request);//把struts2过程中的数据全部清空了 } |
我们关注以下这个方法createActionProxy 方法,这个方法从名字上可以猜出是创建Action的代理类,跟过源码后会发现(这个源码比较散,所以没有粘贴过来)执行了DefaultActionInvocation中的init方法,而在这个inint方法中:
public void init(ActionProxy proxy) { this.proxy = proxy; Map<String, Object> contextMap = createContextMap(); ActionContext actionContext = ActionContext.getContext(); if (actionContext != null) { actionContext.setActionInvocation(this); } createAction(contextMap); if (pushAction) { stack.push(action); contextMap.put("action", action); } invocationContext = new ActionContext(contextMap); invocationContext.setName(proxy.getActionName()); List<InterceptorMapping> interceptorList = new ArrayList<InterceptorMapping>(proxy.getConfig().getInterceptors()); interceptors = interceptorList.iterator(); } |
我们来分析以下这段代码:createAction(contextMap); 这句代码是创建了一个Action,所以Action的创建时机就是当创建ActionProxy的时候会创建Action。对于它如何创建Action的,我并不怎么关心,我看了以下源码,创建Action的过程相对来说比较复杂,也不是我讨论的范围,不过跟过源码发现里面有这么一段代码:
action = objectFactory.buildAction(proxy.getActionName(), proxy.getNamespace(), proxy.getConfig(), contextMap);
从上面可以看到它是利用工厂来创建Action的。
接着我们看下面这两句代码: stack.push(action); contextMap.put("action", action); 从这两句代码我们可以发现它把action放入到了对象栈的栈顶,并且还把Action放了一份到Map栈中。
接着往下看有这两句代码:
List<InterceptorMapping> interceptorList = new ArrayList<InterceptorMapping>(proxy.getConfig().getInterceptors());
interceptors = interceptorList.iterator();
从这两句代码可以看出:是获取所有的拦截器,并且返回了迭代器的形式。
5.接下来我们回到上面的serviceAction()这个方法中,在这个方法中还有一个重要的方法: proxy.execute() ,而这个方法会执行DefaultActionInvocation中的invoke方法,最关键的就是这个invoke方法。我们看看这个invoke方法到底发生了什么?下面摘取一段invoke方法里面的内容:
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(); } 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; } |
6.我们可以看到interceptors.hasNext()这句代码是调用了所有的拦截器,然后这段代码:interceptor.getInterceptor().intercept(DefaultActionInvocation.this);这段代码是执行每一个拦截器,返回一个字符串。然后我们看这行代码:invokeActionOnly(); 这行代码是说明等所有的拦截器执行之后执行Action。接下来是这段代码: executeResult(); 即执行结果集。
7.回到最初的doFilter()方法中,还有最后一个收尾的方法:prepare.cleanupRequest(request); 这个方法是把struts2过程中的数据全部清空了,以免数据膨胀,我们可以看到这个方法是放在finally里面的,也就是说不管是否发生异常都会执行,Struts2的清除数据还有一个destroy() 方法,也就是说为了防止数据膨胀,Struts2做了非常好的清理过程。
最后总结:
1、在struts2的配置文件中
两大类型的配置
一种类型 独立于包外的
bean
constant
include
一种类型 package
action,interceptor,result等都是在package中的,可以利用extend机制继承
2、在执行过滤器的过程中
1、创建ActionContext
1、创建actionContext
2、创建值栈
3、把actionContext加入到当前线程中
2、创建actionProxy
执行DefaultActionInvocation的init方法
1、创建action
2、把action放入到栈顶
3、创建所有的拦截器,并且产生拦截器的迭代器
3、执行Proxy的execute方法
proxy.execute()---->invocaction.invoke方法
1、执行所有的拦截器
2、执行当前请求的action
3、执行RreResultListener
4、执行结果集
4、清除数据 在finally中,这样即便出错,也能清除