Tomcat源碼分析 - 經典啓動架構設計、Servlet容器設計和管道機制

背景apache

Tomcat 是一款開源輕量級 Web 應用服務器,是一款優秀的 Servlet 容器實現。無論在以前War包打天下,仍是如今的分佈式、集羣、虛擬容器化等至關成熟的階段,主流的JAVA服務容器領域,它的地位都不可輕易被替代。對Tomcat停留在使用層面,不免在服務優化和配置的選型等方面存在誤區和遇到一些坑,那麼對其源碼和設計思路的瞭解就至關有必要了。編程


Tomcat的源碼有不少,今天咱們就來重點分析一下啓動流程設計、生命週期組件、容器及管道機制。選用的源碼版本爲目前的主流穩定版8.5;bootstrap

啓動架構設計設計模式

咱們在啓動Tomcat服務的時候,一般去運行的是安裝目錄的bin路徑下startup(.sh/bat) 腳本,其實打開它的源碼就會看到裏面會調用catalina(.sh/.bat)這個啓動腳本,而這個腳本最終運行到Tomcat啓動類Bootstrap.class。在Bootstrap中首先會進行類加載器定義和類加載(下次詳細介紹),而後進行server.xml解析、進而初始化各個部件,最後纔是順序運行各個部件、後臺任務處理線程和事件監聽等。服務器

image.png


Bootstrap啓動類入口架構

public static void main(String args[]) {

   if (daemon == null) {
       // Don't set daemon until init() has completed
       
Bootstrap bootstrap = new Bootstrap();
       try
{
           /**
            * 定義初始化各個類加載器、進行類加載
            */
           
bootstrap.init();
       
} catch (Throwable t) {
           handleThrowable(t);
           
t.printStackTrace();
           return;
       
}
       daemon = bootstrap;
   
} else {
       /**
        * Tomcat線程上下文,默認的類加載器
        */
       
Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
   
}
   try {
       String command = "start";
       if
(args.length > 0) {
           command = args[args.length - 1];
       
}

       if (command.equals("startd")) {
           args[args.length - 1] = "start";
           
daemon.load(args);
           
daemon.start();
       
} else if (command.equals("stopd")) {
           args[args.length - 1] = "stop";
           
daemon.stop();
       
} else if (command.equals("start")) {
           daemon.setAwait(true);
           
/**
            * server.xml配置解析和各個部件初始化的入口
            */
           
daemon.load(args);
           
/**
            * 各個部件運行、後臺任務處理及事件監聽的入口
            */
           
daemon.start();
           if
(null == daemon.getServer()) {
               System.exit(1);
           
}
       } else if (command.equals("stop")) {
           daemon.stopServer(args);
       
} else if (command.equals("configtest")) {
           daemon.load(args);
           if
(null == daemon.getServer()) {
               System.exit(1);
           
}
           System.exit(0);
       
} else {
           log.warn("Bootstrap: command \"" + command + "\" does not exist.");
       
}
   } catch (Throwable t) {
       // Unwrap the Exception for clearer error reporting
       
if (t instanceof InvocationTargetException &&
               t.getCause() != null) {
           t = t.getCause();
       
}
       handleThrowable(t);
       
t.printStackTrace();
       
System.exit(1);
   
}
}

1、啓動設計之初始化部分
併發

跟進源碼進入到Tomcat的主服務Catalina類,load()方法app

image.png

經過生命週期initInternal()方法進入Server部件實現類StandardServer的initInternal()方法內;異步

image.png

經過生命週期initInternal()方法進入Service部件實現類StandardService的initInternal()方法內;分佈式

@Override
protected void initInternal() throws LifecycleException {
   super.initInternal();
   
/**
    * 這裏初始化服務的引擎部件
    */
   
if (engine != null) {
       engine.init();
   
}
   // Initialize any Executors
   
for (Executor executor : findExecutors()) {
       if (executor instanceof JmxEnabled) {
           ((JmxEnabled) executor).setDomain(getDomain());
       
}
       executor.init();
   
}

   /**
    * 這裏初始化各個事件監聽器
    */
   
mapperListener.init();
   
/**
    * 這裏初始化各個配置的鏈接器
    */
   
synchronized (connectorsLock) {
       for (Connector connector : connectors) {
           try {
               connector.init();
           
} catch (Exception e) {
               String message = sm.getString(
                       "standardService.connector.initFailed", connector);
               
log.error(message, e);

               if
(Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"))
                   throw new LifecycleException(message);
           
}
       }
   }
}

經過生命週期initInternal()方法進入Engine部件實現類StandardEngine的initInternal()方法內;

image.png

2、啓動設計之啓動部分

跟進Bootstrap啓動類main()方法中的deamon.start()方法;

image.png

跟進上面的反射start()方法,進入Catalina主服務類的start()方法;

image.png

經過生命週期startInternal()方法進入Server部件實現類StandardServer的startInternal()方法內;

@Override
protected void startInternal() throws LifecycleException {
   /**
    * 流轉和啓動事件監聽
    */
   
fireLifecycleEvent(CONFIGURE_START_EVENT, null);
   
setState(LifecycleState.STARTING);
   
globalNamingResources.start();
   
/**
    * 遍歷啓動server.xml中註冊的各個服務
    */
   
synchronized (servicesLock) {
       for (int i = 0; i < services.length; i++) {
           services[i].start();
       
}
   }
}

經過生命週期startInternal()方法進入Service部件實現類StandardService的startInternal()方法內;

image.png

經過生命週期startInternal()方法進入Engine部件實現類StandardEngine的startInternal()方法內;

image.png


生命週期管理設計

Tomcat經過生命週期組件,對其中運行的全部部件(包括容器自己)和機制的管理。經過底層的Lifecycle約定,抽象出生命週期業務處理骨架LifecycleBase,而後經過它對全部部件在生命週期內各個階段進行整體業務流程控制設計。LifecycleBase自己就設計模式而言採用了模板方法設計模式,統一抽象出業務處理的骨架,而後把部分變化的步驟延遲到子類中實現的這種方式。

image.png

部件與生命週期的關係

全部的業務部件在各個階段的動做都須要經過Lifecycle進行整體設計,而部件實現自己只處理和本身密切相關的定製業務。這種解耦化的設計讓Tomcat核心運做流程更加靈活、插拔度更好、更易於維護和擴展出各個版本的新功能。

image.png



容器設計

Servlet容器做爲Tomcat業務的核心的部分,它包括了Engine、Host(域相關配置)、Context和Servlet Wrapper包裝等主要部件。這些部件被Container統一設計、而後經過ContainerBase統一抽象出容器管理骨架。

image.png

管道機制

在一個比較複雜的大型系統中,若是一個對象或數據流須要進行繁雜的邏輯處理,咱們能夠選擇在一個大的組件中直接處理這些繁雜的業務邏輯, 這個方式雖然達到目的,但擴展性和可重用性較差, 由於可能牽一髮而動全身。更好的解決方案是採用管道機制,用一條管道把多個對象(閥門部件)鏈接起來,總體看起來就像若干個閥門嵌套在管道中同樣,而處理邏輯放在閥門上。

image.png

閥門(Valve)在管道中運行流程

image.png

Tomcat中的管道設計

管道的實現採用了經典的責任鏈設計模式,而且做爲Servlet容器設計的一部分。

image.png

總結

Tomcat源碼中精妙之處遠不止這種優秀的總體設計與機制(固然學習優秀的源碼,我認爲主要是學習它的總體設計思路)。但不少實現的環節也用到併發編程的相關技術,我認爲一樣也可圈可點,如線程池、異步任務、可重入鎖、讀寫鎖、阻塞隊列、寫時複製容器、原子類等,固然也綜合運用了其餘的設計模式如訪問者、適配器等。推薦你們有時間可多讀一下!

相關文章
相關標籤/搜索