欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

TOMCAT 源码分析 -- 启动

程序员文章站 2022-07-14 10:46:24
...

TOMCAT 源码分析 – 启动

前语

​ Tomcat源码版本为官网下载的9.0.35版本。

构建环境参考https://blog.csdn.net/a17816876003/article/details/106586805

配置文件

​ Tomcat启动的配置文件为server.xml,启动过程也全都围绕它进行,Tomcat的模块结构也可以在其中一览无余

<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

  
  <GlobalNamingResources>
    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
  </GlobalNamingResources>

  <Service name="Catalina">

    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
    <Engine name="Catalina" defaultHost="localhost">  
      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>
      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />
      </Host>
    </Engine>
  </Service>
</Server>

TOMCAT模块结构

​ Tomcat最重要的模块是Container容器,它层层包裹,像“套娃”一样一层套一层,便于管理内层的生命周期。其结构于配置文件server.xml中的XML标签可以看得出来。首先记下理解这张图的模块结构,对下面源码启动的顺序理解帮助十分大。

TOMCAT 源码分析 -- 启动

生命周期

​ Tomcat中每个容器的生命周期都实现了一个接口Lifecycle,其重要的方法有initstartstopdestroygetState等。从它的继承树(仅展示部分)可以观察到,容器都实现了它:

TOMCAT 源码分析 -- 启动

启动类

​ 其启动类为Bootstrap,启动方法为main方法。

    public static void main(String args[]) {
        synchronized (daemonLock) {
            if (daemon == null) {
                // Don't set daemon until init() has completed
                Bootstrap bootstrap = new Bootstrap();
                try {
                    bootstrap.init();
                } catch (Throwable t) {
                }
                daemon = bootstrap;
            } else {
                Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
            }
        }
        try {
            String command = "start";
			// ...
            } else if (command.equals("start")) {
                daemon.setAwait(true);
            	// 最重要的两步 -- daemon为Bootstrap类本身
            	// 第一步加载 -- 加载完会绑定监听socket端口(Tomcat 8开始使用NIO)
                daemon.load(args);
            	// 第二步启动 -- 启动完才会去accept()处理请求
                daemon.start();
                if (null == daemon.getServer()) {
                    System.exit(1);
                }
            } 
        } catch (Throwable t) {
            
        }
    }

加载-load

​ 加载过程,通过反射调用了Catalina类的load方法

	/**
     * Load daemon.
     */
    private void load(String[] arguments) throws Exception {

        // Call the load() method
        String methodName = "load";
        Object param[];
        Class<?> paramTypes[];
        if (arguments==null || arguments.length==0) {
            paramTypes = null;
            param = null;
        } else {
            paramTypes = new Class[1];
            paramTypes[0] = arguments.getClass();
            param = new Object[1];
            param[0] = arguments;
        }
        // 通过反射调用了`Catalina`类的`load`方法
        Method method =
            catalinaDaemon.getClass().getMethod(methodName, paramTypes);
        if (log.isDebugEnabled()) {
            log.debug("Calling startup class " + method);
        }
        method.invoke(catalinaDaemon, param);
    }

其加载步骤按顺序有以下几步:

1. 读取配置文件

​ 读取%home%/conf/server.xml配置文件,并用Digester进行解析,将配置文件的容器等配置按照层次递归的放入Server中,实际为StandardServer这个类中。

		// Set configuration source
        ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(Bootstrap.getCatalinaBaseFile(), getConfigFile()));
        File file = configFile();

        // Create and execute our Digester
        Digester digester = createStartDigester();
		// ...
        digester.push(this);
        digester.parse(inputSource);

2. 对server对象进行初始化

getServer().init();
  • 2.1 发现其初始化都是用超类LifecycleBase的final方法进行初始化的(EngineHostContextWrapper等都间接继承于它)。
public abstract class LifecycleBase implements Lifecycle {
    @Override
    public final synchronized void init() throws LifecycleException {
        // ...
        try {
            // 设置状态
            setStateInternal(LifecycleState.INITIALIZING, null, false);
            // 真正执行初始化的方法
            initInternal();
            setStateInternal(LifecycleState.INITIALIZED, null, false);
        } catch (Throwable t) {
            handleSubClassException(t, "lifecycleBase.initFail", toString());
        }
    }

}

查看initInternal方法的定义,发现他将这个方法交由子类进行具体实现,这里就提现了Java多态的模板方法的好处,继续看下去会发现,所有Containner组件的初始化都经过init()方法,最终由自己实现initInternal方法,管理自己内部容器的初始化操作。

// LifecycleBase.java
protected abstract void initInternal() throws LifecycleException;

继续进入initInternal方法执行,进入了Server的实现类StandardServer

    // StandardServer.java
	@Override
    protected void initInternal() throws LifecycleException {

        super.initInternal();

        // Initialize utility executor
        reconfigureUtilityExecutor(getUtilityThreadsInternal(utilityThreads));
        
        // Initialize our defined Services
        // Server管理自己的套娃Service,最终也借用超类的init()方法对Service进行初始化
        for (Service service : services) {
            // 对service进行init
            service.init();
        }
    }

3. 对service进行初始化

在第二步的末尾的循环中又进入了2.1中的init方法,再调用service实现类StandardServiceinitInternal方法

// StandardService.java
    @Override
    protected void initInternal() throws LifecycleException {
		// 初始化Engine
        if (engine != null) {
            engine.init();
        }

        // 初始化执行器
        for (Executor executor : findExecutors()) {
            if (executor instanceof JmxEnabled) {
                ((JmxEnabled) executor).setDomain(getDomain());
            }
            executor.init();
        }

        // 初始化监听器
        mapperListener.init();

        // 初始化 Connectors (可以有多个连接器)
        synchronized (connectorsLock) {
            for (Connector connector : connectors) {
                connector.init();
            }
        }
    }

4. 初始化引擎、执行器、监听器、连接器

  1. 初始化引擎

    重要的仍是其实现父类的initInternal方法,对Realm(领域)的配置,实际获取了LockOutRealm

     // StandardEngine.java
    	@Override
        protected void initInternal() throws LifecycleException {
            getRealm();
            // 这一步中还会有`ContainerBase`中创建`startStopExecuter`线程池供`start`启动阶段使用。
            super.initInternal();
        }
     
       // ContainerBase.java
              private void reconfigureStartStopExecutor(int threads) {
                   if (threads == 1) {
                          // Use a fake executor -- 虚假的线程池
                       if (!(startStopExecutor instanceof InlineExecutorService)) {
                              startStopExecutor = new InlineExecutorService();
                       }
                      } else {
                       // Delegate utility execution to the Service
                          Server server = Container.getService(this).getServer();
                          server.setUtilityThreads(threads);
                          startStopExecutor = server.getUtilityExecutor();
                      }
                  }
      
    
    
    
  2. 初始化执行器

    源码中带的server.xml中没有定义他,debug直接跳过了。

  3. 初始化监听器

    // LifecycleMBeanBase.java
       // MapperListener.java 中并无实现initInternal方法,则一直调用到超类的额该方法
          @Override
           protected void initInternal() throws LifecycleException {
                  if (oname == null) {
                  mserver = Registry.getRegistry(null, null).getMBeanServer();
      			// 将oname 赋值为Catalina:type=Mapper
                  oname = register(this, getObjectNameKeyProperties());
              }
          }
    
    
  4. 初始化连接器

        @Override
        protected void initInternal() throws LifecycleException {
    
            // Initialize adapter
            adapter = new CoyoteAdapter(this);
            protocolHandler.setAdapter(adapter);
         if (service != null) {
                protocolHandler.setUtilityExecutor(service.getServer().getUtilityExecutor());
         }
            try {
                // 对协议处理器进行初始化
                protocolHandler.init();
            } catch (Exception e) {
                throw new LifecycleException(
                        sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);
            }
        }
    
    
    1. 初始化协议处理器
    // AbstractProtocol.java (Http11NioProtocol.java)
	@Override
       public void init() throws Exception {
        // 初始化终点(端点)
           String endpointName = getName();
           endpoint.setName(endpointName.substring(1, endpointName.length()-1));
           endpoint.setDomain(domain);
           endpoint.init();
       }   

	// 终(端)点初始化 --这一步,连接器就要让终点去绑定端口了
	// AbstractEndpoint.java  
	public final void init() throws Exception {
        if (bindOnInit) {
            bindWithCleanup();
            bindState = BindState.BOUND_ON_INIT;
        }
    }

	// NioEndpoint.java
 	protected void initServerSocket() throws Exception {
        if (!getUseInheritedChannel()) {
         serverSock = ServerSocketChannel.open();
            socketProperties.setProperties(serverSock.socket());
            InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
            // 最终这一步去绑定了8080端口
            serverSock.socket().bind(addr,getAcceptCount());
        } else {
            
        }
        serverSock.configureBlocking(true); //mimic APR behavior
    }

5. 回归Bootstrap

接着单步执行会发现它一层一层往外走,最终回到了Bootstrap中,进入下一步启动-start()

启动-start

​ 启动过程通过反射调用了Catalinastart方法。

    // Bootstrap.java
    public void start() throws Exception {
        Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
        method.invoke(catalinaDaemon, (Object [])null);
    }
	
	// Catalina.java
    public void start() {
		// 获取不到Server则调用之前的加载,到这里他已经加载过了,不会进这个分支
        if (getServer() == null) {
            load();
        }

        // 正式调用Server的启动生命周期
        try {
            getServer().start();
        } catch (LifecycleException e) {
            
        }
    }

​ 启动步骤按顺序分以下几个:

1. Server的启动

可以发现它也是交由超类的final start进行实现,可想而知,所有容器的启动,也都会进这个start方法。

并且与load()方法相似startInternal都交由子类具体实现其内部逻辑

   // LifecycleBase.java
	@Override
    public final synchronized void start() throws LifecycleException {
		// ...
        try {
            setStateInternal(LifecycleState.STARTING_PREP, null, false);
            // startInternal交由子类具体实现内部逻辑
            startInternal();
        } catch (Throwable t) {

        }
    }

具体进入startInternal看实现,可以看到实现在StandardServer

// org.apache.catalina.core.StandardServer#startInternal

    @Override
    protected void startInternal() throws LifecycleException {

        // 触发生命周期的事件 -- START
        fireLifecycleEvent(CONFIGURE_START_EVENT, null);
        setState(LifecycleState.STARTING);

        globalNamingResources.start();

        // 对定义的Service进行start
        synchronized (servicesLock) {
            for (Service service : services) {
                service.start();
            }
        }

    }

2. Service启动

同理,service.start();进入了超类的start()方法,且最终调用自身StandardServicestartInternal方法进行实现(下同之处自动省略)。

// org.apache.catalina.core.StandardService#startInternal

    @Override
    protected void startInternal() throws LifecycleException {

        // 第一步启动引擎
        if (engine != null) {
            synchronized (engine) {
                engine.start();
            }
        }
		// 第二步启动执行器
        synchronized (executors) {
            for (Executor executor: executors) {
                executor.start();
            }
        }
		// 第三步启动监听器
        mapperListener.start();

        // 第四步启动已经成功加载的连接器
        // 那么为什么会有失败的呢,最直接的就是端口被占用,无法绑定bind()
        synchronized (connectorsLock) {
            for (Connector connector: connectors) {
                // If it has already failed, don't try and start it
                if (connector.getState() != LifecycleState.FAILED) {
                    connector.start();
                }
            }
        }
    }

3. 启动引擎、执行器、监听器、连接器

  1. 启动引擎

    	// org.apache.catalina.core.StandardEngine#startInternal
        @Override
        protected synchronized void startInternal() throws LifecycleException {
            // Standard container startup
            // 调用了超类[ContainerBase]中的startInternal
            super.startInternal();
        }
    
    	// org.apache.catalina.core.ContainerBase#startInternal
        @Override
        protected synchronized void startInternal() throws LifecycleException {
    		// debug 发现进来为null
            Cluster cluster = getClusterInternal();
            if (cluster instanceof Lifecycle) {
                logger.info(String.format("cluster[%s] 开始执行生命周期之start()", cluster.getClusterName()));
                ((Lifecycle) cluster).start();
            }
            // 会获取到Load方法加载进来的`LockOutRealm`
            Realm realm = getRealmInternal();
            if (realm instanceof Lifecycle) {
                logger.info(String.format("realm-属于container[%s] 开始执行生命周期之start()", realm.getContainer().getName()));
                // 最终执行的为org.apache.catalina.realm.CombinedRealm#startInternal
                ((Lifecycle) realm).start();
            }
    
            // Start our child containers, if any
            // 最终找到了Engine下定义的Host [StandardHost]
            Container children[] = findChildren();
            List<Future<Void>> results = new ArrayList<>();
            for (Container child : children) {
                // 使用了load()时初始化的线程池
                logger.info("开始使用线程池提交多线程去调用子Container[%s]的call方法初始化");
                results.add(startStopExecutor.submit(new StartChild(child)));
            }
    
            MultiThrowable multiThrowable = null;
    		// 使用Future#get进行阻塞,获取Host的初始化结果
            for (Future<Void> result : results) {
                try {
                    result.get();
                } catch (Throwable e) {
                    log.error(sm.getString("containerBase.threadedStartFailed"), e);
                    if (multiThrowable == null) {
                        multiThrowable = new MultiThrowable();
                    }
                    multiThrowable.add(e);
                }
    
            }
            if (multiThrowable != null) {
                throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"),
                        multiThrowable.getThrowable());
            }
    
            // Start the Valves in our pipeline (including the basic), if any
            if (pipeline instanceof Lifecycle) {
                logger.debug(String.format("开始管道pipeline[%s]的生命周期之start()", pipeline.getContainer().getName()));
                ((Lifecycle) pipeline).start();
            }
            // 声明周期改成启动中
            setState(LifecycleState.STARTING);
    
            // Start our thread
            if (backgroundProcessorDelay > 0) {
                monitorFuture = Container.getService(ContainerBase.this).getServer()
                        .getUtilityExecutor().scheduleWithFixedDelay(
                                new ContainerBackgroundProcessorMonitor(), 0, 60, TimeUnit.SECONDS);
            }
        }
    
    1. 启动Cluster

      获得对象为null,无需启动

    2. 启动Realm

      有对象,跳过

    3. 启动子容器Host

      根据上述代码可知,它会被提交到线程池,进行多线程启动,将会调用到线程的call方法

      // org.apache.catalina.core.ContainerBase.StartChild
      	// 超类中的静态内部类
          private static class StartChild implements Callable<Void> {
              @Override
              public Void call() throws LifecycleException {
                  // 调用子容器的start方法,在这步中,子容器为`StandardHost`
                  child.start();
                  //DEBUG: child: "StandardEngine[Catalina].StandardHost[localhost]"
                  return null;
              }
          }
      

      接着通过StandardHoststart生命周期又进入startInternal方法

      	// org.apache.catalina.core.StandardHost#startInternal
        	@Override
          protected synchronized void startInternal() throws LifecycleException {
      
              // 检查管道中有没有报错
              // 真正的启动交由超类实现
              super.startInternal();
          }
      	
      	// 接着又交由到ContainerBase这个熟悉的超类进行实现,在启动-start章节的3.1中已经贴出过,这里不再详细展示,主要就是多线程去启动器子容器 -- 套娃模式的好处
      	// org.apache.catalina.core.ContainerBase#startInternal
          @Override
          protected synchronized void startInternal() throws LifecycleException {
              // Start our child containers, if any
              Container children[] = findChildren();
              // 由于在server.xml中没有在<Host>节点下再定义<Context>容器,所以这次children数组是空数组
              List<Future<Void>> results = new ArrayList<>();
              for (Container child : children) {
                  logger.info("开始使用线程池提交多线程去调用子Container[%s]的call方法初始化");
                  results.add(startStopExecutor.submit(new StartChild(child)));
              }
      
          }
      
    4. 启动管道pipeline

      跳过

  2. 启动执行器

    ​ 在执行完多线程对Host的启动后,一路点击Step Out跳回到StandardService中的startInternal方法。

    若已经分不清层次可以在StandardService类中搜索下面这段代码并打上断点。

    		//  org.apache.catalina.core.StandardService#startInternal
            synchronized (executors) {
                for (Executor executor: executors) {
                    executor.start();
                }
            }
    

    ​ 不过executors数组长度为0,在这儿就跳过了。

  3. 启动监听器

    	// org.apache.catalina.mapper.MapperListener#startInternal
       @Override
        public void startInternal() throws LifecycleException {
    		// 获取引擎
            Engine engine = service.getContainer();
    		// 添加监听器进引擎
            addListeners(engine);
    
            Container[] conHosts = engine.findChildren();
            for (Container conHost : conHosts) {
                Host host = (Host) conHost;
                if (!LifecycleState.NEW.equals(host.getState())) {
                    // Registering the host will register the context and wrappers
                    registerHost(host);
                }
            }
        }
    
    	// org.apache.catalina.mapper.MapperListener#addListeners
        private void addListeners(Container container) {
            // 把监听器[回调接口]注册进容器、生命周期、并递归对子容器进行注册
            container.addContainerListener(this);
            container.addLifecycleListener(this);
            for (Container child : container.findChildren()) {
                // 通过一共5层递归,观察到层次如下
                // StandardEngine[Catalina].StandardHost[localhost].StandardContext[].StandardWrapper[default]
                addListeners(child);
            }
        }
    

    ​ 可以观察到套娃一样对子容器进行递归添加监听器层次为:StandardEngine[Catalina].StandardHost[localhost].StandardContext[].StandardWrapper[default]

  4. 启动连接器

    ​ 可以在org.apache.catalina.core.StandardService#startInternal中搜索for (Connector connector: connectors)打上断点,快速从上一步的递归中出来,再进入connector.start();

    ​ ps: 这时候在windows平台上遇到了端口被占用,如何杀死占用了8080端口的进程呢?

    <!-- 先找出占用8080端口的进程 -->
    netstat -ano| findstr "8080"
    <!-- 杀死占用8080端口的进程[此处刚好为7400] -->
    taskkill /f /pid 7400
    
    // org.apache.catalina.connector.Connector#startInternal
        @Override
        protected void startInternal() throws LifecycleException {
            try {
                // 启动协议处理器
                protocolHandler.start();
            } catch (Exception e) {
    
            }
        }
    
    // org.apache.coyote.AbstractProtocol#start
        @Override
        public void start() throws Exception {
    		// 启动端点
            endpoint.start();
     
        }
    
    // org.apache.tomcat.util.net.AbstractEndpoint#start
        public final void start() throws Exception {
            startInternal();
        }
    
    // org.apache.tomcat.util.net.NioEndpoint#startInternal
       @Override
        public void startInternal() throws Exception {
            if (!running) {
                // 读取配置
                // Create worker collection
                if (getExecutor() == null) {
                    // 初始化线程池,任务队列
                    createExecutor();
                }
    		   // 创建限制锁
                initializeConnectionLatch();
    
                // 创建NIO的poller线程
                poller = new Poller();
                Thread pollerThread = new Thread(poller, getName() + "-ClientPoller");
                pollerThread.setPriority(threadPriority);
                pollerThread.setDaemon(true);
                pollerThread.start();
    		   // 真正开始接收NIO端口的请求
                startAcceptorThread();
            }
        }
    
    // org.apache.tomcat.util.net.AbstractEndpoint#startAcceptorThread
        protected void startAcceptorThread() {
            acceptor = new Acceptor<>(this);
            String threadName = getName() + "-Acceptor"; // http-nio-8080-Acceptor
            acceptor.setThreadName(threadName);
            Thread t = new Thread(acceptor, threadName);
            t.setPriority(getAcceptorThreadPriority());
            t.setDaemon(getDaemon());
            t.start(); // Thread[http-nio-8080-Acceptor,5,main]
        }
    
  5. 回归BootStrap

    一直Step Out回到BootStrap中,完成启动。

总结

  • 加载(load)过程主要完成 配置读取-实例化容器等组件、创建线程池、占用监控端口。
  • 启动(start)过程主要完成 顺序/多线程启动各层容器、开始接收端口的请求数据。

收获

  • 组件模块化,在超类中定义基本操作,在子类中定义具体的一部分的实现。架构脉络清晰,层次分明,节省大量代码量。
  • 加载与启动整体解耦分离,就不必在启动过程中在意所需组件是否加载。有点像Spring的懒加载,读取了Bean的定义,但不必去初始化,用到的时候才去启动(描述的不贴切)。
相关标签: TOMCAT