TOMCAT源码分析(消息处理)

晨曦之光 发布于 2012/04/10 18:48
阅读 1K+
收藏 1
0:前言

我们知道了tomcat的整体框架了, 也明白了里面都有些什么组件, 以及各个组件是干什么用的了。

http://www.csdn.net/Develop/read_article.asp?id=27225

我想,接下来我们应该去了解一下 tomcat 是如何处理jsp和servlet请求的。

1.  我们以一个具体的例子,来跟踪TOMCAT, 看看它是如何把Request一层一层地递交给下一个容器, 并最后交给Wrapper来处理的。

http://localhost:8080/web/login.jsp为例子

(以下例子, 都是以tomcat4 源码为参考)

这篇心得主要分为3个部分: 前期, 中期, 和末期。

前期:讲解了在浏览器里面输入一个URL,是怎么被tomcat抓住的。

中期:讲解了被tomcat抓住后,又是怎么在各个容器里面穿梭, 最后到达最后的处理地点。

末期:讲解到达最后的处理地点后,又是怎么具体处理的。

2、  前期 Request的born.

    在这里我先简单讲一下request这个东西。

     我们先看着这个URL:http://localhost:8080/web/login.jsp  它是动用了8080端口来进行socket通讯的。

     我们知道, 通过

       InputStream in = socket.getInputStream() 和

       OutputStream out = socket.getOutputStream()

     就可以实现消息的来来往往了。

     但是如果把Stream给应用层看,显然操作起来不方便。

     所以,在tomcat 的Connector里面, socket被封装成了Request和Response这两个对象。

     我们可以简单地把Request看成管发到服务器来的数据,把Response看成想发出服务器的数据。

     但是这样又有其他问题了啊? Request这个对象是把socket封装起来了, 但是他提供的又东西太多了。

     诸如Request.getAuthorization(), Request.getSocket()。  像Authorization这种东西开发人员拿来基本上用不太着,而像socket这种东西,暴露给开发人员又有潜在的危险。 而且啊, 在Servlet Specification里面标准的通信类是ServletRequest和HttpServletRequest,而非这个Request类。 So, So, So. Tomcat必须得捣持捣持Request才行。 最后tomcat选择了使用捣持模式(应该叫适配器模式)来解决这个问题。它把org.apache.catalina.Request 捣持成了 org.apache.coyote.tomcat4.CoyoteRequest。 而CoyoteRequest又实现了ServletRequest和HttpServletRequest 这两种接口。 这样就提供给开发人员需要且刚刚需要的方法了。

    ok, 让我们在 tomcat的顶层容器 - StandardEngin 的invoke()方法这里设置一个断点, 然后访问

    http://localhost:8080/web/login.jsp , 我们来看看在前期都会路过哪些地方:

       1. run(): 536, java.lang.Thread, Thread.java

       CurrentThread

      2.  run():666, org.apache.tomcat.util.threads.ThreadPool$ControlRunnable, ThreadPool.java

               ThreadPool

       3.  runIt():589, org.apache.tomcat.util.net.TcpWorkerThread, PoolTcpEndpoint.java

          ThreadWorker

4.        processConnection():  549

org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler, Http11Protocol.java

                  http protocol parser

      5.  Process(): 781, org.apache.coyote.http11.Http11Processor, Http11Processor.java

          http request processor

       6. service(): 193, org.apache.coyote.tomcat4.CoyoteAdapter,CoyoteAdapter.java

          adapter

       7. invoke(): 995, org.apache.catalina.core.ContainerBase, ContainerBase.java

   StandardEngin

    1. 主线程

    2. 启动线程池.

    3. 调出线程池里面空闲的工作线程。

    4. 把8080端口传过来由httpd协议封装的数据,解析成Request和Response对象。

    5. 使用Http11Processor来处理request

    6. 在Http11Processor里面, 又会call CoyoteAdapter来进行适配处理,把Request适配成实现了ServletRequest和HttpServletRequest接口的CoyoteRequest.

7. 到了这里,前期的去毛拔皮工作就基本上搞定,可以交给StandardEngin 做核心的处理工作了。

3. 中期。 在各个容器间的穿梭。

    Request在各个容器里面的穿梭大致是这样一种方式:

    每个容器里面都有一个管道(pipline), 专门用来传送Request用的。

    管道里面又有好几个阀门(valve), 专门用来过滤Request用的。

    在管道的低部通常都会放上一个默认的阀们。 这个阀们至少会做一件事情,就是把Request交给子容器。

    让我们来想象一下:

     当一个Request进入一个容器后, 它就在管道里面流动,波罗~ 波罗~ 波罗~ 地穿过各个阀门。在流到最后一个阀门的时候,吧唧~ 那个该死的阀门就把它扔给了子容器。 然后又开始 波罗~ 波罗~ 波罗~ ... 吧唧~....  波罗~  波罗~ 波罗~ ....吧唧~....

    就是通过这种方式, Request 走完了所有的容器。( 感觉有点像消化系统,最后一个地方有点像那里~  )

    OK, 让我们具体看看都有些什么容器, 各个容器里面又都有些什么阀门,这些阀们都对我们的Request做了些什么吧:

3.1 StandardEngin 的pipeline里面放的是:StandardEnginValve

在这里,VALVE做了三件事:

1.   验证传递过来的request是不是httpservletRequest.

2    验证传递过来的 request 是否携带了host header信息.

3    选择相应的host去处理它。(一般我们都只有一个host:localhost,也就是127.0.0.1)。

到了这个地方, 我们的request就已经完成了在Engin这个部分的历史使命, 通向前途未卜的下一站: host了。

3.2 StandardHost 的pipline里面放的是: StandardHostValve

1.   验证传递过来的request是不是httpservletRequest.

2.   根据Request来确定哪个Context来处理。

Context其实就是webapp, 比如http://localhost:8080/web/login.jsp

这里web就是Context罗!

3.   既然确定了是哪个Context了,那么就应该把那个Context的classloader付给当前线程了。

        Thread.currentThread().setContextClassLoader(context.getLoader().getClassLoader());

   这样request就只看得见指定的context下面的classes啊, jar啊这些, 而看不见tomcat本身的类, 什么Engin啊, Valve啊。 不然还得了啊!

4. 既然request到了这里了,看来用户是准备访问web这个web app了,咋们得更新一下这个用户的session不是! Ok , 就由manager更新一下用户的session信息

5. 交给具体的Context 容器去继续处理Request.

6. Context处理完毕了,把classloader还回来。

3.3 StandardContext 的pipline里面放的是: StandardContextValve

1.   验证传递过来的request是不是httpservletRequest.

2.   如果request意图不轨,想要访问/meta-inf, /web-inf这些目录下的东西,呵呵,没有用D!

3.   这个时候就会根据Request到底是Servlet,还是jsp,还是静态资源来决定到底用哪种Wrapper来处理这个Reqeust了。

4.   一旦决定了到底用哪种Wrapper,OK,交给那个Wrapper处理。

4. 末期。 不同的需求是怎么处理的.

StandardWrapper

之前对Wrapper没有做过讲解,其实它是这样一种东西。

我们在处理Request的时候,可以分成3种。

处理静态的: org.apache.catalina.servlets.DefaultServlet  

处理jsp的: org.apache.jasper.servlet.JspServlet

处理servlet的: org.apache.catalina.servlets.InvokerServlet

不同的request就用这3种不同的servlet去处理。

Wrapper就是对它们的一种简单的封装,有了Wrapper后,我们就可以轻松地拦截每次的Request。也可以容易地调用servlet的init()和destroy()方法, 便于管理嘛!

具体情况是这么滴:

   如果request是找jsp文件,StandardWrapper里面就会封装一个org.apache.jasper.servlet.JspServlet去处理它。

   如果request是找 静态资源 ,StandardWrapper里面就会封装一个org.apache.jasper.servlet.DefaultServlet  去处理它。

   如果request是找servlet ,StandardWrapper里面就会封装一个org.apache.jasper.servlet.InvokerServlet 去处理它。

StandardWrapper同样也是容器,既然是容器, 那么里面一定留了一个管道给request去穿,管道低部肯定也有一个阀门(注1),用来做最后一道拦截工作.

在这最底部的阀门里,其实就主要做了两件事:

   一是启动过滤器,让request在N个过滤器里面筛一通,如果OK! 那就PASS。 否则就跳到其他地方去了。

   二是servlet.service((HttpServletRequest) request,(HttpServletResponse) response); 这个方法.

     如果是 JspServlet, 那么先把jsp文件编译成servlet_xxx, 再invoke servlet_xxx的servie()方法。

     如果是 DefaultServlet, 就直接找到静态资源,取出内容, 发送出去。

     如果是 InvokerServlet, 就调用那个具体的servlet的service()方法。

   ok! 完毕。

注1: StandardWrapper 里面的阀门是最后一道关口了。 如果这个阀门欲意把request交给StandardWrapper 的子容器处理。 对不起, 在设计考虑的时候, Wrapper就被考虑成最末的一个容器, 压根儿就不会给Wrapper添加子容器的机会! 如果硬是要调用addChild(), 立马抛出IllegalArgumentException!

参考:

<http://jakarta.apache.org/tomcat/>
   <
http://www.onjava.com/pub/a/onjava/2003/05/14/java_webserver.html>

TOMCAT源码分析(启动框架)

自己最近在学习Java Web编程,先是读了Sun公司的官方文档Java EE 5的tutorial( http://java.sun.com/javaee/5/docs/tutorial/doc/)中的关键章节,然后学了一阵Java Passion里的相关在线课程(见: http://www.javapassion.com/j2ee/),前几天还看完了一本电子书<<Struts2 in Action>>,但总觉得缺少了什么。这两天突然想起为什么不把tomcat的源码下载下来仔细研究研究呢?记得以前自己一直想学习著名的Java Build工具Ant( http://ant.apache.org/)的用法,但怎么看文档都理解不深,试了几次效果都不好。最后终于把源码下载下来读了其中的主要源码,顿时感觉不仅轻松学会了Ant的用法,而且知道为什么要那么设计。这给我很大的启发:对于开源软件来说,比书籍更好的是在线文档,比在线文档更好的源码本身。其实tomcat和Ant有很深的内在联系,tomcat的早期开发者 James Duncan Davidson就是Ant工具的发明人,为了解决开发tomcat过程中各种复杂的build流程他专门创造了Ant。显然,了解Ant对读懂tomcat会有极大的帮助。tomcat现在的架构师 Craig McClanahan又是另一个著名的MVC框架struts的发明人。

步入正题吧。

首先到tomcat.apache.org里下载最新的源码包,现在最新的版本是6.0.20,下载后解压缩,发现它的根目录里有一个build.xml,这是Ant的默认输入文件。tomcat源码本身是用Eclipse写的,所以我们要做的第一步是在Eclipse里把tomcat工程部署好,这其实很容易,在Eclipse里选择File>New>Project,选择Java project with an existing Ant buildfile,选择那个build.xml就可以了。
然后是下载tomcat的依赖包,这一步也很容易,只要执行ant download(可以打开Eclipse的Ant的view以方便操作)就可以了。它会把下载的依赖包放在一个名称为base.path的目录,最好自己配置一下,它默认配置在build.xml.properties.default文件里。下载完依赖包后再ant deploy后就在output/build目录里得到了可以使用的tomcat。Ant真方便!
Eclipse里要想使得即时编译没有任何错误提示,需要在build path里配置两个变量:一个是ANT_HOME,另一个是保存依赖包的TOMCAT_LIBS_BASE,就是保存依赖包的那个目录。
看一下tomcat的目录结构,真的很简单,启动tomcat的脚本放在bin目录里,分别有对应windows和unix的两种脚本。我在Ubuntu环境下,所以先来看一下它的unix脚本。启动脚本是startup.sh,它实际上是另一个脚本catalina.sh的封装,所以直接跳到catalina.sh,它会调用另一个脚本setclasspath.sh以设置java,catalina.sh设置好各种环境后会调用tomcat的启动类:org.apache.catalina.startup.Bootstrap的start方法。
跟踪一下代码,Bootstrap类里主要是设置几个不同的ClassLoader和JMX Server,然后通过反射技术把任务移交给真正的启动类:org.apache.catalina.startup.Catalina。
怎么样,这是不是很像启动脚本一样,先有个包装,最后都调的是catalina,不管是shell还是Java的类。
Catalina里的方法最主要是这个方法:protected Digester createStartDigester();它是读取conf/server.xml配置文件进行动态加载配置组件的过程,用的是apache的另一个子项目:Digester,它也是struts读取配置文件动态加载的机制,别忘了tomcat的架构师可是struts项目的作者啊。
加载完server的层次化组件后,通过调用 ((Lifecycle) server).start();整个server就自顶向下启动起来了。另外,代码中大量运用了observer这个设计模式,把各种状态的变化通知给相应的listener。
在Bootstrap类的源码中发现一处小的bug:

if (replace && log.isDebugEnabled())
log.debug("Expanded " + before + " to " + replace);

应该是:
if (replace && log.isDebugEnabled())
log.debug("Expanded " + before + " to " + repository);
原文链接:http://blog.csdn.net/chengchanglun/article/details/7440955
加载中
返回顶部
顶部