Struts2 源码剖析 控制部分-----1
这部分着重分析从我们发出一个uri请求,一直到代码运行到我们自己写的action类为止,struts的控制部分的代码(还有数据流部分,我们后面再分析)
已经用了快1年多的struts2了,一直认为对开源框架的学习,应该到源码级别才行,否则开源岂不是没有意义了。
struts的运行图如下:
1、客户端初始化一个指向Servlet容器(例如Tomcat)的请求;
2、这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个叫做ActionContextCleanUp的可选过滤器,这个过滤器对于Struts2和其他框架的集成很有帮助,例如:SiteMesh Plugin);
3、接着StrutsPrepareAndExecuteFilter被调用,StrutsPrepareAndExecuteFilter询问ActionMapper来决定这个请求是否需要调用某个Action;
4、如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求的处理交给ActionProxy;
5、ActionProxy通过Configuration Manager询问框架的配置文件,找到需要调用的Action类;
6、ActionProxy创建一个ActionInvocation的实例。
7、ActionInvocation实例使用命名模式来调用,在调用Action的过程前后,涉及到相关拦截器(Intercepter)的调用。
8、一旦Action执行完毕,ActionInvocation负责根据struts.xml中的配置找到对应的返回结果。返回结果通常是(但不总是,也可能是另外的一个Action链)一个需要被表示的JSP或者FreeMarker的模版。在表示的过程中可以使用Struts2 框架中继承的标签。在这个过程中需要涉及到ActionMapper。
先看我自己画的时序图:
struts在web.xml中的配置如下:
<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>
很显然,我们得去StrutsPrepareAndExecuteFilter里看看,这是一个filter。
现在,我们先不看代码,根据以往关于filter的学习经历,我们能知道,filter至少有3个方法:
init(FilterConfig filterConfig)
doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
destroy()
init方法是初始化的,只运行一次,destroy一样,也只运行一次。
doFilter是真正的struts的业务逻辑。
我们猜一下,init应该就是struts的环境初始化,目前我们先不管它。(下一节,我们再探讨init方法)
我们就按照前面说的struts运行的八个步骤来看看doFilter。
//StrutsPrepareAndExecuteFilter.java 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); //之前的代码 我们先不管 //第三步 找到action的信息 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); } }
上面的ActionMapping mapping = prepare.findActionMapping(request, response, true);就是找到对应的action,怎么找的?看代码:
//PrepareOperations.java public ActionMapping findActionMapping(HttpServletRequest request, HttpServletResponse response, boolean forceLookup) { ActionMapping mapping = (ActionMapping) request.getAttribute(STRUTS_ACTION_MAPPING_KEY); if (mapping == null || forceLookup) { try { mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager()); if (mapping != null) { request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping); } } catch (Exception ex) { dispatcher.sendError(request, response, servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex); } } return mapping; }
这里有一个actionmapping,它是干什么的?
看代码:
public class ActionMapping { private String name; private String namespace; private String method; private String extension; private Map<String, Object> params; private Result result; //..... }
懂了么?,就是对应一个action
mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager());
上面的这行,就是先获取ActionMapper,再通过ActionMapper获取ActionMapping。
actionMapper是干什么的?
暂时不清楚
但是我能知道,getInstance返回的是ActionMapper的实现类:DefaultActionMapper
//DefaultActionMapper.java 下面的就是时序图中的第四步 public ActionMapping getMapping(HttpServletRequest request, ConfigurationManager configManager) { ActionMapping mapping = new ActionMapping(); //去除工程名等等 获得uri String uri = RequestUtils.getUri(request); //url的带; jsessionid 问题 int indexOfSemicolon = uri.indexOf(";"); uri = (indexOfSemicolon > -1) ? uri.substring(0, indexOfSemicolon) : uri; //删除扩展名,如.action或者.do uri = dropExtension(uri, mapping); if (uri == null) { return null; } //把uri中的信息分析到mapping中 parseNameAndNamespace(uri, mapping, configManager); //处理特殊参数 handleSpecialParameters(request, mapping); //主要是用来处理形如/userAction!getAll.action的请求,分离action名和方法名: return parseActionName(mapping); }
对DefaultActionMapper的getMapping方法,我们得边调试边看。
例如我访问下面这个路径:
http://localhost:8080/Struts2.3.16.1Hibernate4.3.4Spring4.0.2/login.jsp
RequestUtils.getUri(request)返回的string是:/login.jsp
另外,经过uri = dropExtension(uri, mapping)后,uri就是null了。
然后就直接返回一个null
我们换一个请求路径
http://localhost:8080/Struts2.3.16.1Hibernate4.3.4Spring4.0.2/user/login.action
此时RequestUtils.getUri(request)的返回值就是:/user/login.action
经过dropExtension(uri, mapping)后,uri就是:/user/login
我们看到parseNameAndNamespace,这里干的就是从uri中分离得到请求的action名、命名空间。
这里面的代码,比较复杂,我们只需要知道它干了什么事就OK,它不是我们的重点。
之后的parseActionName干的就是处理动态方法调用的事,感兴趣的,大家可以看看代码
之后,我们就回到了StrutsPrepareAndExecuteFilter的dofilter方法,此时调用的是:
execute.executeAction(request, response, mapping); (时序图中的第五步)
查看代码,最终我们到达了Dispatcher的serviceAction方法:
serviceAction方法,如下,我已经删掉了一些数据相关的代码,例如valuestack
//Dispatcher.java 时序图中的第六步 public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context, ActionMapping mapping) throws ServletException { //处理栈的问题 String namespace = mapping.getNamespace(); String name = mapping.getName(); String method = mapping.getMethod(); Configuration config = configurationManager.getConfiguration(); //时序图中的第7,8步 ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy( namespace, name, method, extraContext, true, false); //时序图中的第9步 //4、如果ActionMapper决定需要调用某个Action, //FilterDispatcher把请求的处理交给ActionProxy; proxy.execute(); }
//StrutsActionProxy.java 时序图中的第10步 public String execute() throws Exception { ActionContext previous = ActionContext.getContext(); ActionContext.setContext(invocation.getInvocationContext()); try { return invocation.invoke(); } finally { if (cleanupContext) ActionContext.setContext(previous); } }
StrutsActionProxy.execute中的invocation是DefaultActionInvocation的实例,其invoke方法如下:
//DefaultActionInvocation.java public String invoke() throws Exception { if (interceptors.hasNext()) { final InterceptorMapping interceptor = interceptors.next(); resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this); } else { resultCode = invokeActionOnly(); } return resultCode; } }
关于interceptor.getInterceptor().intercept(DefaultActionInvocation.this),这是一个典型的责任链模式,如果对它不清楚,大家可以参考
说说struts2中拦截器的请求流程一(模拟大致流程)
DefaultActionInvocation中有很多过滤器,对每一个请求,都是首先经过那个一个个过滤器然后进入action,之后再通过过滤器
invokeActionOnly之后会调用invokeAction,马上就要进入我们自己写的action里面了。
//DefaultActionInvocation.java 代码有省略 protected String invokeAction(Object action, ActionConfig actionConfig) throws Exception { String methodName = proxy.getMethod(); String timerKey = "invokeAction: " + proxy.getActionName(); boolean methodCalled = false; Object methodResult = null; Method method = null; try { method = getAction().getClass().getMethod(methodName, EMPTY_CLASS_ARRAY); } if (!methodCalled) { //OK,我们已经进入 自己写的action了 methodResult = method.invoke(action, EMPTY_OBJECT_ARRAY); } return saveResult(actionConfig, methodResult); }
这里我们只是分析了控制部分的代码,action中的成员变量的值是什么时候写入的,我们写的action返回一个success之后struts又干了什么,struts的启动过程是怎么样的,这都是一个一个的大问题,我会在后面的博客里和大家一一道来。
另外,今天glt回来了
glt是谁?
我女朋友
呵呵 她刚从成都飞回来
辛苦了~~~
参考资料
http://www.cnblogs.com/liuling/p/2013-8-10-01.html
Struts2 源码剖析 控制部分-----1的更多相关文章
- SpringMVC源码剖析(四)- DispatcherServlet请求转发的实现
SpringMVC完成初始化流程之后,就进入Servlet标准生命周期的第二个阶段,即“service”阶段.在“service”阶段中,每一次Http请求到来,容器都会启动一个请求线程,通过serv ...
- SpringMVC源码剖析(二)- DispatcherServlet的前世今生
上一篇文章<SpringMVC源码剖析(一)- 从抽象和接口说起>中,我介绍了一次典型的SpringMVC请求处理过程中,相继粉墨登场的各种核心类和接口.我刻意忽略了源码中的处理细节,只列 ...
- 源码剖析@ApiImplicitParam对@RequestParam的required属性的侵入性
问题起源 使用SpringCloud构建项目时,使用Swagger生成相应的接口文档是推荐的选项,Swagger能够提供页面访问,直接在网页上调试后端系统的接口, 非常方便.最近却遇到了一个有点困惑的 ...
- jQuery之Deferred源码剖析
一.前言 大约在夏季,我们谈过ES6的Promise(详见here),其实在ES6前jQuery早就有了Promise,也就是我们所知道的Deferred对象,宗旨当然也和ES6的Promise一样, ...
- Nodejs事件引擎libuv源码剖析之:高效线程池(threadpool)的实现
声明:本文为原创博文,转载请注明出处. Nodejs编程是全异步的,这就意味着我们不必每次都阻塞等待该次操作的结果,而事件完成(就绪)时会主动回调通知我们.在网络编程中,一般都是基于Reactor线程 ...
- Apache Spark源码剖析
Apache Spark源码剖析(全面系统介绍Spark源码,提供分析源码的实用技巧和合理的阅读顺序,充分了解Spark的设计思想和运行机理) 许鹏 著 ISBN 978-7-121-25420- ...
- Struts2 源码分析——DefaultActionInvocation类的执行action
本章简言 上一章讲到关于拦截器的机制的知识点,让我们对拦截器有了一定的认识.我们也清楚的知道在执行用户action类实例之前,struts2会先去执行当前action类对应的拦截器.而关于在哪里执行a ...
- Struts2 源码分析——拦截器的机制
本章简言 上一章讲到关于action代理类的工作.即是如何去找对应的action配置信息,并执行action类的实例.而这一章笔者将讲到在执行action需要用到的拦截器.为什么要讲拦截器呢?可以这样 ...
- Struts2 源码分析——Action代理类的工作
章节简言 上一章笔者讲到关于如何加载配置文件里面的package元素节点信息.相信读者到这里心里面对struts2在启动的时候加载相关的信息有了一定的了解和认识.而本章将讲到关于struts2启动成功 ...
随机推荐
- 查询优化--小表驱动大表(In,Exists区别)
Mysql 系列文章主页 =============== 本文将以真实例子来讲解小表驱动大表(In,Exists区别) 1 准备数据 1.1 创建表.函数.存储过程 参照 这篇(调用函数和存储过程批 ...
- 初识 Runtime
前言 之前在看一些第三方源码的时候,时不时的能碰到一些关于运行时相关的代码.于是乎,就阅读了一些关于运行时的文章,感觉写的都不错,写此篇文章为了记录一下,同时也重新学习一遍. Runtime简介 Ru ...
- js error
0x800a0259 - JavaScript 运行时错误: 未知的运行时错误 <p id="navigatorInfo"></p> var txt = & ...
- BlockingQueue阻塞队列(解决多线程中数据安全问题 可用于抢票,秒杀)
案例:一个线程类中 private static BlockingQueue<Map<String, String>> dataQueue = new LinkedBlocki ...
- 初识Redis系列之三:Redis支持的数据类型及使用
支持的数据类型有五种: string(字符串).hash(哈希).list(列表).set(集合)及zset(sorted set:有序集合): 下面分别对这几种类型进行简单的Redis存取操作 1: ...
- 开源Spring解决方案--lm.solution
Github 项目地址: https://github.com/liumeng0403/lm.solution 一.说明 1.本项目未按java项目传统命名方式命名项目名,包名 如:org.xxxx. ...
- Apache 443端口占用解决方法
当运行httpd.exe时,出现如下问题 原因是启动Apache会占用443端口,而443被其他程序占用了.我们只需将Apache默认端口443改掉就行.网上搜了一下,说是更改Apache24\con ...
- ACM Primes
Write a program to read in a list of integers and determine whether or not each number is prime. A n ...
- Django 缓存模块 page_cache 源码阅读
Django cache中比较常用的有 cache_page 这么个 decorators, 下面就根据请求流程,结合源码来说说它是怎么工作的? 版本是django1.8,不同版本可能函数等会变化,逻 ...
- 在Spring Boot框架下使用WebSocket实现聊天功能
上一篇博客我们介绍了在Spring Boot框架下使用WebSocket实现消息推送,消息推送是一对多,服务器发消息发送给所有的浏览器,这次我们来看看如何使用WebSocket实现消息的一对一发送,模 ...