飞道的博客

Tomcat源码分析--启动流程

517人阅读  评论(0)

1.概述

要想了解Tomcat的启动流程,必须先弄明白Tomcat有哪些组件。而对于Tomcat组件的层级结构了解,我们必须弄明白Tomcat一个最重要的配置文件“server.xml”,如果有过Tomcat调优经验或者对Tomat有一定了解的话,一定知道这个文件,他位于${tomcat.base}/conf/server.xml。通过这个文件,我们可以配给几乎所有tomcat的参数信息(当然不会包括jvm相关的参数)。那么我们先看一下server.xml的真实面目。为了更清楚的展现Tomcat组件,我把server.xml中的注释部分全部删除了。


  
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <Server port="8005" shutdown="SHUTDOWN">
  3. <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
  4. <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  5. <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  6. <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  7. <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
  8. <GlobalNamingResources>
  9. <Resource name="UserDatabase" auth="Container"
  10. type= "org.apache.catalina.UserDatabase"
  11. description= "User database that can be updated and saved"
  12. factory= "org.apache.catalina.users.MemoryUserDatabaseFactory"
  13. pathname= "conf/tomcat-users.xml" />
  14. </GlobalNamingResources>
  15. <Service name="Catalina">
  16. <Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
  17. maxThreads= "150" minSpareThreads= "4"/>
  18. <Connector executor="tomcatThreadPool" port="8080" protocol="HTTP/1.1"
  19. connectionTimeout= "20000"
  20. redirectPort= "8443" />
  21. <Engine name="Catalina" defaultHost="localhost">
  22. <Realm className="org.apache.catalina.realm.LockOutRealm">
  23. <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
  24. resourceName= "UserDatabase"/>
  25. </Realm>
  26. <Host name="localhost" appBase="webapps"
  27. unpackWARs= "true" autoDeploy= "true">
  28. <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
  29. prefix= "localhost_access_log" suffix= ".txt"
  30. pattern= "%h %l %u %t &quot;%r&quot; %s %b" />
  31. <Context docBase="webapps/test" path="/test" reloadable="true" />
  32. </Host>
  33. </Engine>
  34. </Service>
  35. </Server>

需要指出的是,server.xml配置文件中并没有<Context>元素标签,不是说这里不可以用这个标签,而是Tomcat的官方文档并不提议通过这种方式设置应用程序的位置。在http://ip:8080/docs/config/context.html中有如下一段话:

这段话的开头就说明 了并不建议直接在server.xml文件中添加<Context>元素。当然,感兴趣的可以往后面看看原因。说了这么多,看到上面的按个xml其实还是有些犯怵,这么多,目录结构哪有那么清晰,为了方便大家掌握,我有做了一个目录结构图:

在上图中,Protocol Handler并不是server.xml的一个元素,但是他通过<Connector>中的protocol属性配置的,默认HTTP/1.1。然后针对不同版本的tomat(比如tomcat7和tomcatd8),默认的协议处理器是不一样的。下方代码块是取自tomcat8.5.59。


  
  1. /**
  2. * Tomcat8.5.59
  3. *org.apache.catalina.connector.Connector#setProtocol
  4. */
  5. public void setProtocol(String protocol) {
  6. boolean aprConnector = AprLifecycleListener.isAprAvailable() &&
  7. AprLifecycleListener.getUseAprConnector();
  8. if ( "HTTP/1.1".equals(protocol) || protocol == null) {
  9. if (aprConnector) {
  10. setProtocolHandlerClassName( "org.apache.coyote.http11.Http11AprProtocol");
  11. } else {
  12. setProtocolHandlerClassName( "org.apache.coyote.http11.Http11NioProtocol");
  13. }
  14. } else if ( "AJP/1.3".equals(protocol)) {
  15. if (aprConnector) {
  16. setProtocolHandlerClassName( "org.apache.coyote.ajp.AjpAprProtocol");
  17. } else {
  18. setProtocolHandlerClassName( "org.apache.coyote.ajp.AjpNioProtocol");
  19. }
  20. } else {
  21. //决定可以直接输入类名
  22. setProtocolHandlerClassName(protocol);
  23. }
  24. }

如下代码块取自Tomcat7..0.103。


  
  1. /**
  2. * Tomcat7.0.103
  3. *org.apache.catalina.connector.Connector#setProtocol
  4. */
  5. public void setProtocol(String protocol) {
  6. if (AprLifecycleListener.isAprAvailable()) {
  7. if ( "HTTP/1.1".equals(protocol)) {
  8. setProtocolHandlerClassName
  9. ( "org.apache.coyote.http11.Http11AprProtocol");
  10. } else if ( "AJP/1.3".equals(protocol)) {
  11. setProtocolHandlerClassName
  12. ( "org.apache.coyote.ajp.AjpAprProtocol");
  13. } else if (protocol != null) {
  14. setProtocolHandlerClassName(protocol);
  15. } else {
  16. setProtocolHandlerClassName
  17. ( "org.apache.coyote.http11.Http11AprProtocol");
  18. }
  19. } else {
  20. if ( "HTTP/1.1".equals(protocol)) {
  21. setProtocolHandlerClassName
  22. ( "org.apache.coyote.http11.Http11Protocol");
  23. } else if ( "AJP/1.3".equals(protocol)) {
  24. setProtocolHandlerClassName
  25. ( "org.apache.coyote.ajp.AjpProtocol");
  26. } else if (protocol != null) {
  27. setProtocolHandlerClassName(protocol);
  28. }
  29. }
  30. }

通过上面两个代码块,我们清楚的指导Tomcat8.5.59配置的类为org.apache.coyote.http11.Http11NioProtocol。Tomcat7.0.103中配置的类org.apache.coyote.http11.Http11Protocol。所以Tomcat8.5.59采用同步非阻塞的方式,而Tomcat7.0.103则采用同步阻塞的方式。

到目前为止,我们了解了Tomcat的主要组件以及层次结构,那么接下来我们就进入Tomcat的源码世界,一起揭开他的神秘面纱。

2. 流程时序图

为了进一步更好的理解Tomat的启动流程,我们先来一张时序图,心里先有个粗略的印象。需要注意的事,要注意每一步的序号,接下来我是按照序号进行讲解的。

3.启动流程源码分析

通过上面的时序图,我们可以看出来Tomcat启动主要就是三步,Tomcat环境的初始化、Tomcat组件的各个init方法以及Tomcat组件的各个start方法。由于Tomcat的各个组件都要记录到生命周期中,所以基本被org.apache.catalina.util. LifecycleMBeanBase的init和start方法包裹,真正的实现是以Standard开头的类的以Internal结尾的发方法中。比如,service的init方法的具体实现为org.apache.catalina.core.StandardService#initInternal。接下里,我们就以Tomcat的入口类org.apache. catalina.startup.Bootstrap(以下简称"Bootstrap")作为切入点,进行一步一步的详细分析。

3.1 Tomcat环境初始化(0:static)

通过0:static这个关键字,相信大家已经猜出这一部分代码主要是在哪执行的吧?没错,是Tomcat启动类Bootstrap的静态代码块中。针对这段代码块,主要实现一个功能,初始化catalinaHomeFile和catalinaBaseFile两个变量,便于寻找Tomcat涉及到的配置文件、classpath等路径。针对catalinaHomeFile变量,寻找的优先级为:环境变量catalina.home->包含bootsrap.jar的父级目录->程序运行的当前路径System.getProperty("user.dir");针对catalinaBaseFile变量,寻找的优先级为:环境变量catalina.base->catalinaHomeFile。

3.2 Bootstap对象的初始化(1.1:init)

在方法org.apache.catalina.startup.Bootstrap#init()中,主要做了如下几件事:

1)初始化Tomcat的三大类加载器(commonLoader->catalinaLoader->sharedLoader)。

类加载的初始化主要依赖于配置文件${tomcat.base}/conf/catalina.properties中的common.loader、server.loader和shared.loader。默认情况下,common.loader会有值,commonLoader作为jvm中AppClassLoader的子加载器,主要增加加载Tomcat下的的jar或者class文件,而server.loader和shared.loader属性配置为空,将commonLoader作为自己的加载器。也就是说,默认情况下,AppClassLoader有个子加载器commonLoader,commonLoader有一个与其相同的子加载器catalinaLoader,catalinaLoader有一个与其相同的子加载器sharedLoader。


  
  1. common.loader= "${catalina.base}/lib", "${catalina.base}/lib/*.jar", "${catalina.home}/lib", "${catalina.home}/lib/*.jar"
  2. server.loader=
  3. shared.loader=

2)初始化Catalina类。

Catalina作为Tomcat副总级别的人物,对于Tomcat有着极其重要的作用。Bootstrap其实没有做多少工作,主要的初始化工作包括server.xml文件的解析、server组件的初始化都是在此类中进行的。为了便于扩展,此处采用了反射的方式进行初始化此类。最终,将Catalina对象赋值给catalinaDaemon 。


  
  1. public void init() throws Exception {
  2. ...
  3. //通过反射的方式初始化Catalina类,并将最底层的类加载器sharedLoader赋值给对象的父加载器
  4. Class<?> startupClass = catalinaLoader.loadClass( "org.apache.catalina.startup.Catalina");
  5. Object startupInstance = startupClass.getConstructor().newInstance();
  6. // Set the shared extensions class loader
  7. if (log.isDebugEnabled())
  8. log.debug( "Setting startup class properties");
  9. String methodName = "setParentClassLoader";
  10. Class<?> paramTypes[] = new Class[ 1];
  11. paramTypes[ 0] = Class.forName( "java.lang.ClassLoader");
  12. Object paramValues[] = new Object[ 1];
  13. paramValues[ 0] = sharedLoader;
  14. Method method =
  15. startupInstance.getClass().getMethod(methodName, paramTypes);
  16. method.invoke(startupInstance, paramValues);
  17. //Catalina作为守护线程
  18. catalinaDaemon = startupInstance;
  19. }

这里针对类加载器需要注意的事

1)Catalina并没有使用末级加载器sharedLoader加载此类,而是使用其父加载器catalina加载。

2)Catalina有一个属性parentClassLoader,并将shareLoader赋值给他。而这也将作为应用部署的父类加载器。

3.3 组件的初始化(1.2:load)

在Bootstrap#main中,通过调用Bootstrap#load方法,此方法仍然通过反射的方式调用Catalina的load方法。


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

这个类没什么可说了,很容易理解。那么接下来进入我们的重头戏,从Catalina的load方法开始,寻求Tomcat是怎么初始化他的各个组件的。

3.3.1 Catalina的加载(1.2.1:load)

Catalina加载在方法org.apache.catalina.startup.Catalina#load()中执行,在此方法中,主要完成了以下三件事:

1)解析server.xml文件,并将server对象赋值给org.apache.catalina.startup.Catalina#server。

2)重新定向System.out流。

3)server组件的初始化

3.3.1.1 解析server.xml

server.xml解析采用SAXParser解析器加创建解析规则的方式进行解析。代码开始通过createStartDigester()方法创建Digester。什么是Digester呢?

A Digester processes an XML input stream by matching a series of element nesting patterns to execute Rules that have been added prior to the start of parsing.  This package was inspired by the XmlMapper class that was part of Tomcat 3.0 and 3.1, but is organized somewhat differently.

Digester实现了SAXParser默认的处理器DefaultHandler2,通过添加一系列规则,实现server.xml文件的解析。而这些规则要继承org.apache.tomcat.util.digester.Rule类,主要实现了begin(namespace,name,attributes)和end(namespace, name)方法,主要完成SAXParser在解析到当前元素开始元素标记的业务逻辑(eg:<sever>)和解析结束元素标记(eg:</sever>)。在Tomcat源码中,主要的规则包括org.apache.tomcat.util.digester.ObjectCreateRule、org.apache.catalina.startup.ConnectorCreateRule和org.apache.catalina.startup.SetAllPropertiesRule。其中ObjectCreateRule处理除了Connector之外几乎所有的元素,ConnectorCreateRule主要针对Connector标签进行解析,SetAllPropertiesRule值用于将其他节点的属性复制到当前节点相同的属性名称上。


  
  1. digester.addRule( "Server/Service/Connector",
  2. new ConnectorCreateRule());
  3. digester.addRule( "Server/Service/Connector",
  4. new SetAllPropertiesRule( new String[]{ "executor", "sslImplementationName"}));
  5. digester.addSetNext( "Server/Service/Connector",
  6. "addConnector",
  7. "org.apache.catalina.connector.Connector");

比如,看上面代码,针对相同的标签Server/Service/Connector,添加了两个规则。

上面还说到server对象赋值给org.apache.catalina.startup.Catalina#server,那么这一步是怎么设置的呢?如果我们通过查看org.apache.catalina.startup.Catalina#setServer()被哪些对象应用,根本是查不到的。好吧,他也是通过规则设置的。

digester.addObjectCreate("Server","org.apache.catalina.core.StandardServer","className");
digester.addSetProperties("Server");
digester.addSetNext("Server","setServer","org.apache.catalina.Server");

3.3.1.2 重新定向System.out流

这一步不难理解,将System.out和System.err重定向到org.apache.tomcat.util.log.SystemLogHandler上。SystemLogHandler又是一个什么鬼?


   
  1. /**
  2. * This helper class may be used to do sophisticated redirection of
  3. * System.out and System.err on a per Thread basis.
  4.  * 可以使用该帮助程序类在每个线程的基础上对System.out和System.err进行复杂的重定向。
  5. *
  6. * A stack is implemented per Thread so that nested startCapture
  7. * and stopCapture can be used.
  8. *
  9. * @author Remy Maucherat
  10. * @author Glenn L. Nielsen
  11. */

我们知道,System.out他是一个静态的输出流,是一个被所有线程共享的对象。而tomcat启动了一大堆线程,为了保证县城的安全,需要通过SystemLogHandler包装一下。在这个类中,通过ThreadLocal(线程局部变量)保证了各个线程System.out的独立性。

3.3.1.3 server组件的初始化


  
  1. try {
  2. getServer().init();
  3. } catch (LifecycleException e) {
  4. if (Boolean.getBoolean( "org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
  5. throw new java.lang.Error(e);
  6. } else {
  7. log.error( "Catalina.start", e);
  8. }
  9. }

server初始化放在下一章节讲解。这里只需明白调用的server的init()方法即可。

3.3.2 server初始化(1.2.2:init)

在这个方法中,并没有做太多实质性的工作,主要是通过循环遍历services实现service的初始化。


  
  1. // Initialize our defined Services
  2. for (Service service : services) {
  3. service.init();
  4. }

3.3.3 service初始化(1.2.3:init)

由于Tomcat的各个组件都有生命周期的概念,其无论是容器类组件还是非容器类组件都继承了org.apache.catalina.util. LifecycleBase,而LifecycleBase又实现了org.apache.catalina.Lifecycle。对于所有继承LifecycleBase的组件,通过调用org.apache.catalina.util.LifecycleBase#init方法,改变当前组件的状态。而真实的实现方法为org.apache.catalina.util. LifecycleBase#initInternal(内部的初始化方法)。通过下面代码可以知道,首先这个方法是线程安全的,其次这个方法使用final修饰,不能重写,这也就是避免组件的组件的状态被随意篡改。


  
  1. public final synchronized void init() throws LifecycleException {
  2. if (!state.equals(LifecycleState.NEW)) {
  3. invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
  4. }
  5. try {
  6. setStateInternal(LifecycleState.INITIALIZING, null, false);
  7. initInternal();
  8. setStateInternal(LifecycleState.INITIALIZED, null, false);
  9. } catch (Throwable t) {
  10. handleSubClassException(t, "lifecycleBase.initFail", toString());
  11. }
  12. }

接下来,我们重点查看initInternal方法。当我们点进这个方法,发现是一个抽象方法,真正的实现交给子类。这里使用了模板方法的设计模式。当我们按ctrl+alt+B,发现实现他的有一大堆,此时不要惊慌,现在我们分析的事service方法,所以我们找和service相关的实现类,终于我们找到StandardService。

以后找Tomat组件实现类的时候,基本以Standard开头,除了Connector。


  
  1. @Override
  2. protected void initInternal() throws LifecycleException {
  3. super.initInternal();
  4. //初始化执行引擎
  5. if (engine != null) {
  6. engine.init();
  7. }
  8. // Initialize any Executors
  9. //初始化配置的执行器
  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. mapperListener.init();
  18. // Initialize our defined Connectors
  19. synchronized (connectorsLock) {
  20. for (Connector connector : connectors) {
  21. try {
  22. connector.init();
  23. } catch (Exception e) {
  24. }
  25. }
  26. }
  27. }

通过上面的代码,我们知道此方法主要完成了三件事情:

1)初始化执行引擎。

2)初始化执行器(线程池),如果配置了。

3)初始化连接器。

详细的执行过程后面会有讲解,这里我们只是了解一个大体的结构。想一想,这里的初始化过程是否可以颠倒?

3.3.4 Engine初始化(1.2.4:init)

代码的开始还是一如既往的初始化当前组件的状态,然后通过调用initInternal方法实现组件的初始化。所以我们重点看一下org.apache.catalina.core.StandardEngine#initInternal方法,当我们进入此方法,发现只有两行代码。


  
  1. @Override
  2. protected void initInternal() throws LifecycleException {
  3. // Ensure that a Realm is present before any attempt is made to start
  4. // one. This will create the default NullRealm if necessary.
  5. getRealm();
  6. super.initInternal();
  7. }

这里我们重点看super.initInternal()方法,org.apache.catalina.core.ContainerBase#initInternal。这是容器基本类的初始化方法。


  
  1. @Override
  2. protected void initInternal() throws LifecycleException {
  3. BlockingQueue<Runnable> startStopQueue = new LinkedBlockingQueue<>();
  4. startStopExecutor = new ThreadPoolExecutor(
  5. getStartStopThreadsInternal(),
  6. getStartStopThreadsInternal(), 10, TimeUnit.SECONDS,
  7. startStopQueue,
  8. new StartStopThreadFactory(getName() + "-startStop-"));
  9. startStopExecutor.allowCoreThreadTimeOut( true);
  10. super.initInternal();
  11. }

通过上面的代码,我们了解到执行引擎Engine主要初始化一个线程池startStopExecutor。不知道大家是否用过阿里的代码检查工具,当我们使用 Executors.newSingleThreadExecutor()创建线程池的时候就会报错,这里就是一个典范,大家有时间可以学习一下。

3.3.5 Executor初始化(1.2.5:init)

这个方法的初始化也十分简单,并没有做什么事。无非就是设置个状态什么的。

3.3.6 Connector初始化(1.2.6:init)

按照常理,我们依旧进入org.apache.catalina.connector.Connector#initInternal方法。这个Connector并没有按照之前那种以Standard开头,这就是一个实现类。在这个方法中,主要完成如下几件事:

1)创建CoyoteAdapter,这个类主要用于将http请求封装成request、response对象

2)将CoyoteAdapter赋值给协议处理器protocolHandler

3)protocolHandler初始化

在这里,想一想protocolHandler是什么时候初始化的?

3.3.7 ProtocolHandler初始化(1.2.7:init)

protocolHandler.init();

->org.apache.coyote.http11.AbstractHttp11Protocol#init

->org.apache.coyote.AbstractProtocol#init

通过如下的跟踪路径,我们找到了endPoint,这里重点看一下endPoint初始化org.apache.tomcat.util.net.AbstractEndpoint #init。在这个方法中,我们看到有个bind()的方法,我们点进入,发现有三个实现,AprEndpoint、Nio2EndPoint和NioEndpoint。

这里简单提一下Nio2Endpoint和NioEndpoint,NioEndpoint属于同步非阻塞类,而Nio2Endpoint属于异步非阻塞类。这也就决定了NioEndpoint的请求调用链为acceptor->poller->线程池;而Nio2Endpoint的请求调用路径为acceptor->线程池。这里可以想一下为什么Nio2Endpoint少了poller的调用。

当然,针对于当前版本的tomcat(8.5.59),默认的还是使用NioEndpoint,这个第一章有简单介绍,剩下的时间就是自己捋代码找到这个位置。在org.apache.tomcat.util.net.NioEndpoint#bind方法中,规规矩矩写了一遍NioSocket编程,不熟悉Socket编程的借此机会可以熟悉一下。

到此位置,Tomcat各组件的初始化工作算是告一段落。接下来,就是真正的Tomcat启动了。

3.4 组件的启动(1.3:start)

到目前为止,我们逐渐熟悉了Tomcat的套路了。这里仍然是通过反射的方式调用Catalina的start方法。


  
  1. public void start() throws Exception {
  2. if (catalinaDaemon == null) {
  3. init();
  4. }
  5. Method method = catalinaDaemon.getClass().getMethod( "start", (Class []) null);
  6. method.invoke(catalinaDaemon, (Object []) null);
  7. }

3.4.1 Catalina启动(1.3.1:start)

Catalina启动主要完成了三件事:

1)server组件的启动;

2)设置关闭程序的钩子方法;

3)开启一个线程,用于监听8005端口

第一件事我们稍后再说,现在我们主要说一下第二、第三件事。

3.4.1.1 设置关闭程序的钩子方法

在这里,使用了jdk的一个方法Runtime.getRuntime().addShutdownHook(),实现了Tomcat的安全关闭。

在这里,我们可以了解一下钩子方法的触发时机:
1.程序正常退出
2.使用System.exit()
3.终端使用Ctrl+C触发的中断
4.系统关闭
5.OutOfMemory宕机
6.使用Kill pid命令干掉进程(但是在使用kill -9 pid时,是不会被调用的)

3.4.1.2 开启8005端口

我们一路追踪,org.apache.catalina.startup.Catalina#await->org.apache.catalina.core.StandardServer#await,发现这里开启一个8005端口的socket服务。这是做什么用的?

不知道大家是否有发现,针对于脚本启动tomcat和服务类启动tomcat,他们是怎么关闭tomcat的?

1)针对脚本启动的tomcat,有一个shutdown.bat或者是shutdown.sh文件,这个文件可以帮组我们关闭tomcat服务。

2)针对Windows下的服务类Tomcat,服务界面有一个stop的按钮,可以关闭tomcat。

而这两种关闭的方式,其实就是发送一个8005的socket请求,服务端接受到这个请求后,安全的将Tomcat关闭。

3.4.2 Server组件启动(1.3.2:start)

这里仍然遵循上面的套路,通过调用父类的start()方法,经过一系列的状态设置之后,调用真正的实现方法startInternal。


  
  1. protected void startInternal() throws LifecycleException {
  2. fireLifecycleEvent(CONFIGURE_START_EVENT, null);
  3. setState(LifecycleState.STARTING);
  4. globalNamingResources.start();
  5. // Start our defined Services
  6. synchronized (servicesLock) {
  7. for (Service service : services) {
  8. service.start();
  9. }
  10. }
  11. }

这里很简单,主要遍历启动service。

3.4.3 Service组件启动(1.3.3:start)

在这里,和service启动一样,主要做了三件事:


  
  1. protected void startInternal() throws LifecycleException {
  2. engine.start();
  3. executor.start();
  4. connector.start();
  5. }

3.4.4 Engine组件启动(1.3.4:start)

org.apache.catalina.core.StandardEngine#startInternal->org.apache.catalina.core.ContainerBase#startInternal

在这里,主要调用threadStart()方法。


  
  1. protected void threadStart() {
  2. if (thread != null)
  3. return;
  4. if (backgroundProcessorDelay <= 0)
  5. return;
  6. threadDone = false;
  7. String threadName = "ContainerBackgroundProcessor[" + toString() + "]";
  8. thread = new Thread( new ContainerBackgroundProcessor(), threadName);
  9. thread.setDaemon( true);
  10. thread.start();
  11. }

在这里,我们主要关注ContainerBackgroundProcessor这个方法,tomat应用的动态部署就是通过这个类实现了。而这个类的官方说明如下:

/**
 * Private runnable class to invoke the backgroundProcess method
 * of this container and its children after a fixed delay.
 * 固定的可运行类,用于在固定延迟后调用此容器及其子级的backgroundProcess方法。
 */

以后也许会针对tomat的动态部署做一个专题,这里限于篇幅就不说了。

3.4.5 Executor组件启动(1.3.5:start)

我们快速来到org.apache.catalina.core.StandardThreadExecutor#startInternal。


  
  1. @Override
  2. protected void startInternal() throws LifecycleException {
  3. taskqueue = new TaskQueue(maxQueueSize);
  4. TaskThreadFactory tf = new TaskThreadFactory(namePrefix,daemon,getThreadPriority());
  5. executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), maxIdleTime, TimeUnit.MILLISECONDS,taskqueue, tf);
  6. executor.setThreadRenewalDelay(threadRenewalDelay);
  7. //预启动最小的核心空闲线程
  8. if (prestartminSpareThreads) {
  9. executor.prestartAllCoreThreads();
  10. }
  11. taskqueue.setParent(executor);
  12. setState(LifecycleState.STARTING);
  13. }

在这里,我们看到,还是以之前那种方式创建了线程池。这里也没啥说的。

3.4.6 Connector组件启动(1.3.6:start)

我们快速来到org.apache.catalina.connector.Connector#startInternal。


  
  1. @Override
  2. protected void startInternal() throws LifecycleException {
  3. // Validate settings before starting
  4. if (getPort() < 0) {
  5. throw new LifecycleException(sm.getString(
  6. "coyoteConnector.invalidPort", Integer.valueOf(getPort())));
  7. }
  8. setState(LifecycleState.STARTING);
  9. try {
  10. protocolHandler.start();
  11. } catch (Exception e) {
  12. throw new LifecycleException(
  13. sm.getString( "coyoteConnector.protocolHandlerStartFailed"), e);
  14. }
  15. }

是不是感觉很神奇?原来大神都是这么写代码?看似一段轰轰烈烈的代码,其实有用的就是那么一两句。然而体现代码功底的确实那些看似“没用的代码”,这些“没用的代码”才让整个系统具有更强的健壮性,一言不合就抛异常。言归正传,这里其实就是实现了protocolHandler.start();启动协议处理器。

3.4.7 ProtocolHandler组件启动(1.3.7:start)

我们依旧快速定位到org.apache.coyote.AbstractProtocol#start方法。这里有一句重要的代码endpoint.start();,我们继续往下跟踪:

org.apache.tomcat.util.net.AbstractEndpoint#start->org.apache.tomcat.util.net.NioEndpoint#startInternal


  
  1. public void startInternal() throws Exception {
  2. if (!running) {
  3. running = true;
  4. paused = false;
  5. processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
  6. socketProperties.getProcessorCache());
  7. eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
  8. socketProperties.getEventCache());
  9. nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
  10. socketProperties.getBufferPool());
  11. // Create worker collection
  12. if ( getExecutor() == null ) {
  13. createExecutor();
  14. }
  15. //10000个
  16. initializeConnectionLatch();
  17. // Start poller threads
  18. //启动poller线程,根据
  19. pollers = new Poller[getPollerThreadCount()];
  20. for ( int i= 0; i<pollers.length; i++) {
  21. pollers[i] = new Poller();
  22. Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i);
  23. pollerThread.setPriority(threadPriority);
  24. pollerThread.setDaemon( true);
  25. pollerThread.start();
  26. }
  27. startAcceptorThreads();
  28. }
  29. }

还记得我在上面说过NIOEndPoint和NIO2Endpoint的区别吗?我们仔细看一下这一段代码,先后启动了两类型线程,pollerThread和AcceptorThread。系统默认Acceptor有一个线程,pollerThread有两个线程。

现在,我们启动tomat程序,并启动jconsole观察一下线程。

 

到此为止,Tomcat的启动流程算是告一段落了。然而,Tomcat的源码并没有结束。以后会以专题形式针对特殊处理逻辑做一分享。


转载:https://blog.csdn.net/oYinHeZhiGuang/article/details/109150947
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场