Tomcat源码分析—请求在容器中

Tomcat源码分析—请求在容器中

这篇文章是我在2011年时写的,现转到我自己的博客上

 

   Tomcat容器处理请求的过程第一部分如下图所示:

首先通过CoyoteAdapter调用
connector.getContainer().getPipeline().getFirst().invoke(request, response);
将请求转给容器,关于这句话的意思,请看  Tomcat源码分析—设计模式
容器经过一系列的valve处理,最终会将请求转到servlet或者JSP,前面三个valve处理的事情都很少,代码比较多的集中在StandardContextValve和
StandardWrapperValve。
StandardHostValve在将请求转给StandardContextValve之前,还设置了线程上下文类装载器
请求到达StandardContextValve后,首先判断访问的是否是WEB-INF或者META-INF,如果是则直接返回,接着找到和上下文关联的所有自定义的
Listener,然后遍历所有的Listener,判断当前的Listener是否是ServletRequestListener,如果是则执行requestInitialized(),之后将请求转给
StandardWrapperValve,待StandardWrapperValve执行完后,StandardContextValve又会调用自定义Listener的requestDestroyed()。

请求处理第二部分(从StandardWrapperValve开始):


StandardWrapperValve的调用过程就明显复杂了很多,这里调用的过程,还有调用fitler的时候还包括了comet的过程,这里就省略掉了。
在执行servlet执行,需要装载这个servlet,这部分的工作,是交给StandardWrapper去完成的,StandardWrapper#allocate()首先执行一段DCL
检查,看实例是否已经加载了,如果已经加载的话,就直接返回,不会再去装载了,从这里可以看出,一个servlet在上下文容器中就只有一份,多个
请求都可以共用这个servlet实例,但如果servlet里面包含了同步块,则多个请求都会去抢这个锁。当然有一种情况例外,就是单线程模式,在servlet
2.3规范中有一个SingleThreadModel接口,当实现了这个接口之后,容器会为每一个请求创建一个实例,但是如果这个servlet里面保存了静态的类
变量,则仍然不能保证线程安全问题,所以在2.4规范中将其废弃了。

当实例为null,则调用自身的 loadServlet()方法去装载这个类,首先需要获得一个类装载器,类装载器是和Loader关联的,那么再调用自身的
getLoader()方法,这个方法是在父类ContainerBase中定义的,首先检查当前关联的loader是否为null,不为null就直接返回,否则就是用父容器的,
如果父容器也为null,则使用父容器的父容器。正常情况下获取的就是 WebappLoader,之后使用WebappLoader关联的WebappClassLoader
(注意WebappClassLoader才是真正的类装载器,前面的WebappLoader并不是类装载器)去装载Class,调用loadClass()后,就返回了这个类的
Class对象,之后通过反射创建servlet实例:
servlet = (Servlet) classClass.newInstance();
并执行servlet的init()方法,从整个流程来看,单线程模式servlet和普通servlet在初始化的时候是一样的。
单线程模式的servlet和普通servlet的区别是,单线程模式servlet由一个Stack保存,里面存有多份,每次请求的时候
由Stack里面返回一个servlet。
这里有一个小插曲,tomcat使用DCL,但是其变量没有定义为volatile类型,所以是有问题的,另外单线程模式servlet在第一次装载的时候会执行两次
init()方法,这也是有问题的,tomcat在6.0.32 这个版本仍然没有修复。

之后再调用ApplicationFilterFactory#createFilterChain(),从上下文中找到所有的filter,然后遍历所有的filter,看路径是否匹配,如果路径匹配
的话,调用addFilter()方法,将其增加到ApplicationFilterChain中。接着StandardWrapperValve就去调用ApplicationFilterChain,这里会有一个
门面模式,将规范实现的Request包装成RequestFacade,Response包装成ResponseFacade。ApplicationFilterChain#doFilter()又掉用自身的  internalDoFilter()方法,然后就是真正调用自定义的filter:
filter.doFilter(request, response, this);
这里会将this作为参数传递给自定义的Filter,这第三个参数就是FilterChain,待自定义Filter执行完后,又会调用到ApplicationFilterChain的
doFilter()方法,有点像递归调用了。直到所有的filter执行完后,就去调用Servlet的service()方法,这个Servlet就是自定义的Servlet了(如果继承了
HttpServlet,会先调用HttpServlet的service()方法,根据请求的method类型,再调用自定义servlet的相应方法)

假设自定义Servlet的方法里面包含了HttpServletRequest#() 和this.getServletContext()#setAttribute()方法:
向请求里增加属性首先会调用到RequestFacade#setAttribute(),接着RequestFacade再去调用规范实现的Request,向Request中设置属性是
保存在一个Map里面的,由于Request具有重用性,所以在请求结束的时候会清空Map里面的数据,而请求达到容器之后一直到servlet处理都是一个
线程,所以也就保证了每一个请求的属性不会相互干扰。等属性设置完后,从上下文获取所有的Listener,遍历所有的Listener看当前的Listener
是否是 ServletRequestAttributeListener类型的,如果再判断属性是新设置的还是覆盖的,来调用自定义Listener的相应方法,Listener的增加,
覆盖,删除方法的执行逻辑都是类似的。
向Servlet上下文中增加属性,首先是调用ApplicationContextFacade#setAttribute(),接着回去调用规范的实现ApplicationContext。在
ApplicationContext#setAttribute()里,所做的事情和Request类似,首先是从上下文中找到所有的Listener,然后遍历所有的Listener,判断当前
的Listener是否是ServletContextAttributeListener,如果是再判断是新增还是覆盖,再去调用自定义上下文属性Listener的相应方法。和Request
的增加覆盖删除一样,上下文属性的增加覆盖删除逻辑也是类似的。

3 次阅读

发表评论

电子邮件地址不会被公开。