struts2大概分为两块:一是struts2系统初始化,二是struts2处理请求,对请求作出响应。

下面就说说个人对struts2对请求处理流程的理解:

下面是StrutsPrepareAndExecuteFilter过滤器的doFilter方法中的主要代码:

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对象的方法。

prepare.setEncodingAndLocale(request, response);这一句处理请求编码与响应Locale,其内部调用的就是Dipatcher的prepare方法,下面是源码:

/*** Sets the request encoding and locale on the response*/public void setEncodingAndLocale(HttpServletRequest request, HttpServletResponse response) {dispatcher.prepare(request, response);}

其逻辑是如果在在struts2的配置文件中指写了i18n编码则使用配置文件中的编码,否则不会调用request.setCharacterEncoding()方法。至于响应Locale的取值与encoding的原理是一样的,但具体的逻辑源码有点多,但不难,这里就不作解释了,大家应该都看得懂。

prepare.createActionContext(request, response);

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, servletContext));ctx = new ActionContext(stack.getContext());}request.setAttribute(CLEANUP_RECURSION_COUNTER, counter);ActionContext.setContext(ctx);return ctx;

ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();从struts2容器中获取ValueStackFactory并创建出ValueStack

stack.getContext().putAll(dispatcher.createContextMap(request, response, null, servletContext));把ActionContext中的数据复制一份到ValueStack中,所以从ActionContext与ValueStack都能拿到我们想要的所有数据。

dispatcher.createContextMap

// request map wrapping the http request objectsMap requestMap = new RequestMap(request);// parameters map wrapping the http parameters.  ActionMapping parameters are now handled and applied separatelyMap params = new HashMap(request.getParameterMap());// session map wrapping the http sessionMap session = new SessionMap(request);// application map wrapping the ServletContextMap 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方法,下面是其源码:

public HashMap<String,Object> createContextMap(Map requestMap,Map parameterMap,Map sessionMap,Map applicationMap,HttpServletRequest request,HttpServletResponse response,ServletContext servletContext) {HashMap<String,Object> extraContext = new HashMap<String,Object>();extraContext.put(ActionContext.PARAMETERS, new HashMap(parameterMap));extraContext.put(ActionContext.SESSION, sessionMap);extraContext.put(ActionContext.APPLICATION, applicationMap);Locale locale;if (defaultLocale != null) {locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());} else {locale = request.getLocale();}extraContext.put(ActionContext.LOCALE, locale);//extraContext.put(ActionContext.DEV_MODE, Boolean.valueOf(devMode));extraContext.put(StrutsStatics.HTTP_REQUEST, request);extraContext.put(StrutsStatics.HTTP_RESPONSE, response);extraContext.put(StrutsStatics.SERVLET_CONTEXT, servletContext);// helpers to get access to request/session/application scopeextraContext.put("request", requestMap);extraContext.put("session", sessionMap);extraContext.put("application", applicationMap);extraContext.put("parameters", parameterMap);AttributeMap attrMap = new AttributeMap(extraContext);extraContext.put("attr", attrMap);return extraContext;}

该方法主要就是把转换后的Map与Servlet原生对象外加一个AttributeMap都存进了一个大Map中,然后返回。

这样就即能获取转换后的Map,又可以获取Servlet原生对象。

现在回到prepare.createActionContext方法中

ActionContext.setContext(ctx);把创建的ActionContext对象放进ThreadLocal<T>中,绑定到当前线程以在其它地方方便获取ActionContext对象。

再加到核心过滤器中,prepare.assignDispatcherToThread();从该方法的名称就知道是把Dispatcher对象绑定到当前线程,也就是放进了一个ThreadLocal<T>对象中,这样做的目的是为了解决多线程并发访问的问题,因为Dispathcer对象只创建了一个,创建代码就在StrutsPrepareAndExecuteFilter的init方法当中,而init方法只会执行一次,当然Dispatcher对象也就只有一个了,而Web应用天生就是一个多线程的环境,所以把Dispatcher放进ThreadLocal<T>中成为了最佳选择。这里与上面ActionContext对象放进ThreadLocal<T>中的原因是不一样的,因为每当一个请求到来系统都会为其创建一个ActionContext对象,这个ActionContext是该请求独享的,并不存在多线程的问题,所以把该对象放进ThreadLocal<T>中是为了获取方便。

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中进行查找,下面的源码即是证明:

 // If not found, then try the ValueStackctx.put("__requestWrapper.getAttribute", Boolean.TRUE);ValueStack stack = ctx.getValueStack();if (stack != null) {attribute = stack.findValue(s);}

这就是为什么在JSP页面中用EL表达式也能访问到ValueStack中值的属性的原因。

ActionMapping mapping = prepare.findActionMapping(request, response, true);这句就没什么说了,就是得到Action映射信息。

如果请求的静态页面就会执行execute.executeStaticResourceRequest(request, response);在方法内部会判断有无能力对该请求进行处理,如果有则处理没有则调用chain.doFilter放行,实现基本都一样就是原样返回给客户端了。

真正会执行Action的是这一句:execute.executeAction(request, response, mapping);该方法调用的是Dispatcher的serviceAction方法,我们进入该方法看看,下面是该方法中的重要代码:

    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 requestif (!nullStack) {request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);}

先获取Configuration对象,通过Configuration得到容器对象,再从容器中获取ActionProxyFactory,ActionProxy工厂类,然后创建ActionProxy,大家都知道struts2内部包装的是webwork框架,而ActionProxy正是sturts与webwork的分界线,ActionProxy是进行webwork框架的门户。

struts2默认使用的是DefaultActionProxyFactory创建ActionProxy对象的,下面是其createActionFactor方法:

 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方法,下面是该方法中重要的代码:

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 listList<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实现的。

现在回到Dispatcher的serviceAction方法中,创建出来ActionProxy对象后的下一句代码把ValueStack对象放进了request中,这也就意味着我们通过HttpServletRequest对象也是可获取,只要知道key就行了。

proxy.execute();这一句执行ActionProxy的execute方法,struts2中ActionProxy的默认实现是StrutsActionProxy,下面进行该类的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);}}

代码很简单,就是调用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();}
//这里省略了一些代码...
// now execute the result, if we're supposed toif (proxy.getExecuteResult()) {executeResult();}

显示这里就是在执行拦截器栈中的所有拦截器,当拦截器执行完后就会根据配置执行Action中相应的方法,执行完后得到了一个resultCode字符串,系统就是根据这个字符串去查找相应的Result。

紧凑着就是执行executeResult方法,该方法中就是根据Result配置创建出相应的Result对象,然后执行Result的execute方法,例如用得最多的ServletDispatcherResult,其execute方法主要就是调用dispatcher.forward(request, response)方法,返回一个页面给Tomcat进行解析,然后将解析后的内容呈现给客户端浏览器。

至此,struts2的整个执行流程基本上就讲完了,如果有错误之处,尽请指正。

下面上传的是个人为struts2执行流程画的一个时序图,有兴趣的可以看看,因图片太大所以要放大了才看得清,希望有所帮助:

转载于:https://www.cnblogs.com/pangblog/p/3357891.html

struts2处理请求流程详解相关推荐

  1. springMVC请求流程详解

    SpringMVC框架是一个基于请求驱动的Web框架,并且使用了'前端控制器'模型来进行设计, 再根据'请求映射规则'分发给相应的页面控制器进行处理.核心流程: 第一步:发起请求到前端控制器(Disp ...

  2. Springboot启动流程详解

    SpringMVC请求流程详解 SpringMVC框架是一个基于请求驱动的Web框架,并且使用了'前端控制器'模型来进行设计,再根据'请求映射规则'分发给相应的页面控制器进行处理. (一)整体流程 每 ...

  3. java处理请求的流程_Java Spring mvc请求处理流程详解

    Spring mvc请求处理流程详解 前言 spring mvc框架相信很多人都很熟悉了,关于这方面的资料也是一搜一大把.但是感觉讲的都不是很细致,让很多初学者都云里雾里的.本人也是这样,之前研究过, ...

  4. 杂志订阅管理系统c++_电池管理系统BMS功能安全开发流程详解

    点击上面 "电动知家"可以订阅哦! BMS功能安全开发流程详解 BMS和ISO26262 - BMS & ISO26262简介 BMS即Battery Management ...

  5. linux中流设备_[快速上手Linux设备驱动]之块设备驱动流程详解一

    [快速上手Linux设备驱动]之块设备驱动流程详解一 walfred已经在[快速上手Linux设备驱动]之我看字符设备驱动一 文中详细讲解了linux下字符设备驱动,并紧接着用四篇文章描述了Linux ...

  6. 推荐系统整体架构及算法流程详解

    省时查报告-专业.及时.全面的行研报告库 省时查方案-专业.及时.全面的营销策划方案库 知识图谱在美团推荐场景中的应用实践 搜索场景下的智能实体推荐 机器学习在B站推荐系统中的应用实践 小红书推荐系统 ...

  7. [nRF51822] 5、 霸屏了——详解nRF51 SDK中的GPIOTE(从GPIO电平变化到产生中断事件的流程详解)...

    :由于在大多数情况下GPIO的状态变化都会触发应用程序执行一些动作.为了方便nRF51官方把该流程封装成了GPIOTE,全称:The GPIO Tasks and Events (GPIOTE) . ...

  8. 负载均衡原理与实践详解 第五篇 负载均衡时数据包流程详解

    负载均衡原理与实践详解 第五篇 负载均衡时数据包流程详解 系列文章: 负载均衡详解第一篇:负载均衡的需求 负载均衡详解第二篇:服务器负载均衡的基本概念-网络基础 负载均衡详解第三篇:服务器负载均衡的基 ...

  9. iOS APP上架流程详解

    iOS APP上架流程详解 青葱烈马 2016.04.28  前言:作为一名 iOS 开发工程师, APP 的上架是必备技能. iOS 上架的流程主要可以简单总结为: 一个包,两个网址,三个证书, 一 ...

最新文章

  1. vue数组中数据变化但是视图没有更新解决方案
  2. BlockChain:Scene application区块链场景应用集合
  3. 如何使用命令行拿到SAP Kyma的Lambda Function明细
  4. 教你配置支付宝应用网关和授权回调地址
  5. LSGO软件技术团队2015~2016学年第十一周(1109~1115)总结
  6. Android入门(12)| 数据持久化
  7. java web开发需要学习哪些知识_java web开发需要学习哪些知识?
  8. Android---------------Handler的学习
  9. Python操作SQLAlchemy
  10. linux内核双向链表学习
  11. 南阳理工ACM 题目252 01串
  12. Tcpip详解卷一第3章(2)
  13. P6800 - 刷入CWM
  14. 最佳匹配问题-KM算法
  15. Ember.js和Vue.js对比,哪个框架更优秀?
  16. 解决谷歌地图alert出现“此页面无法正确加载 Google 地图“
  17. 知道SG函数是干什么的
  18. 三极管共射放大电路调试
  19. 一款APP的完整开发流程
  20. LinuxProbe 0x21 使用Ansible服务实现自动化运维

热门文章

  1. doxygen相关问题 转
  2. linux 内核 发送数据,linux 内核tcp数据发送的实现
  3. 【转】计算机学会推荐国际学术期刊
  4. linux下eclipse cdt引用函数库设置问题
  5. Pandas简明教程:五、Pandas简单统计操作及通用方式
  6. 关于qte illegal instruction的一些心得
  7. JVM盘点家底查看初始默认值
  8. 多款eclipse黑色坏境任你选择,只要导入配置
  9. Error - section 'InterruptVectorLow' can not fit the absolute section. Section 'InterruptVectorLow'
  10. 关掉Ctrl+Alt+方向键转屏功能