一、启动类

Tomcat的启动涉及到两个类:BootStrap和Catalina类。Catalina类用于启动和关闭Server对象,并通过Digester来解析conf目录下的Server.xml文件。BootStrap类则是一个入口类,入口是main方法。下面来看看源码中Tomcat的启动过程。

二、如何调试源码

这里介绍一种比较简单的方法:导入jar包调试法
找一个自己配过tomcat的web项目,在pom中引入Tomcat的嵌入jar包,如下图所示。其中版本设置为你自己下载的tomcat版本即可。

  1. 1. <dependency>
  2. 2. <groupId>org.apache.tomcat.embed</groupId>
  3. 3. <artifactId>tomcat-embed-core</artifactId>
  4. 4. <version>7.0.84</version>
  5. 5. </dependency>

三、启动过程

BootStrap类

首先在BootStrap类的main方法中打一个断点,启动项目就会停在如下断点:
Tomcat启动过程 - 图1
1.调用init方法
BootStrap的init方法会做以下的事情
(1)设置Catalina的一些目录信息。setCatalinaHome、setCatalinaBase
(2)初始化类加载器。initClassLoaders会初始化Tomcat的三个类加载器:CommonClassLoader、CatalinaClassLoader、SharedClassLoader
(3)用CatalinaClassLoader加载Catalina类,并通过反射得到Catalina的实例,再通过反射设置父加载器,最后将catalina实例赋值给Daemon
执行完初始化之后,会回到main执行后续的逻辑

  1. 1. public void init()
  2. 2. throws Exception
  3. 3. {
  4. 4.
  5. 5. // Set Catalina path
  6. 6. setCatalinaHome();
  7. 7. setCatalinaBase();
  8. 8.
  9. 9. initClassLoaders();
  10. 10.
  11. 11. Thread.currentThread().setContextClassLoader(catalinaLoader);
  12. 12.
  13. 13. SecurityClassLoad.securityClassLoad(catalinaLoader);
  14. 14.
  15. 15. // Load our startup class and call its process() method
  16. 16. if (log.isDebugEnabled())
  17. 17. log.debug("Loading startup class");
  18. 18. Class<?> startupClass =
  19. 19. catalinaLoader.loadClass
  20. 20. ("org.apache.catalina.startup.Catalina");
  21. 21. Object startupInstance = startupClass.newInstance();
  22. 22.
  23. 23. // Set the shared extensions class loader
  24. 24. if (log.isDebugEnabled())
  25. 25. log.debug("Setting startup class properties");
  26. 26. String methodName = "setParentClassLoader";
  27. 27. Class<?> paramTypes[] = new Class[1];
  28. 28. paramTypes[0] = Class.forName("java.lang.ClassLoader");
  29. 29. Object paramValues[] = new Object[1];
  30. 30. paramValues[0] = sharedLoader;
  31. 31. Method method =
  32. 32. startupInstance.getClass().getMethod(methodName, paramTypes);
  33. 33. method.invoke(startupInstance, paramValues);
  34. 34.
  35. 35. catalinaDaemon = startupInstance;
  36. 36.
  37. 37. }

2.依据传入的args类型,分别执行不同的动作方法
前面截图中已经看到参数是“start”,因此会走到如下逻辑:
Tomcat启动过程 - 图23.接下来看看load和start中做了些什么
load方法中通过反射调用了daemon的load方法,也就是Catalina的load方法
start方法和load方法类似,这里不再赘述
总体看来,BootStrap做的工作也不多,初始化了几个类加载器,然后将所有的load、start等动作通过反射反映到Catalina上面

Catalina类

1.首先看下被Bootstrap调用的load方法
(1)创建一个Digester对象。createStartDigester方法里面点进去看看就是按照xml中配置的结构
(2)读取并解析配置文件。configFile默认就是server.xml,首先将当前对象Catalina设置为根对象
(3)解析完之后就得到了Server对象,并设置Server对象的Catalina参数未当前对象
(4)调用Server的init方法进行初始化。
Server对象是StandardServer的实例,它内部的initInternal方法会遍历该Server下的Service实例的init方法。
Service的initInternal方法又会初始化Container、Executor和Connectors。
Connector会调用各个ProtocolHandler的init方法。Protocol是处理各种协议处理的父接口,里面又会调用各个处理协议的Endpoint的init方法,并绑定端口
这样依次各个组件都得到了初始化

  1. 1. /**
  2. 2. * Start a new server instance.
  3. 3. */
  4. 4. public void load() {
  5. 5.
  6. 6. long t1 = System.nanoTime();
  7. 7.
  8. 8. initDirs();
  9. 9.
  10. 10. // Before digester - it may be needed
  11. 11.
  12. 12. initNaming();
  13. 13.
  14. 14. // Create and execute our Digester
  15. 15. Digester digester = createStartDigester();
  16. 16.
  17. 17. InputSource inputSource = null;
  18. 18. InputStream inputStream = null;
  19. 19. File file = null;
  20. 20. try {
  21. 21. file = configFile();
  22. 22. inputStream = new FileInputStream(file);
  23. 23. inputSource = new InputSource(file.toURI().toURL().toString());
  24. 24. } catch (Exception e) {
  25. 25. if (log.isDebugEnabled()) {
  26. 26. log.debug(sm.getString("catalina.configFail", file), e);
  27. 27. }
  28. 28. }
  29. 29. if (inputStream == null) {
  30. 30. try {
  31. 31. inputStream = getClass().getClassLoader()
  32. 32. .getResourceAsStream(getConfigFile());
  33. 33. inputSource = new InputSource
  34. 34. (getClass().getClassLoader()
  35. 35. .getResource(getConfigFile()).toString());
  36. 36. } catch (Exception e) {
  37. 37. if (log.isDebugEnabled()) {
  38. 38. log.debug(sm.getString("catalina.configFail",
  39. 39. getConfigFile()), e);
  40. 40. }
  41. 41. }
  42. 42. }
  43. 43.
  44. 44. // This should be included in catalina.jar
  45. 45. // Alternative: don't bother with xml, just create it manually.
  46. 46. if( inputStream==null ) {
  47. 47. try {
  48. 48. inputStream = getClass().getClassLoader()
  49. 49. .getResourceAsStream("server-embed.xml");
  50. 50. inputSource = new InputSource
  51. 51. (getClass().getClassLoader()
  52. 52. .getResource("server-embed.xml").toString());
  53. 53. } catch (Exception e) {
  54. 54. if (log.isDebugEnabled()) {
  55. 55. log.debug(sm.getString("catalina.configFail",
  56. 56. "server-embed.xml"), e);
  57. 57. }
  58. 58. }
  59. 59. }
  60. 60.
  61. 61.
  62. 62. if (inputStream == null || inputSource == null) {
  63. 63. if (file == null) {
  64. 64. log.warn(sm.getString("catalina.configFail",
  65. 65. getConfigFile() + "] or [server-embed.xml]"));
  66. 66. } else {
  67. 67. log.warn(sm.getString("catalina.configFail",
  68. 68. file.getAbsolutePath()));
  69. 69. if (file.exists() && !file.canRead()) {
  70. 70. log.warn("Permissions incorrect, read permission is not allowed on the file.");
  71. 71. }
  72. 72. }
  73. 73. return;
  74. 74. }
  75. 75.
  76. 76. try {
  77. 77. inputSource.setByteStream(inputStream);
  78. 78. digester.push(this);
  79. 79. digester.parse(inputSource);
  80. 80. } catch (SAXParseException spe) {
  81. 81. log.warn("Catalina.start using " + getConfigFile() + ": " +
  82. 82. spe.getMessage());
  83. 83. return;
  84. 84. } catch (Exception e) {
  85. 85. log.warn("Catalina.start using " + getConfigFile() + ": " , e);
  86. 86. return;
  87. 87. } finally {
  88. 88. try {
  89. 89. inputStream.close();
  90. 90. } catch (IOException e) {
  91. 91. // Ignore
  92. 92. }
  93. 93. }
  94. 94.
  95. 95. getServer().setCatalina(this);
  96. 96.
  97. 97. // Stream redirection
  98. 98. initStreams();
  99. 99.
  100. 100. // Start the new server
  101. 101. try {
  102. 102. getServer().init();
  103. 103. } catch (LifecycleException e) {
  104. 104. if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
  105. 105. throw new java.lang.Error(e);
  106. 106. } else {
  107. 107. log.error("Catalina.start", e);
  108. 108. }
  109. 109.
  110. 110. }
  111. 111.
  112. 112. long t2 = System.nanoTime();
  113. 113. if(log.isInfoEnabled()) {
  114. 114. log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms");
  115. 115. }
  116. 116.
  117. 117. }

StandardServer中的initInternal方法:
Tomcat启动过程 - 图3
StandardService中的initInternal方法:
Tomcat启动过程 - 图4
Connector中的init方法,这里protocolHandler取决于xml中的配置,如处理bio的Http11Protocol
Protocolhandler的init实现是在AbstractProtocol
Tomcat启动过程 - 图5
AbstractProtocol中的init实现
Tomcat启动过程 - 图6
AbstractEndPoint中的init方法,调用bind
Tomcat启动过程 - 图7
2.start方法和load中的init方法类似,不再赘述
对于Connector,各个EndPoint实现中会启动acceptor线程,每个Acceptor就会监听Sorket的连接