tomcat源码分析
1、运行环境
tomcat 版本:8.0.x
编译工具:ant
运行IDE:idea13.1
2、tomcat架构组成
如下图所示:
Server: 其实就是BackGround程序, 在Tomcat里面的Server的用处是启动和监听服务端事件(诸如重启、关闭等命令。
Service: 在tomcat里面,service是指一类问题的解决方案。 通常我们会默认使用tomcat提供的:Tomcat-Standalone 模式的service。 在这种方式下的service既给我们提供解析jsp和servlet的服务, 同时也提供给我们解析静态文本的服务。
Connector: Tomcat都是在容器里面处理问题的, 而容器又到哪里去取得输入信息呢?
Connector就是专干这个的。 他会把从socket传递过来的数据, 封装成Request, 传递给容器来处理。 通常我们会用到两种Connector,一种叫http connectoer, 用来传递http需求的。 另一种叫AJP, 在我们整合apache与tomcat工作的时候,apache与tomcat之间就是通过这个协议来互动的。 (说到apache与tomcat的整合工作, 通常我们的目的是为了让apache 获取静态资源, 而让tomcat来解析动态的jsp或者servlet。)
Container: 当http connector把需求传递给*的container: Engin的时候, 我们的视线就应该移动到Container这个层面来了。 在Container这个层, 我们包含了3种容器:Engin, Host, Context. Engin: 收到service传递过来的需求, 处理后, 将结果返回给service( service 是通过connector 这个媒介来和Engin互动的). Host: Engin收到service传递过来的需求后,不会自己处理, 而是交给合适的Host来处理。 Host在这里就是虚拟主机的意思, 通常我们都只会使用一个主机,既“localhost”本地机来处理。 Context: Host接到了从Host传过来的需求后, 也不会自己处理, 而是交给合适的Context来处理。
3、tomcat启动流程
1> 初始化自定义的classloader
• 根据catalina.properties的配置读取要加载的class的路径及jar包;
• 将上述路径转化为URL集合;
• 调用AccessController.doPrivileged生成classloader;
• 设置当前上下文及对应的classloader。
2>使用Digester技术装配tomcat各个容器与组件
• 创建一个Digester,并且配置好所有需要用到的actions;
• 读取server.xml文件并将其转换为InputSource,并提供给Digester来解析成实例对象,并封装在catalina里面;
• 最后catalina触发初始化动作,开始逐层初始化。
3> 初始化
依次通过设置NEW、INITIALIZING、INITIALIZED这三个状态来表示初始化当前组件的完成。依次是server->service->engine->host->context->http connector->ajp connector。先是server初始化开始,然后是service,然后依次将里面的container初始化,然后初始化connector,其中也会初始化各种listener、manger、logger等,最后整个server才算初始化完成。在初始化StantardEngine时会创建一个线程池,用来处理HTTP请求,但此时还未启动线程。
4> 服务的启动
启动server及service
每个组件或服务的启动流程:设置STARTING_PREP状态,调用LifecycleBase的star的方法、startInternal的方法中会设置成STARTING状态,最后设置STARTED状态;其中startInternal方法的调用是其扩展点,按照上述流程会调用其内层的组件,直至所有服务或组件启动完毕。其实初始化的过程也是类似,只是调用的方法不一样而已。
启动 engine(容器)
startInternal的方法中会有各个组件自己的实现内容。从engine的启动开始(容器的启动),有所区别。它首先会启动Realm服务用来验证配置文件,用户名、密码等。然后将engine的所有容器都放到future里面,最后以异步的方式启动其子容器,然后调用pipeline,执行valve链,然后设置STARTING状态,然后启动一个线程来对当前classloader的加载进行监听从而实现可配置的热部署。
启动mapperListener及connectors:
mapperListener是保存了host及service的映射关系,以保证处理请求事能找到对应的服务来处理。会按照server.xml的配置来创建指定最小和最大线程数的线程池,来接收并处理请求,超过的线程数就会排队,超过排队线程数的就会被丢弃。
每个容器里面都会定义一个pipeline,里面会放入各种valve,每个valve都会对request进行过滤、校验等,最后都会指定下一级处理的子容器,除非到达最后一级容器context。
4、Tomcat处理一次请求的过程
假设来自客户的请求为:
http://localhost:8080/test/index.jsp
1) 请求被发送到本机端口8080,被在那里侦听的Coyote HTTP/1.1 Connector获得
2) Connector把该请求交给它所在的Service的Engine来处理,并等待来自Engine的回应
3) Engine获得请求localhost/test/index.jsp,匹配它所拥有的所有虚拟主机Host
4) Engine匹配到名为localhost的Host(即使匹配不到也把请求交给该Host处理,因为该Host被定义为该Engine的默认主机)
5) localhost Host获得请求/test/index.jsp,匹配它所拥有的所有Context,此处为最长匹配
6) Host匹配到路径为/test的Context(如果匹配不到就把该请求交给路径名为""的Context去处理) 7) path="/test"的Context获得请求/index.jsp,在它的mapping table中寻找对应的servlet
8) Context匹配到URL PATTERN为*.jsp的servlet,对应于JspServlet类
9) 构造HttpServletRequest对象和HttpServletResponse对象,作为参数调用JspServlet的doGet或doPost方法
10)Context把执行完了之后的HttpServletResponse对象返回给Host
11)Host把HttpServletResponse对象返回给Engine
12)Engine把HttpServletResponse对象返回给Connector
13)Connector把HttpServletResponse对象返回给客户browser
5、tomcat用到的主要设计模式
门面设计模式:tomcat在将http请求封装成request、response对象时,并不需要其所有的信息,所以自定义一个对象,将httprequest、httpresponse作为该对象的成员变量,然后通过这2个对象来实现自己需要的方法,避免暴露不必要的信息给外部的调用类。
命令模式(模板设计模式):所有组件都会实现同一个抽象类,像初始化、初始化完成等操作,又共同的抽象类完成,而设置内部状态的操作,由各个组件自己去实现。
责任链模式:所有容器的pipeline的实现,每个pipeline都有多个valve的定义,用来做一些过滤、校验的工作,并且在非最后一个容器的时候,会再最后一个valve指定下一个要接收的容器,从而按指定流程流转起来。
观察者模式:所有容器的生命周期的监听以及每个组件的状态变化及响应,像整个tomcat的初始化、加载、启动都会去修改对应组件的状态,然后将状态的变化通知给关心此状态变化的监听器。