TOMCAT 源碼分析 -- 啓動

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中每個容器的生命週期都實現了一個接口Lifecycle,其重要的方法有initstartstopdestroygetState等。從它的繼承樹(僅展示部分)可以觀察到,容器都實現了它:

在這裏插入圖片描述

啓動類

​ 其啓動類爲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的定義,但不必去初始化,用到的時候纔去啓動(描述的不貼切)。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章