Tomcat是一款我们平时开发过程中最常用到的Servlet容器。本系列博客会记录Tomcat的整体架构、主要组件、IO线程模型、请求在Tomcat内部的流转过程以及一些Tomcat调优的相关知识。

力求达到以下几个目的:

  • 更加熟悉Tomcat的工作机制,工作中遇到Tomcat相关问题能够快速定位,从源头来解决;
  • 对于一些高并发场景能够对Tomcat进行调优;
  • 通过对Tomcat源码的分析,吸收一些Tomcat的设计的理念,应用到自己的软件开发过程中。

1. Bootstrap启动入口

在前面分析Tomcat启动脚本的过程中,我们最后发现startup.bat最后是通过调用Bootstrap这个类的main方法来启动Tomcat的,所以先去看下Bootstrap这个类。

  1. public static void main(String args[]) {
  2. synchronized (daemonLock) {
  3. if (daemon == null) {
  4. // Don't set daemon until init() has completed
  5. Bootstrap bootstrap = new Bootstrap();
  6. try {
  7. //创建Bootstrap对象,代用init方法
  8. bootstrap.init();
  9. } catch (Throwable t) {
  10. handleThrowable(t);
  11. t.printStackTrace();
  12. return;
  13. }
  14. daemon = bootstrap;
  15. } else {
  16. // When running as a service the call to stop will be on a new
  17. // thread so make sure the correct class loader is used to
  18. // prevent a range of class not found exceptions.
  19. Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
  20. }
  21. }
  22. try {
  23. String command = "start";
  24. if (args.length > 0) {
  25. command = args[args.length - 1];
  26. }
  27. if (command.equals("startd")) {
  28. args[args.length - 1] = "start";
  29. daemon.load(args);
  30. daemon.start();
  31. } else if (command.equals("stopd")) {
  32. args[args.length - 1] = "stop";
  33. daemon.stop();
  34. } else if (command.equals("start")) {
  35. //一般情况下会进入这步,调用Bootstrap对象的load和start方法。
  36. //将Catalina启动设置成block模式
  37. daemon.setAwait(true);
  38. daemon.load(args);
  39. daemon.start();
  40. if (null == daemon.getServer()) {
  41. System.exit(1);
  42. }
  43. } else if (command.equals("stop")) {
  44. daemon.stopServer(args);
  45. } else if (command.equals("configtest")) {
  46. daemon.load(args);
  47. if (null == daemon.getServer()) {
  48. System.exit(1);
  49. }
  50. System.exit(0);
  51. } else {
  52. log.warn("Bootstrap: command \"" + command + "\" does not exist.");
  53. }
  54. } catch (Throwable t) {
  55. // Unwrap the Exception for clearer error reporting
  56. if (t instanceof InvocationTargetException &&
  57. t.getCause() != null) {
  58. t = t.getCause();
  59. }
  60. handleThrowable(t);
  61. t.printStackTrace();
  62. System.exit(1);
  63. }
  64. }

上面的代码逻辑比较简单,如果我们正常启动tomcat,会顺序执行Bootstrap对象的init()方法, daemon.setAwait(true)、daemon.load(args)和daemon.start()方法。我们先看下Bootstrap对象的init方法:

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

这个方法主要做了以下几件事:

  • 创建commonLoader、catalinaLoader、sharedLoader类加载器(默认情况下这三个类加载器指向同一个对象。建议看看createClassLoader方法,里面做的事情还挺多,比如装载catalina.properties里配置的目录下的文件和jar包,后两个加载器的父加载器都是第一个,最后注册了MBean,可以用于JVM监控该对象);
  • 实例化一个org.apache.catalina.startup.Catalina对象,并赋值给静态成员catalinaDaemon,以sharedLoader作为入参通过反射调用该对象的setParentClassLoader方法。

执行完init()方法,就开始执行bootstrap对象的load和start方法;

  1. private void load(String[] arguments)
  2. throws Exception {
  3. // Call the load() method
  4. String methodName = "load";
  5. Object param[];
  6. Class<?> paramTypes[];
  7. if (arguments==null || arguments.length==0) {
  8. paramTypes = null;
  9. param = null;
  10. } else {
  11. paramTypes = new Class[1];
  12. paramTypes[0] = arguments.getClass();
  13. param = new Object[1];
  14. param[0] = arguments;
  15. }
  16. Method method =
  17. catalinaDaemon.getClass().getMethod(methodName, paramTypes);
  18. if (log.isDebugEnabled())
  19. log.debug("Calling startup class " + method);
  20. method.invoke(catalinaDaemon, param);
  21. }

调用catalinaDaemon对象的load方法,catalinaDaemon这个对象的类型是org.apache.catalina.startup.Catalina。strat方法也是类似的,最后都是调用Catalina的start方法。

总结下Bootstrap的启动方法最主要干的事情就是创建了Catalina对象,并调用它的load和start方法。

2. Catalina的load和start方法

第一节分析到Bootstrap会触发调用Catalina的load和start方法。

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

将上面的代码精简下:

  1. Digester digester = createStartDigester();
  2. inputSource.setByteStream(inputStream);
  3. digester.push(this);
  4. digester.parse(inputSource);
  5. getServer().setCatalina(this);
  6. getServer().init();

做的事情就两个:

  • 创建一个Digester对象(Digester对象的作用就是解析server.xml配置文件,这边会先加载conf/server.xml文件,找不到的话会尝试加载server-embed.xml这个配置文件),解析完成后生成org.apache.catalina.core.StandardServer、org.apache.catalina.core.StandardService、org.apache.catalina.connector.Connector、org.apache.catalina.core.StandardEngine、org.apache.catalina.core.StandardHost、org.apache.catalina.core.StandardContext等等一系列对象,这些对象从前到后前一个包含后一个对象的引用(一对一或一对多的关系)。最后将StandardServer赋值给Catalina对象的server属性;如果你配置了连接器组件共享的线程池,还会生成StandardThreadExecutor对象。
  • 第二件事就是调用StandardServer的init方法。

临时总结下:Catalina的load方法的作用主要是解析conf/server.xml,生成StandardServer对象,再触发StandardServer的init方法。

第一节中还分析到Bootstrap会触发调用Catalina的start方法。那么我们看看start方法中干了什么。

  1. /**
  2. * Start a new server instance.
  3. */
  4. public void start() {
  5. if (getServer() == null) {
  6. load();
  7. }
  8. if (getServer() == null) {
  9. log.fatal("Cannot start server. Server instance is not configured.");
  10. return;
  11. }
  12. long t1 = System.nanoTime();
  13. // Start the new server
  14. try {
  15. getServer().start();
  16. } catch (LifecycleException e) {
  17. log.fatal(sm.getString("catalina.serverStartFail"), e);
  18. try {
  19. getServer().destroy();
  20. } catch (LifecycleException e1) {
  21. log.debug("destroy() failed for failed Server ", e1);
  22. }
  23. return;
  24. }
  25. long t2 = System.nanoTime();
  26. if(log.isInfoEnabled()) {
  27. log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
  28. }
  29. // Register shutdown hook
  30. if (useShutdownHook) {
  31. if (shutdownHook == null) {
  32. shutdownHook = new CatalinaShutdownHook();
  33. }
  34. Runtime.getRuntime().addShutdownHook(shutdownHook);
  35. // If JULI is being used, disable JULI's shutdown hook since
  36. // shutdown hooks run in parallel and log messages may be lost
  37. // if JULI's hook completes before the CatalinaShutdownHook()
  38. LogManager logManager = LogManager.getLogManager();
  39. if (logManager instanceof ClassLoaderLogManager) {
  40. ((ClassLoaderLogManager) logManager).setUseShutdownHook(
  41. false);
  42. }
  43. }
  44. if (await) {
  45. await();
  46. stop();
  47. }
  48. }

这段代码最主要的作用就是调用StandardServer对象的start方法。

总结下:Catalina对象的laod和start方法的作用是解析conf/server.xml,生成StandardServer对象,再触发StandardServer的init方法和start方法。

到这边为止我们可以看到Tomcat的启动流程还是很清晰的,下面继续看StandardServer的init方法和start到底干了些什么。

3.StandardServer的init和start方法

通过寻找StandardServer的init方法,我们发现StandardServer本身没有实现这个方法,这个方法是它从父类LifecycleBase中继承过来的:

  1. @Override
  2. public final synchronized void init() throws LifecycleException {
  3. if (!state.equals(LifecycleState.NEW)) {
  4. invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
  5. }
  6. try {
  7. //发布初始化容器时间,对应的listener做相应处理
  8. setStateInternal(LifecycleState.INITIALIZING, null, false);
  9. //调用子类的initInternal()
  10. initInternal();
  11. //发布容器已经初始化事件,对应的listener做相应处理
  12. setStateInternal(LifecycleState.INITIALIZED, null, false);
  13. } catch (Throwable t) {
  14. handleSubClassException(t, "lifecycleBase.initFail", toString());
  15. }
  16. }

所以调用StandardServer的init方法,其实是促发了容器初始化事件发布,然后又调到了StandardServer的initInternal方法。那么我们看看StandardServer的start方法的逻辑是什么。

代码点进去,发现StandardServer的start方法也是调的父类LifecycleBase中的方法。

  1. @Override
  2. public final synchronized void start() throws LifecycleException {
  3. if (LifecycleState.STARTING_PREP.equals(state) || LifecycleState.STARTING.equals(state) ||
  4. LifecycleState.STARTED.equals(state)) {
  5. if (log.isDebugEnabled()) {
  6. Exception e = new LifecycleException();
  7. log.debug(sm.getString("lifecycleBase.alreadyStarted", toString()), e);
  8. } else if (log.isInfoEnabled()) {
  9. log.info(sm.getString("lifecycleBase.alreadyStarted", toString()));
  10. }
  11. return;
  12. }
  13. if (state.equals(LifecycleState.NEW)) {
  14. init();
  15. } else if (state.equals(LifecycleState.FAILED)) {
  16. stop();
  17. } else if (!state.equals(LifecycleState.INITIALIZED) &&
  18. !state.equals(LifecycleState.STOPPED)) {
  19. invalidTransition(Lifecycle.BEFORE_START_EVENT);
  20. }
  21. try {
  22. //发布事件
  23. setStateInternal(LifecycleState.STARTING_PREP, null, false);
  24. //调用子类的startInternal
  25. startInternal();
  26. if (state.equals(LifecycleState.FAILED)) {
  27. // This is a 'controlled' failure. The component put itself into the
  28. // FAILED state so call stop() to complete the clean-up.
  29. stop();
  30. } else if (!state.equals(LifecycleState.STARTING)) {
  31. // Shouldn't be necessary but acts as a check that sub-classes are
  32. // doing what they are supposed to.
  33. invalidTransition(Lifecycle.AFTER_START_EVENT);
  34. } else {
  35. //发布容器启动事件
  36. setStateInternal(LifecycleState.STARTED, null, false);
  37. }
  38. } catch (Throwable t) {
  39. // This is an 'uncontrolled' failure so put the component into the
  40. // FAILED state and throw an exception.
  41. handleSubClassException(t, "lifecycleBase.startFail", toString());
  42. }
  43. }

从以上init和start方法的定义可以看到这两个方法最终将会调用StandardServer中定义的initInternal和startInternal。

先来看initInternal方法

  1. protected void initInternal() throws LifecycleException {
  2. super.initInternal();
  3. // Register global String cache
  4. // Note although the cache is global, if there are multiple Servers
  5. // present in the JVM (may happen when embedding) then the same cache
  6. // will be registered under multiple names
  7. onameStringCache = register(new StringCache(), "type=StringCache");
  8. // Register the MBeanFactory
  9. MBeanFactory factory = new MBeanFactory();
  10. factory.setContainer(this);
  11. onameMBeanFactory = register(factory, "type=MBeanFactory");
  12. // Register the naming resources
  13. globalNamingResources.init();
  14. // Populate the extension validator with JARs from common and shared
  15. // class loaders
  16. if (getCatalina() != null) {
  17. ClassLoader cl = getCatalina().getParentClassLoader();
  18. // Walk the class loader hierarchy. Stop at the system class loader.
  19. // This will add the shared (if present) and common class loaders
  20. while (cl != null && cl != ClassLoader.getSystemClassLoader()) {
  21. if (cl instanceof URLClassLoader) {
  22. URL[] urls = ((URLClassLoader) cl).getURLs();
  23. for (URL url : urls) {
  24. if (url.getProtocol().equals("file")) {
  25. try {
  26. File f = new File (url.toURI());
  27. if (f.isFile() &&
  28. f.getName().endsWith(".jar")) {
  29. ExtensionValidator.addSystemResource(f);
  30. }
  31. } catch (URISyntaxException e) {
  32. // Ignore
  33. } catch (IOException e) {
  34. // Ignore
  35. }
  36. }
  37. }
  38. }
  39. cl = cl.getParent();
  40. }
  41. }
  42. // Initialize our defined Services
  43. for (int i = 0; i < services.length; i++) {
  44. services[i].init();
  45. }
  46. }

重点代码在最后,循环调用了Service组件的init方法。

再来看StandardServer的startInternal方法

  1. @Override
  2. protected void startInternal() throws LifecycleException {
  3. fireLifecycleEvent(CONFIGURE_START_EVENT, null);
  4. setState(LifecycleState.STARTING);
  5. globalNamingResources.start();
  6. // Start our defined Services
  7. synchronized (servicesLock) {
  8. for (int i = 0; i < services.length; i++) {
  9. services[i].start();
  10. }
  11. }
  12. }

也是循环调用了Service组件的start方法。这边的Service组件就是在从conf/server.xml中解析出来的StandardService对象,查看下这个类的继承体系:

  1. LifecycleBase (org.apache.catalina.util)
  2. LifecycleMBeanBase (org.apache.catalina.util)
  3. StandardService (org.apache.catalina.core)

我们发现这个类继承体系和StandardServer是一样的。其实我们再观察的仔细一点会发现从conf/server.xml解析胡来的类的继承体系都是一样的。所以我们调用这些类的init和start方法最后还是会调用到他们的initInternal和startInternal方法。

4. StandardService的initInternal和startInternal方法

先看下StandardService的initInternal方法

  1. @Override
  2. protected void initInternal() throws LifecycleException {
  3. super.initInternal();
  4. //调用engine的initInternal方法,这个方法中也没做特别重要的操作,只是做了一个getReal操作
  5. if (engine != null) {
  6. engine.init();
  7. }
  8. //StandardThreadExecutor的initInternal方法中没没干什么事情
  9. // Initialize any Executors
  10. for (Executor executor : findExecutors()) {
  11. if (executor instanceof JmxEnabled) {
  12. ((JmxEnabled) executor).setDomain(getDomain());
  13. }
  14. executor.init();
  15. }
  16. // Initialize mapper listener
  17. //这步也没做什么重要操作
  18. mapperListener.init();
  19. // Initialize our defined Connectors
  20. // 连接器主键的初始化,主要是检查连接器的protocolHandler的主键,并将其初始化.
  21. synchronized (connectorsLock) {
  22. for (Connector connector : connectors) {
  23. connector.init();
  24. }
  25. }
  26. }

看了上面的代码,觉得Tomcat源码逻辑还是很清晰的。之前在分析Tomcat组件的文章中讲到Service组件是有Connector组件、engine组件和一个可选的线程池组成。上面的代码中正好对应了这三个组件的初始化话。

Connector组件和engine组件的初始化又会触发他们各自子组件的初始化,所以StandardService的initInternal方法会触发Tomcat下各类组件的初始化。这边大致记录下各个组件初始化话的顺序:

  • engine组件初始化:engine组件初始化没做什么特别的操作,也没触发它的子组件(Host、Context和Wrapper组件的初始化),所以这步比较简单;
  • Executor组件的初始化:没有触发其他组件初始化;
  • Mapper组件初始化:mapper组件初始化也没干什么重要的操作,也没触发其他子组件初始化;
  • Connector组件初始化:检查连接器的protocolHandler的子组件,并触发其初始化
  • ProtocolHandler组件初始化:触发Endpoint组件初始化,Endpoint类才是接收转化请求的真正的类;

然后再看StandardService的startInternal方法

  1. protected void startInternal() throws LifecycleException {
  2. if(log.isInfoEnabled())
  3. log.info(sm.getString("standardService.start.name", this.name));
  4. setState(LifecycleState.STARTING);
  5. // Start our defined Container first
  6. if (engine != null) {
  7. synchronized (engine) {
  8. engine.start();
  9. }
  10. }
  11. synchronized (executors) {
  12. for (Executor executor: executors) {
  13. executor.start();
  14. }
  15. }
  16. mapperListener.start();
  17. // Start our defined Connectors second
  18. synchronized (connectorsLock) {
  19. for (Connector connector: connectors) {
  20. // If it has already failed, don't try and start it
  21. if (connector.getState() != LifecycleState.FAILED) {
  22. connector.start();
  23. }
  24. }
  25. }
  26. }

逻辑依然很清楚,StandardService会依次触发各个子组件的start方法。

  • Engine组件的start:Engine组件的start方法组要作用还是触发了Host组件的start方法,具体代码见

    1. protected synchronized void startInternal() throws LifecycleException {
    2. // Start our subordinate components, if any
    3. if ((loader != null) && (loader instanceof Lifecycle))
    4. ((Lifecycle) loader).start();
    5. logger = null;
    6. getLogger();
    7. if ((manager != null) && (manager instanceof Lifecycle))
    8. ((Lifecycle) manager).start();
    9. if ((cluster != null) && (cluster instanceof Lifecycle))
    10. ((Lifecycle) cluster).start();
    11. Realm realm = getRealmInternal();
    12. if ((realm != null) && (realm instanceof Lifecycle))
    13. ((Lifecycle) realm).start();
    14. if ((resources != null) && (resources instanceof Lifecycle))
    15. ((Lifecycle) resources).start();
    16. // 找出Engine的子容器,也就是Host容器
    17. Container children[] = findChildren();
    18. List<Future<Void>> results = new ArrayList<Future<Void>>();
    19. //利用线程池调用Host的start方法
    20. for (int i = 0; i < children.length; i++) {
    21. results.add(startStopExecutor.submit(new StartChild(children[i])));
    22. }
    23. boolean fail = false;
    24. for (Future<Void> result : results) {
    25. try {
    26. result.get();
    27. } catch (Exception e) {
    28. log.error(sm.getString("containerBase.threadedStartFailed"), e);
    29. fail = true;
    30. }
    31. }
    32. if (fail) {
    33. throw new LifecycleException(
    34. sm.getString("containerBase.threadedStartFailed"));
    35. }
    36. // Start the Valves in our pipeline (including the basic), if any
    37. if (pipeline instanceof Lifecycle)
    38. ((Lifecycle) pipeline).start();
    39. setState(LifecycleState.STARTING);
    40. // Start our thread
    41. threadStart();
    42. }
  • Host组件的start:经过前面介绍,我们知道Host组件的start方法最后还是会调用自己startInternal方法;

  • Context组件的start:触发Wrapper的start,加载filter、Servlet等;

  • Wrapper组件的start:

这边我们重点看下StandardContext的startInternal,这个方法干的事情比较多:

  1. protected synchronized void startInternal() throws LifecycleException {
  2. if(log.isDebugEnabled())
  3. log.debug("Starting " + getBaseName());
  4. // Send j2ee.state.starting notification
  5. if (this.getObjectName() != null) {
  6. Notification notification = new Notification("j2ee.state.starting",
  7. this.getObjectName(), sequenceNumber.getAndIncrement());
  8. broadcaster.sendNotification(notification);
  9. }
  10. setConfigured(false);
  11. boolean ok = true;
  12. // Currently this is effectively a NO-OP but needs to be called to
  13. // ensure the NamingResources follows the correct lifecycle
  14. if (namingResources != null) {
  15. namingResources.start();
  16. }
  17. // Post work directory
  18. postWorkDirectory();
  19. // Add missing components as necessary
  20. if (getResources() == null) { // (1) Required by Loader
  21. if (log.isDebugEnabled())
  22. log.debug("Configuring default Resources");
  23. try {
  24. setResources(new StandardRoot(this));
  25. } catch (IllegalArgumentException e) {
  26. log.error(sm.getString("standardContext.resourcesInit"), e);
  27. ok = false;
  28. }
  29. }
  30. if (ok) {
  31. resourcesStart();
  32. }
  33. if (getLoader() == null) {
  34. WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
  35. webappLoader.setDelegate(getDelegate());
  36. setLoader(webappLoader);
  37. }
  38. // An explicit cookie processor hasn't been specified; use the default
  39. if (cookieProcessor == null) {
  40. cookieProcessor = new Rfc6265CookieProcessor();
  41. }
  42. // Initialize character set mapper
  43. getCharsetMapper();
  44. // Validate required extensions
  45. boolean dependencyCheck = true;
  46. try {
  47. dependencyCheck = ExtensionValidator.validateApplication
  48. (getResources(), this);
  49. } catch (IOException ioe) {
  50. log.error(sm.getString("standardContext.extensionValidationError"), ioe);
  51. dependencyCheck = false;
  52. }
  53. if (!dependencyCheck) {
  54. // do not make application available if dependency check fails
  55. ok = false;
  56. }
  57. // Reading the "catalina.useNaming" environment variable
  58. String useNamingProperty = System.getProperty("catalina.useNaming");
  59. if ((useNamingProperty != null)
  60. && (useNamingProperty.equals("false"))) {
  61. useNaming = false;
  62. }
  63. if (ok && isUseNaming()) {
  64. if (getNamingContextListener() == null) {
  65. NamingContextListener ncl = new NamingContextListener();
  66. ncl.setName(getNamingContextName());
  67. ncl.setExceptionOnFailedWrite(getJndiExceptionOnFailedWrite());
  68. addLifecycleListener(ncl);
  69. setNamingContextListener(ncl);
  70. }
  71. }
  72. // Standard container startup
  73. if (log.isDebugEnabled())
  74. log.debug("Processing standard container startup");
  75. // Binding thread
  76. ClassLoader oldCCL = bindThread();
  77. try {
  78. if (ok) {
  79. // Start our subordinate components, if any
  80. Loader loader = getLoader();
  81. if (loader instanceof Lifecycle) {
  82. ((Lifecycle) loader).start();
  83. }
  84. // since the loader just started, the webapp classloader is now
  85. // created.
  86. setClassLoaderProperty("clearReferencesRmiTargets",
  87. getClearReferencesRmiTargets());
  88. setClassLoaderProperty("clearReferencesStopThreads",
  89. getClearReferencesStopThreads());
  90. setClassLoaderProperty("clearReferencesStopTimerThreads",
  91. getClearReferencesStopTimerThreads());
  92. setClassLoaderProperty("clearReferencesHttpClientKeepAliveThread",
  93. getClearReferencesHttpClientKeepAliveThread());
  94. setClassLoaderProperty("clearReferencesObjectStreamClassCaches",
  95. getClearReferencesObjectStreamClassCaches());
  96. setClassLoaderProperty("skipMemoryLeakChecksOnJvmShutdown",
  97. getSkipMemoryLeakChecksOnJvmShutdown());
  98. // By calling unbindThread and bindThread in a row, we setup the
  99. // current Thread CCL to be the webapp classloader
  100. unbindThread(oldCCL);
  101. oldCCL = bindThread();
  102. // Initialize logger again. Other components might have used it
  103. // too early, so it should be reset.
  104. logger = null;
  105. getLogger();
  106. Realm realm = getRealmInternal();
  107. if(null != realm) {
  108. if (realm instanceof Lifecycle) {
  109. ((Lifecycle) realm).start();
  110. }
  111. // Place the CredentialHandler into the ServletContext so
  112. // applications can have access to it. Wrap it in a "safe"
  113. // handler so application's can't modify it.
  114. CredentialHandler safeHandler = new CredentialHandler() {
  115. @Override
  116. public boolean matches(String inputCredentials, String storedCredentials) {
  117. return getRealmInternal().getCredentialHandler().matches(inputCredentials, storedCredentials);
  118. }
  119. @Override
  120. public String mutate(String inputCredentials) {
  121. return getRealmInternal().getCredentialHandler().mutate(inputCredentials);
  122. }
  123. };
  124. context.setAttribute(Globals.CREDENTIAL_HANDLER, safeHandler);
  125. }
  126. // Notify our interested LifecycleListeners
  127. fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
  128. // Start our child containers, if not already started
  129. for (Container child : findChildren()) {
  130. if (!child.getState().isAvailable()) {
  131. child.start();
  132. }
  133. }
  134. // Start the Valves in our pipeline (including the basic),
  135. // if any
  136. if (pipeline instanceof Lifecycle) {
  137. ((Lifecycle) pipeline).start();
  138. }
  139. // Acquire clustered manager
  140. Manager contextManager = null;
  141. Manager manager = getManager();
  142. if (manager == null) {
  143. if (log.isDebugEnabled()) {
  144. log.debug(sm.getString("standardContext.cluster.noManager",
  145. Boolean.valueOf((getCluster() != null)),
  146. Boolean.valueOf(distributable)));
  147. }
  148. if ( (getCluster() != null) && distributable) {
  149. try {
  150. contextManager = getCluster().createManager(getName());
  151. } catch (Exception ex) {
  152. log.error("standardContext.clusterFail", ex);
  153. ok = false;
  154. }
  155. } else {
  156. contextManager = new StandardManager();
  157. }
  158. }
  159. // Configure default manager if none was specified
  160. if (contextManager != null) {
  161. if (log.isDebugEnabled()) {
  162. log.debug(sm.getString("standardContext.manager",
  163. contextManager.getClass().getName()));
  164. }
  165. setManager(contextManager);
  166. }
  167. if (manager!=null && (getCluster() != null) && distributable) {
  168. //let the cluster know that there is a context that is distributable
  169. //and that it has its own manager
  170. getCluster().registerManager(manager);
  171. }
  172. }
  173. if (!getConfigured()) {
  174. log.error(sm.getString("standardContext.configurationFail"));
  175. ok = false;
  176. }
  177. // We put the resources into the servlet context
  178. if (ok)
  179. getServletContext().setAttribute
  180. (Globals.RESOURCES_ATTR, getResources());
  181. if (ok ) {
  182. if (getInstanceManager() == null) {
  183. javax.naming.Context context = null;
  184. if (isUseNaming() && getNamingContextListener() != null) {
  185. context = getNamingContextListener().getEnvContext();
  186. }
  187. Map<String, Map<String, String>> injectionMap = buildInjectionMap(
  188. getIgnoreAnnotations() ? new NamingResourcesImpl(): getNamingResources());
  189. setInstanceManager(new DefaultInstanceManager(context,
  190. injectionMap, this, this.getClass().getClassLoader()));
  191. }
  192. getServletContext().setAttribute(
  193. InstanceManager.class.getName(), getInstanceManager());
  194. InstanceManagerBindings.bind(getLoader().getClassLoader(), getInstanceManager());
  195. }
  196. // Create context attributes that will be required
  197. if (ok) {
  198. getServletContext().setAttribute(
  199. JarScanner.class.getName(), getJarScanner());
  200. }
  201. // Set up the context init params
  202. mergeParameters();
  203. // Call ServletContainerInitializers
  204. for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
  205. initializers.entrySet()) {
  206. try {
  207. entry.getKey().onStartup(entry.getValue(),
  208. getServletContext());
  209. } catch (ServletException e) {
  210. log.error(sm.getString("standardContext.sciFail"), e);
  211. ok = false;
  212. break;
  213. }
  214. }
  215. // Configure and call application event listeners
  216. if (ok) {
  217. if (!listenerStart()) {
  218. log.error(sm.getString("standardContext.listenerFail"));
  219. ok = false;
  220. }
  221. }
  222. // Check constraints for uncovered HTTP methods
  223. // Needs to be after SCIs and listeners as they may programmatically
  224. // change constraints
  225. if (ok) {
  226. checkConstraintsForUncoveredMethods(findConstraints());
  227. }
  228. try {
  229. // Start manager
  230. Manager manager = getManager();
  231. if (manager instanceof Lifecycle) {
  232. ((Lifecycle) manager).start();
  233. }
  234. } catch(Exception e) {
  235. log.error(sm.getString("standardContext.managerFail"), e);
  236. ok = false;
  237. }
  238. // Configure and call application filters
  239. if (ok) {
  240. if (!filterStart()) {
  241. log.error(sm.getString("standardContext.filterFail"));
  242. ok = false;
  243. }
  244. }
  245. // Load and initialize all "load on startup" servlets
  246. if (ok) {
  247. if (!loadOnStartup(findChildren())){
  248. log.error(sm.getString("standardContext.servletFail"));
  249. ok = false;
  250. }
  251. }
  252. // Start ContainerBackgroundProcessor thread
  253. super.threadStart();
  254. } finally {
  255. // Unbinding thread
  256. unbindThread(oldCCL);
  257. }
  258. // Set available status depending upon startup success
  259. if (ok) {
  260. if (log.isDebugEnabled())
  261. log.debug("Starting completed");
  262. } else {
  263. log.error(sm.getString("standardContext.startFailed", getName()));
  264. }
  265. startTime=System.currentTimeMillis();
  266. // Send j2ee.state.running notification
  267. if (ok && (this.getObjectName() != null)) {
  268. Notification notification =
  269. new Notification("j2ee.state.running", this.getObjectName(),
  270. sequenceNumber.getAndIncrement());
  271. broadcaster.sendNotification(notification);
  272. }
  273. // The WebResources implementation caches references to JAR files. On
  274. // some platforms these references may lock the JAR files. Since web
  275. // application start is likely to have read from lots of JARs, trigger
  276. // a clean-up now.
  277. getResources().gc();
  278. // Reinitializing if something went wrong
  279. if (!ok) {
  280. setState(LifecycleState.FAILED);
  281. } else {
  282. setState(LifecycleState.STARTING);
  283. }
  284. }

上面的代码有4处重点:调用ServletContainerInitializers、启用Listener、启用Filter和启用startup的Servlet。这个和我们平时对Tomcat启动流程的认知是一致的。

到这里整个Container组件(包括Engine、Host、Context和Wrapper组件)的start方法调用就结束了。接下来是Connector和Mapper组件的start。

  1. //MapperListenner的startInternal
  2. public void startInternal() throws LifecycleException {
  3. setState(LifecycleState.STARTING);
  4. Engine engine = service.getContainer();
  5. if (engine == null) {
  6. return;
  7. }
  8. findDefaultHost();
  9. addListeners(engine);
  10. Container[] conHosts = engine.findChildren();
  11. for (Container conHost : conHosts) {
  12. Host host = (Host) conHost;
  13. if (!LifecycleState.NEW.equals(host.getState())) {
  14. // Registering the host will register the context and wrappers
  15. registerHost(host);
  16. }
  17. }
  18. }

以上方法的主要作用是将Host组件和域名映射起来。

最后看下Connector组件的start:

  1. protected void startInternal() throws LifecycleException {
  2. // Validate settings before starting
  3. if (getPortWithOffset() < 0) {
  4. throw new LifecycleException(sm.getString(
  5. "coyoteConnector.invalidPort", Integer.valueOf(getPortWithOffset())));
  6. }
  7. setState(LifecycleState.STARTING);
  8. try {
  9. //促发protocolHandler组件的start,最后促发endpoint组件的start
  10. //触发endpoint时会建立exceutor线程池,默认的话核心线程数10,最大线程数200
  11. //建立poller线程,最大是2个线程,如果你机器cpu的核数小于2的话就建立1个
  12. //建立accetpor线程,默认是1个(可以看看Acceptor这个类的源代码,了解下怎么接收请求的)
  13. protocolHandler.start();
  14. } catch (Exception e) {
  15. throw new LifecycleException(
  16. sm.getString("coyoteConnector.protocolHandlerStartFailed"), e);
  17. }
  18. }

通过以上一些列复杂的调用过程,最终执行完所有在server.xml里配置的节点的实现类中initInternal和startInternal方法。上面提到的org.apache.catalina.core.StandardServer、org.apache.catalina.core.StandardService、org.apache.catalina.connector.Connector、org.apache.catalina.core.StandardEngine、org.apache.catalina.core.StandardHost、org.apache.catalina.core.StandardContext等等组件的这两个方法都会调用到。

至此,Tomcat已经能开始响应浏览器发过来的请求了。至于具体的Tomcat响应请求流程会在后续博客中介绍。

5. 总结

看了整个启动流程,虽然逻辑是比较清楚的,但是流程比较上,所以有必要做下总结:

  • step1:Bootstrap作为整个Tomcat主启动类,最主要的功能是创建Catalina对象,并调用它的load和start方法;

  • step2:Catalina的load方法的作用主要是解析conf/server.xml,生成StandardServer对象(此时生成StandardServer对象中已经包含了各种子组件,比如StandardService、StandardEngine等),再触发StandardServer的init方法;Catalina的start方法又触发了StandardServer的start方法;

  • step3:StandardServer的init方法和start方法会依次触发各个子组件的initInternal和startInternal方法。大致的触发顺序是:

    Engine组件的initInternal(这边要注意的是Engine组件并没有触发它的子组件Host、Context和Wrapper的initInternal)-->Executor组件initInternal(处理请求的工作线程池)-->Mapper组件初始化(mapper组件初始化也没干什么重要的操作,也没触发其他子组件初始化)-->Connector组件初始化(检查连接器的protocolHandler的子组件,并触发其初始化)-->ProtocolHandler组件初始化(触发Endpoint组件初始化)​

​ Engine组件的startInternal(主要作用是触发Host组件的start)-->Host组件的startInternal(主要作用是触发Context组件的startInternal)-->Contextz组件的startInternal(加载调用ServletContainerInitializers、加载Listener、加载filtr和startup的Servlet,并且触发Wrapper组件的startInternal)-->Wrapper组件的startInternal(加载映射Servlet)-->Mapper组件的startInternal(将域名和Host组件映射起来)-->Connector组件的startInternal(protocolHandler组件的start,最后促发endpoint组件的start)

Tomcat启动流程简析的更多相关文章

  1. React Native 启动流程简析

    导读:本文以 react-native-cli 创建的示例工程(安卓部分)为例,分析 React Native 的启动流程. 工程创建步骤可以参考官网.本文所分析 React Native 版本为 v ...

  2. Android 启动过程简析

    首先我们先来看android构架图: android系统是构建在linux系统上面的. 所以android设备启动经历3个过程. Boot Loader,Linux Kernel & Andr ...

  3. zxing二维码扫描的流程简析(Android版)

    目前市面上二维码的扫描似乎用开源google的zxing比较多,接下去以2.2版本做一个简析吧,勿喷... 下载下来后定位两个文件夹,core和android,core是一些核心的库,android是 ...

  4. OpenStack Cinder源代码流程简析

    版权声明:本博客欢迎转载,转载时请以超链接形式标明文章原始出处!谢谢! 博客地址:http://blog.csdn.net/i_chips 一.概况 OpenStack的各个模块都有对应的client ...

  5. TOMCAT启动流程分析

    ------------------tomcat服务开启----------2014-9-26 9:17:07 org.apache.catalina.core.AprLifecycleListene ...

  6. tomcat 工作原理简析

    https://github.com/HappyTomas/another-tutorial-about-java-web/blob/master/00-08.md 在00-02.理解HTTP中给出了 ...

  7. Tomcat和Servlet简析

    目录 Servlet Tomcat 参考 Servlet Servlet通常指我们继承了Servlet接口的类,我们开发Servlet时一般就是继承HttpServlet重写它的doGet.doPos ...

  8. LinkedHashMap结构get和put源码流程简析及LRU应用

    原理这篇讲得比较透彻Java集合之LinkedHashMap. 本文属于源码阅读笔记,因put,get调用逻辑及链表维护逻辑复杂(至少网上其它文章的逻辑描述及配图,我都没看明白LinkedHashMa ...

  9. jquery选择器的实现流程简析及提高性能建议!

    当我们洋洋得意的使用jquery强大的选择器功能时有没有在意过jquery的选择性能问题呢,其实要想高效的使用jquery选择器,了解其实现流程是很有必要的,那么这篇文章我就简单的讲讲其实现流程,相信 ...

随机推荐

  1. Java实现 LeetCode 71 简化路径

    71. 简化路径 以 Unix 风格给出一个文件的绝对路径,你需要简化它.或者换句话说,将其转换为规范路径. 在 Unix 风格的文件系统中,一个点(.)表示当前目录本身:此外,两个点 (-) 表示将 ...

  2. Java实现 LeetCode 12 整数转罗马数字

    12. 整数转罗马数字 罗马数字包含以下七种字符: I, V, X, L,C,D 和 M. 字符 数值 I 1 V 5 X 10 L 50 C 100 D 500 M 1000 例如, 罗马数字 2 ...

  3. Java实现第八届蓝桥杯迷宫

    迷宫 题目描述 X星球的一处迷宫游乐场建在某个小山坡上. 它是由10x10相互连通的小房间组成的. 房间的地板上写着一个很大的字母. 我们假设玩家是面朝上坡的方向站立,则: L表示走到左边的房间, R ...

  4. XStrea学习手册

    ​​ 一.前言 1.XStream官网 http://x-stream.github.io 2.XStream是什么 XStream是一个简单的基于Java的类库,用来将Java对象序列化成XML(J ...

  5. 2018年全国多校算法寒假训练营练习比赛(第二场)H-了断局

    题目描述 既然是了断局了,大家就随便玩玩数字呗.已知一个数列前十项分别是{0, 1, 1, 2, 4, 7, 13, 24, 44, 81},小G不满足呀:我要更多的数!!!不给就不让你们玩了.小G会 ...

  6. 温故知新-Mysql锁&事务&MVCC

    文章目录 锁概述 锁分类 MyISAM 表锁 InnoDB 行锁 事务及其ACID属性 InnoDB 的行锁模式 注意 MVCC InnoDB 中的 MVCC 参考 你的鼓励也是我创作的动力 Post ...

  7. css3图片防止变形

    1.object-fit 由于图片原始大小都不一样,强行设定大小值会导致拉伸,如果不设定大小则参差不齐. 之前我们大多数用的 大多数都是利用background-size: cover 来避免对图片造 ...

  8. XAI/MLI 可解释机器学习系列1- 开源&paper汇总

    一直在关注可解释机器学习领域,因为确实在工作中有许多应用 模型检查,特征重要性是否符合预期和AUC一样重要 模型解释,比起虚无缥缈的模型指标,解释模型学到的规律更能说服业务方 样本解释,为什么这些用户 ...

  9. http的几种请求方法

    1.HTTP请求方法有以下8种方法:OPTIONS.GET.HEAD.POST.PUT.DELETE.TRACE和CONNECT. GET GET请求会显示请求指定的资源.一般来说GET方法应该只用于 ...

  10. 淘宝官网css初始化

    body, h1, h2, h3, h4, h5, h6, hr, p, blockquote, dl, dt, dd, ul, ol, li, pre, form, fieldset, legend ...