一、背景
Tomcat作为JavaWeb领域的Web容器,目前在我们淘宝也使用的也非常广泛,现在基本上所有线上业务系统都是部署在Tomcat上。为了对平时开发的Web系统有更深入的理解以及出于好奇心对我们写的Web系统是如何跑在Tomcat上的,于是仔细研究了下Tomcat的源码。大家都知道Servlet规范是Java领域中为服务端编程制定的规范,对于我们开发者只是关注了Servlet规范中提供的编程组件(ServletContextListener,Filer,Servlet) 等 ,但是规范中还有一些我们经常使用的接口(ServletContext,ServletRequest,ServletResponse,FilterChain)等都是由Tomcat去实现的,并且我们开发者实现的编程组件只是被Tomcat去回调而已。所以看Tomcat源码实现也有助于我们更好的理解Servlet规范及系统如何在容器中运行(一些开源的MVC框架如Struts2,Webx,SpringMVC本质无非就是这个),顺便整理了一下与大家分享(Tomcat版本7.0.23,JDK版本1.6)。
二、Tomcat源码目录结构
三、Tomcat体系结构
仔细查看下图(网络上描述Tomcat架构比较清晰的一张图),不难发现其中的Connecotr组件以及与Container组件是Tomcat的核心。一个Server可以有多个Service,而一个Service可以包含了多个Connector组件和一个Engine容器组件,一个Engine可以由多个虚拟主机Host组成,每一个Host下面又可以由多个Web应用Context构成,每一个的Context下面可以包含多个Wrapper(Servlet的包装器)组成。
Tomcat将Engine,Host,Context,Wrapper统一抽象成Container。一个抽象的Container模块可以包含各种服务。例如,Manager管理器(Session管理),Pipeline管道( 维护管道阀门Value )等。Lifecycle接口统一定义了容器的生命周期,通过事件机制实现各个容器间的内部通讯。而容器的核心接口Container的抽象实现中定义了一个Pipeline,一个Manager,一个Realm以及ClassLoader统一了具体容器的实现规范。连接器(Connector)组件的主要任务是为其所接收到的每一个请求(可以是HTTP协议,也可以AJP协议),委托给具体相关协议的解析类ProtocolHandler,构造出Request 对象和Response 对象。然后将这两个对象传送给容器(Container)进行处理。容器(Container)组件收到来自连接器(Connector)的Request 和Response对象后,负责调用Filter,最后调用Servlet的service 方法(进入我们开发的Web系统中)。
四、Tomcat源码解析
Servlet规范由一组用 Java编程语言编写的类和接口组成。Servlet规范为服务端开发人员提供了一个标准的 API以及为服务器厂商制定了相关实现规范,开发人员只需要关心Servlet规范中的编程组件(如Filter,Servlet等),其他规范接口由第三方服务器厂商(如Tomcat)去实现,三者的关系如下图,Servlet规范之于Tomcat的关系,也类似于JDBC规范与数据库驱动的关系,本质就是一套接口和一套实现的关系。对于一个Web服务器主要需要做的事情,个人认为基本由以下组件组成: [TCP连接管理] --> [请求处理线程池管理] --> [HTTP协议解析封装] --> [Servlet规范的实现,对编程组件的回调] --> [MVC框架,Web系统业务逻辑]。
Tomcat的源码及功能点因为实在过于庞大,下面的源码解析不可能全部功能模块都涉及到,接下来我们主要会根据Tomcat的启动、请求处理、关闭三个流程来梳理Tomcat的具体实现,尽可能把各个模块的核心组件都展示出来。 基本上每个流程的源码分析我都画了时序图,如果不想看文字的话,可以对着时序图看(如果想看完整的源代码分析也会在附件中上传),最后还会分析下Tomcat的Connector组件及Tomcat运行过程中的线程概况及线程模型及Tomcat的类加载机制及Tomcat所涉及的设计模式。
Servlet规范和Web应用及Web容器间的关系
Tomcat对Servlet规范的实现
1. Tomcat的启动流程
Tomcat在启动时的重点功能如下:
· 初始化类加载器:主要初始化Tomcat加载自身类库的StandardClassLoader。
· 解析配置文件:使用Digester组件解析Tomcat的server.xml,初始化各个组件(包含各个web应用,解析对应的web.xml进行初始化)。
· 初始化Tomcat的各级容器Container,当然最后会初始我们Web应用(我们熟悉的Listener,Filter,Servlet等初始化等在这里完成)。
· 初始化连接器Connector:初始化配置的Connector,以指定的协议打开端口,等待请求。
不管是是通过脚本bootstrap.sh启动还是通过Eclipse中启动,Tomcat的启动流程是在org.apache.catalina.startup.Bootstrap类的main方法中开始的,启动时这个类的核心代码如下所示:
Java代码
1. public static void main(String args[]) {
2. if (daemon == null) {
3. daemon = new Bootstrap(); //实例化Bootstrap的一个实例
4. try {
5. // 创建StandardClassLoader类加载器,并设置为main线程的线程上下文类加载器
6. // 通过StandardClassLoader加载类Cataline并创建Catalina对象 ,并保存到Boosstrap对象中
7. daemon.init();
8. } catch (Throwable t) {
9. }
10. }
11. if (command.equals("start")) {
12. daemon.setAwait(true);
13. daemon.load(args); //执行load,加载资源,调用Catalina的load方法,利用Digester读取及解析server.xml配置文件并且创建StandardServer服务器对象 */
14. daemon.start(); //启动各个组件,容器开始启动,调用Catalina的start方法
15. }
16. }
从以上的代码中,可以看到在Tomcat启动的时候,执行了三个关键方法即init、load、和start。后面的两个方法都是通过反射调用org.apache.catalina.startup.Catalina的同名方法完成的,所以后面在介绍时将会直接转到Catalina的同名方法。首先分析一下Bootstrap的init方法,在该方法中将会初始化一些全局的系统属性、初始化类加载器、通过反射得到Catalina实例,在这里我们重点看一下初始化类加载器的initClassLoaders()方法:
Java代码
1. private void initClassLoaders() {
2. try {
3. //创建StandardClassLoader类加载器
4. commonLoader = createClassLoader("common", null);
5. if( commonLoader == null ) {
6. commonLoader=this.getClass().getClassLoader();
7. }
8. catalinaLoader = createClassLoader("server", commonLoader);
9. sharedLoader = createClassLoader("shared", commonLoader);
10. } catch (Throwable t) {
11. System.exit(1);
12. }
13. }
在以上的代码总,我们可以看到初始化了StandardClassLoader类加载器,这个类加载器详细的会在后面关于Tomcat类加载器机制中介绍。
然后我们进入Catalina的load方法:
Java代码
1. public void load() {
2. //初始化Digester组件,定义了解析规则
3. Digester digester = createStartDigester();
4. try {
5. inputSource.setByteStream(inputStream);
6. digester.push(this);
7. //通过Digester解析这个文件,在此过程中会初始化各个组件实例及其依赖关系
8. //最后会把server.xml文件中的内容解析到StandardServer中
9. digester.parse(inputSource);
10. inputStream.close();
11. } catch (Exception e) {}
12. if (getServer() instanceof Lifecycle) {
13. try {
14. // 调用Server的initialize方法,初始化各个组件
15. getServer().init();
16. } catch (LifecycleException e) {
17. }
18. }
19. }
在以上的代码中,关键的任务有两项即使用Digester组件按照给定的规则解析server.xml、调用Server的init方法,而Server的init方法中,会发布事件并调用各个Service的init方法,从而级联完成各个组件的初始化。每个组件的初始化都是比较有意思的,但是我们限于篇幅先关注Tomcat各级容器的初始化及Connector的初始化,这可能是最值得关注的地方。
首先看下StandardService的start方法(), 核心代码如下:
1. // StandardService的启动
2. protected void startInternal() throws LifecycleException {
4. //启动容器StandardEngine
5. if (container != null) {
6. synchronized (container) {
7. // 启动StandardEngine容器
8. container.start();
9. }
10. }
12. //两个connector的启动,HTTP/1.18080和AJP/1.38009
13. synchronized (connectors) {
14. for (Connector connector: connectors) {
15. try {
16. if (connector.getState() != LifecycleState.FAILED) {
17. //依次启动Connector[HTTP/1.1-8080]或Connector[AJP/1.3-8009]
18. //这里会创建请求线程执行器并启动接收线程Acceptor
19. connector.start();
20. }
21. } catch (Exception e) {
22. }
23. }
24. }
25. }
启动Tomcat各级容器的会依次先启动StandardEngine --> StandardHost --> StandardContext(代表一个WebApp应用), 因为我们比较关心我们的Web应用是哪里被初始化回调的,所以就重点看下StandardContext的start()方法,核心代码如下:
Java代码
1. //启动WebApp应用
2. @Override
3. protected synchronized void startInternal() throws LifecycleException {
4. boolean ok = true;
6. if (getLoader() == null) {
7. //创建此WebApp应用的WebApp载入器WebAppLoader,这个应用的WebAppClassLoader类加载器全部是由这个载入器创建
8. WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
9. webappLoader.setDelegate(getDelegate());
10. //在这里开始启动WebAppLoader加载器,并且创建此WebApp的WebAppClassLoader类加载器,保存到WebAppLoader中,最后会被设置到此Context的InstanceManager中
11. setLoader(webappLoader);
12. }
14. ///线程上下文类加载器切换成当前WebApp的类加载器,从Context的loader中获取
15. ClassLoader oldCCL = bindThread();
17. try {
18. if (ok) {
19. //触发Context组件的configure_start事件,通知ContextConfig监听器
20. //开始解析Web应用WEB-INF/web.xml文件配置到WebXml对象,最后把配置信息全部解析到StandardContext
21. fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
23. if (pipeline instanceof Lifecycle) {
24. //启动StandardContext的管道Pipeline
25. ((Lifecycle) pipeline).start();
26. }
27. }
28. } finally {
29. //线程上下文类加载器切换成之前的StandardClassLoader
30. unbindThread(oldCCL);
31. }
33. //线程上下文类加载器切换成当前WebApp的类加载器,从Context的loader中获取
34. oldCCL = bindThread();
36. if (ok ) {
37. if (getInstanceManager() == null) {
38. Map<String, Map<String, String>> injectionMap = buildInjectionMap(
39. getIgnoreAnnotations() ? new NamingResources(): getNamingResources());
40. //创建每个应用StandardContext自己的DefaultInstanceManager实例管理器对象
41. //并且会从WebAppLoader拿到WebAppClassLoader类加载器,之后此WebApp的应用类都由此ClassLoader加载
42. setInstanceManager(new DefaultInstanceManager(context,
43. injectionMap, this, this.getClass().getClassLoader()));
44. }
45. }
47. try {
48. //把StandardContext的上下文参数设置到ServletContext中
49. mergeParameters();
51. if (ok) {
52. //启动监听器Listener
53. if (!listenerStart()) {
54. log.error( "Error listenerStart");
55. ok = false;
56. }
57. }
59. try {
60. if ((manager != null) && (manager instanceof Lifecycle)) {
61. //启动Session管理器StandardManager
62. ((Lifecycle) getManager()).start();
63. }
64. super.threadStart();
65. } catch(Exception e) {
66. ok = false;
67. }
69. if (ok) {
70. //启动过滤器Filter
71. if (!filterStart()) {
72. log.error("Error filterStart");
73. ok = false;
74. }
75. }
77. if (ok) {
78. //启动load-on-startup的Servlet
79. loadOnStartup(findChildren());
80. }
82. } finally {
83. // WepApp应用启动完成,线程上下文类加载器切换成之前的StandardClassLoader
84. unbindThread(oldCCL);
85. }
86. }
Tomcat的各级容器初始化完成后,就开始对Connector的初始化,接着看Connector的initInternal方法,核心代码如下:
Java代码
1. public void initInternal() throws LifecycleException{
2. //该协议适配器会完成请求的真正处理
3. adapter = new CoyoteAdapter(this);
4. //对于不同的实现,会有不同的ProtocolHandler实现类, Http11Protocol用来处理HTTP请求
5. protocolHandler.setAdapter(adapter);
6. try {
7. // 初始化Http11Protocol协议
8. protocolHandler.init();
9. } catch (Exception e) {
10. }
11. }
在Http11Protocol的init方法中,核心代码如下:
Java代码
1. public void init() throws Exception {
2. endpoint.setName(getName());
3. endpoint.setHandler(cHandler);
4. try {
5. endpoint.init(); //核心代码就是调用JIoEndpoint的初始化方法
6. } catch (Exception ex) {
7. }
8. }
我们看到最终的初始化方法最终都会调到JIoEndpoint的bind方法,网络初始化和对请求的最初处理都是通过该类及其内部类完成的,后续的内容会详细阐述这个JioEndpoint:
Java代码
1. public void bind() throws Exception {
2. //请求接收线程数,默认为1
3. if (acceptorThreadCount == 0) {
4. acceptorThreadCount = 1;
5. }
6. if (serverSocket == null) {
7. try {
8. if (address == null) {
9. //创建ServerSocket,绑定指定端口并打开该端口的服务,默认8080端口
10. serverSocket = serverSocketFactory.createSocket(port, backlog);
11. } else {
12. serverSocket = serverSocketFactory.createSocket(port, backlog, address);
13. }
14. } catch (BindException orig) {
15. }
16. }
17. }
在上面的代码中,我们可以看到此时初始化了一个ServerSocket对象,用来监听绑定端口的请求。
紧接着我们看JioEndpoint的start()方法,核心代码如下:
Java代码
1. public void startInternal() throws Exception {
2. if (!running) {
4. if (getExecutor() == null) {
5. //创建请求处理线程池ThreadPoolExecutor, 请求接收线程启动前必须把请求处理线程池准备好
6. createExecutor();
7. }
9. initializeConnectionLatch();
11. //创建并启动Acceptor接收线程
12. startAcceptorThreads();
13. }
14. }
从以上的代码,可以看到,如果没有在server.xml中声明Executor的话,将会使用内部的一个容量为200的线程池用来后续的请求处理。并且按照参数acceptorThreadCount的设置,初始化线程来接受请求。而Acceptor就是正在接受请求并会分派给请求处理线程池:
Java代码
1. protected class Acceptor implements Runnable {
2. public void run() {
3. while (running) {
4. // 默认监听8080端口请求,server.xml中的默认配置
5. Socket socket =serverSocketFactory.acceptSocket(serverSocket);
6. serverSocketFactory.initSocket(socket);
7. // 处理Socket,把客户端请求Socket交给线程池来处理,当前Acceptor线程继续返回接收客户端Socket
8. if (!processSocket(socket)) {
9. //关闭连接
10. try {
11. socket.close();
12. } catch (IOException e) {
13. }
14. }
15. }
16. }
17. }
从这里我们可以看到,Acceptor已经可以接收Socket请求了,并可以调用processSocket方法来对请求进行处理。至此,Tomcat的组件启动初始化完成,等待请求的到来。
Tomcat的启动流程具体序列图如下:
2. Tomcat一次完整请求的处理流程
Tomcat一次完整请求处理的重点功能如下:
· 接收Socket请求,把请求转发到线程池,由线程池分配一个线程来处理。
· 获取Socket数据包之后,解析HTTP协议,翻译成Tomcat内部的Request和Response对象,再映射相应Container。
· Request和Response对象进入Tomcat中的Container容器的各级Piepline管道去流式执行,最终会流到StandardWrapperValve这个阀门。
· 在 StandardWrapperValve这个阀门中,把当前请求的Filter及Servlet封装成FilterChain, 最终执行FilterChain, 回调Web应用,完成响应。
JIoEndpoint的Acceptor线程在接收到用户的请求之后,调用processSocket方法。该方法主要是从Executor请求处理线程池中获取一个线程,然后启用一个新线程执行Socket请求,JIoEndpoint的processSocket()方法的核心代码如下:
Java代码
1. protected boolean processSocket(Socket socket) {
2. try {
3. //把Socket包装成SocketWrapper
4. SocketWrapper<Socket> wrapper = new SocketWrapper<Socket>(socket);
5. //包装成SocketProcessor交给线程池处理,当前Acceptor线程不处理,以便接收下一个到达的请求
6. getExecutor().execute(new SocketProcessor(wrapper));
7. } catch (RejectedExecutionException x) {
8. return false;
9. }
10. return true;
11. }
接着请求处理线程池会起一个线程来处理请求来执行SocketProcessor,而SocketProcessor的run()方法中会调用Http11ConnectionHandler的process方法,SocketProcessor的run()方法核心代码如下:
Java代码
1. // Tomcat线程池中的请求处理线程
2. public void run() {
3. boolean launch = false;
4. synchronized (socket) {
5. try {
6. SocketState state = SocketState.OPEN;
7. if ((state != SocketState.CLOSED)) {
8. if (status == null) {
9. //通过Http11Protocol$Http11ConnectionHandler处理请求,引用外部类对象的成员handler
10. state = handler.process(socket, SocketStatus.OPEN);
11. } else {
12. state = handler.process(socket, status);
13. }
14. }
15. if (state == SocketState.CLOSED) {
16. try {
17. //关闭Socket
18. socket.getSocket().close();
19. } catch (IOException e) {
20. }
21. }
22. }
23. }
24. //清空请求Socket
25. socket = null;
26. }
在Http11ConnectionHandler中会根据当前请求的协议类型去创建相应的协议处理器,我们这里分析的是HTTP协议,所以会创建Http11Processor去执行process()方法, 拿到Socket数据包后解析生成Tomcat内部的Request对象与Response对象。其中Request对象只是解析Header部分内容,请求参数等做延迟处理,接着就开始调用CoyoteAdapter类进入容器处理, Http11Processor的process方法核心代码如下:
Java代码
1. public SocketState process(SocketWrapper<S> socketWrapper)
2. throws IOException {
3. RequestInfo rp = request.getRequestProcessor();
4. //设置SocketWrapper
5. setSocketWrapper(socketWrapper);
7. //获取Socket中的inputStream设置到inputBuffer,也就是设置到Request中
8. getInputBuffer().init(socketWrapper, endpoint);
9. //获取Socket中的outputStream设置到outputBuffer,也就是设置到Response中
10. getOutputBuffer().init(socketWrapper, endpoint);
12. while (!error && keepAlive && !comet && !isAsync() &&
13. !endpoint.isPaused()) {
14. try {
15. setRequestLineReadTimeout();
17. //解析HTTP请求的method,requestURI,protocol等
18. if (!getInputBuffer().parseRequestLine(keptAlive)) {
19. if (handleIncompleteRequestLineRead()) {
20. break;
21. }
22. }
24. if (endpoint.isPaused()) {
25. response.setStatus(503);
26. error = true;
27. } else {
28. request.setStartTime(System.currentTimeMillis());
29. keptAlive = true;
31. //解析HTTP请求的报头headers
32. if (!getInputBuffer().parseHeaders()) {
33. openSocket = true;
34. readComplete = false;
35. break;
36. }
37. }
38. } catch (IOException e) {
39. } catch (Throwable t) {
40. }
42. if (!error) {
43. try {
44. //准备Request,根据已解析的信息做一些过滤
45. prepareRequest();
46. } catch (Throwable t) {
47. ExceptionUtils.handleThrowable(t);
48. }
49. }
50. if (!error) {
51. try {
52. //调用CoyoteAdapter的service方法,传入org.apache.coyote.Request对象及org.apache.coyote.Response对象
53. adapter.service(request, response);
54. } catch (InterruptedIOException e) {
55. error = true;
56. } 39. } catch (Throwable t) {
40. }
42. if (!error) {
43. try {
44. //准备Request,根据已解析的信息做一些过滤
45. prepareRequest();
46. } catch (Throwable t) {
47. ExceptionUtils.handleThrowable(t);
48. }
49. }
50. if (!error) {
51. try {
52. //调用CoyoteAdapter的service方法,传入org.apache.coyote.Request对象及org.apache.coyote.Response对象
53. adapter.service(request, response);
54. } catch (InterruptedIOException e) {
55. error = true;
56. }
57. }
58. }