淺讀tomcat架構設計和tomcat啟動過程(1)

飄渺紅塵✨發表於2021-07-06

  一圖甚千言,這張圖真的是耽擱我太多時間了:

    

 

 

 

 

     下面的tomcat架構設計程式碼分析,和這張圖息息相關.

    使用maven搭建本次的環境,貼出pom.xml完整內容:

      

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>groupId</groupId>
    <artifactId>JavaWebDemo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
        </dependency>

        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-catalina</artifactId>
            <version>8.0.14</version>
        </dependency>
    </dependencies>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    
</project>

    至此,環境已經準備就緒,就可以愉快看程式碼了.

    tomcat的Server是由org.apache.catalina.startup.Catalina來管理的,Catalina是tomcat的管理類可以通過反射載入檢視程式碼:

    Catalina類中有很多方法,他們具有不同的含義,其中

public void load() {...
public void start() {...
public void stop() {...

  這些方法用於管理tomcat的生命週期,其中load方法:

  load方法重要前半部分:

public void load() {
        long t1 = System.nanoTime();
        this.initDirs();
        this.initNaming();
        Digester digester = this.createStartDigester();
        InputSource inputSource = null;
        InputStream inputStream = null;
        File file = null;

        try {
            file = this.configFile();
            inputStream = new FileInputStream(file);
            inputSource = new InputSource(file.toURI().toURL().toString());

    load方法重要後半部分:

      

        this.getServer().setCatalina(this);
            this.getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
            this.getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
            this.initStreams();

            try {
                this.getServer().init();

 

        

 

       

      其中load方法是根據建立conf/server.xml檔案來建立Server,並呼叫Server的init方法進行初始化

    start和stop方法暫定,下面會講到.這三個方法都會按照容器結構逐層呼叫相應的方法.

    不過tomcat的入口main方法並不在Catalina類裡,而是在org.apache.catalina.startup.Bootstrap類中,這樣做的好處是tomcat管理類和入口類實現分離

    Bootstrap是tomcat的入口,正常情況下啟動tomcat就是呼叫Bootstrap類的main方法,程式碼如下:   

public static void main(String[] args) {
        if (daemon == null) {
       //新建一個bootstrap Bootstrap bootstrap
= new Bootstrap(); try {
        //初始化 bootstrap.init(); }
catch (Throwable var3) { handleThrowable(var3); var3.printStackTrace(); return; }         //賦值給daemon daemon = bootstrap; } else { 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); daemon.load(args); daemon.start(); } 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 var4) { Throwable t = var4; if (var4 instanceof InvocationTargetException && var4.getCause() != null) { t = var4.getCause(); } handleThrowable(t); t.printStackTrace(); System.exit(1); } }

 

  Bootstrap類中的main方法只幹兩件事情,(1):新建一個bootstrap,並執行init方法初始化,初始化後賦值給daemon,然後處理main方法傳入進來的命令,來判斷執行對應的方法,比如傳入start,執行start方法,如果傳入錯誤的命令,直接告警command不存在.

    在main方法中,daemon呼叫了好幾個方法,當main方法傳入的命令是start的時候,會自動呼叫setAwait(true),load()和start()方法:

      

} else if (command.equals("start")) {
                daemon.setAwait(true);
                daemon.load(args);
                daemon.start();

    跟進start方法:

      org.apache.catalina.startup.Bootstrap#182:

public void start() throws Exception {
        if (this.catalinaDaemon == null) {
            this.init();
        }

        Method method = this.catalinaDaemon.getClass().getMethod("start", (Class[])null);
        method.invoke(this.catalinaDaemon, (Object[])null);
    }

  首先會判斷你的catalinaDaemon是否為空,不為空,再用反射呼叫start方法,然後例項化類.

    所以上面的daemon.start相當於:(Catalina)catalinaDaemon.start()

    同理,其他的方法如setAwait(true)和load方法也是通過反射呼叫,這裡不在展示程式碼.

  

  從前面的分析,我們知道tomcat入口類會呼叫tomcat管理類的start,load,setAwait方法:

    tomcat入口類Bootstarp和tomcat管理類Catalina是相輔相成的:

    Catalina的啟動主要是呼叫setAwait,load和start方法來完成的,setAwait方法用於設定Server啟動完成後是否進入等待狀態的標誌,如果為true就進入,否則講究不進入

    load方法會自動載入server.xml配置檔案,建立並初始化Server [getServer.init()] 

    start方法用於啟動伺服器 

    下面一個個看下這些方法:

    首先來看setAwait方法:

    org.apache.startup.Catalina:

public void setAwait(boolean b) {
        this.await = b;
    }

  

 

 

  這個方法就是設定await的屬性值,await屬性會在start方法中的伺服器啟動完成之後,使用它來判斷是否進入等待狀態:

    檢視load方法,文章開頭已經講了load方法,load方法中會根據conf/server.xml建立Server物件,並呼叫server的init方法來初始化

    Catalina的start方法檢視:

      

public void start() {
        if (this.getServer() == null) {
            this.load();
        }

        if (this.getServer() == null) {
            log.fatal("Cannot start server. Server instance is not configured.");
        } else {
            long t1 = System.nanoTime();

            try {
         //呼叫Server的start方法啟動伺服器
this.getServer().start(); } catch (LifecycleException var7) { log.fatal(sm.getString("catalina.serverStartFail"), var7); try { this.getServer().destroy(); } catch (LifecycleException var6) { log.debug("destroy() failed for failed Server ", var6); } return; } long t2 = System.nanoTime(); if (log.isInfoEnabled()) { log.info("Server startup in " + (t2 - t1) / 1000000L + " ms"); } if (this.useShutdownHook) { if (this.shutdownHook == null) { this.shutdownHook = new Catalina.CatalinaShutdownHook(); } Runtime.getRuntime().addShutdownHook(this.shutdownHook); LogManager logManager = LogManager.getLogManager(); if (logManager instanceof ClassLoaderLogManager) { ((ClassLoaderLogManager)logManager).setUseShutdownHook(false); } }         //判斷等待狀態 if (this.await) { this.await(); this.stop(); } } }

    start方法主要呼叫了server的start方法啟動伺服器,並根據等待狀態判斷是否讓程式進行等待狀態

   這裡首先判斷getServer是否存在,如果不存在就啟動server的load方法進行初始化Server.然後呼叫Server的start方法來啟動伺服器,註冊判斷await屬性.在tomcat入口類Bootstrap類中,設定await為true,所以需要進入等待狀態,跟進邏輯判斷的await方法,靜態除錯進入:

    org.apache.catalina.core.StandServer:

    

 

 

  發現await方法內部會執行一個while迴圈,這樣程式就會停到awit方法,當await方法裡的while迴圈退出時,就會執行stop方法,從而關閉伺服器.

    

    通過上面的學習,我們簡單梳理了tomcat的入口類Bootstrap類和tomcat的管理類Catalina

    繼續學習往下突進:

    Server介面的預設實現是org.apache.catalina.core.StandardServer,可通過反射載入進入檢視程式碼:

   

    

public final class StandardServer extends LifecycleMBeanBase implements Server {

  StandardServer類繼承自LifecycleMBeanBase類,跟進LifecycleMBeanBase類:

    org.apache.catalina.util.LifecycleMBeanBase:

    

 

 

  LifecycleMBeanBase類又繼承自LifecycleBase:

    org.apache.catalina.util.LifecycleBase:

  

 

 

    檢視LifecycleBase類的init和start方法:

      org.apache.catalina.util.LifecycleBase:

 public final synchronized void init() throws LifecycleException {
        if (!this.state.equals(LifecycleState.NEW)) {
            this.invalidTransition("before_init");
        }

        this.setStateInternal(LifecycleState.INITIALIZING, (Object)null, false);

        try {
            this.initInternal();
        } catch (Throwable var2) {
            ExceptionUtils.handleThrowable(var2);
            this.setStateInternal(LifecycleState.FAILED, (Object)null, false);
            throw new LifecycleException(sm.getString("lifecycleBase.initFail", new Object[]{this.toString()}), var2);
        }

        this.setStateInternal(LifecycleState.INITIALIZED, (Object)null, false);
    }

   發現init方法會呼叫initInternal方法:

    

 

 

  initInternal是一個模組方法,需要其子類去實現此方法.

    LifecycleBase類start方法:

    擷取部分:     

public final synchronized void start() throws LifecycleException {
        if (!LifecycleState.STARTING_PREP.equals(this.state) && !LifecycleState.STARTING.equals(this.state) && !LifecycleState.STARTED.equals(this.state)) {
            if (this.state.equals(LifecycleState.NEW)) {
                this.init();
            } else if (this.state.equals(LifecycleState.FAILED)) {
                this.stop();
            } else if (!this.state.equals(LifecycleState.INITIALIZED) && !this.state.equals(LifecycleState.STOPPED)) {
                this.invalidTransition("before_start");
            }

            this.setStateInternal(LifecycleState.STARTING_PREP, (Object)null, false);

            try {
                this.startInternal();
            } catch (Throwable var2) {
                ExceptionUtils.handleThrowable(var2);
                this.setStateInternal(LifecycleState.FAILED, (Object)null, false);
                throw new LifecycleException(sm.getString("lifecycleBase.startFail", new Object[]{this.toString()}), var2);
            }

    會呼叫this.startInternal();方法:

    

  startInternal方法也是模組方法,需要其子類去具體實現方法:

    我們的StandardServer類和LifecycleMBeanBase類都是繼承自LifecycleBase,都是LifecycleBase的子類,都可以去實現方法的.

   回到StandardServer類,檢視initInternal方法實現:

  org.apache.catalina.core.StandardServer:

protected void initInternal() throws LifecycleException {
        super.initInternal();
        this.onameStringCache = this.register(new StringCache(), "type=StringCache");
        MBeanFactory factory = new MBeanFactory();
        factory.setContainer(this);
        this.onameMBeanFactory = this.register(factory, "type=MBeanFactory");
        this.globalNamingResources.init();
        if (this.getCatalina() != null) {
  ..................

    

  檢視startInternal方法具體實現:

protected void startInternal() throws LifecycleException {
        this.fireLifecycleEvent("configure_start", (Object)null);
        this.setState(LifecycleState.STARTING);
        this.globalNamingResources.start();
        synchronized(this.servicesLock) {
            for(int i = 0; i < this.services.length; ++i) {
                this.services[i].start();
            }

        }
    }

    除了startInternal和initInternal方法,StandardServer中還實現了await方法,Catalina中就是呼叫它讓伺服器進入等待狀態的:

public void await() {
        if (this.port != -2) {
            if (this.port == -1) {
                try {
                    this.awaitThread = Thread.currentThread();

                    while(!this.stopAwait) {
                        try {
                            Thread.sleep(10000L);
                        } catch (InterruptedException var64) {
                        }
                    }
                } finally {
                    this.awaitThread = null;
                }

            } else {
                try {
                    this.awaitSocket = new ServerSocket(this.port, 1, InetAddress.getByName(this.address));
                } catch (IOException var67) {
                    log.error("StandardServer.await: create[" + this.address + ":" + this.port + "]: ", var67);
                    return;
                }

                boolean var32 = false;

                ServerSocket serverSocket;
                try {
                    var32 = true;
                    this.awaitThread = Thread.currentThread();

                    while(true) {
                        if (this.stopAwait) {
                            var32 = false;
                            break;
                        }

                        serverSocket = this.awaitSocket;
                        if (serverSocket == null) {
                            var32 = false;
                            break;
                        }

                        Socket socket = null;
                        StringBuilder command = new StringBuilder();

                        label603: {
                            label602: {
                                try {
                                    label618: {
                                        long acceptStartTime = System.currentTimeMillis();

                                        InputStream stream;
                                        try {
                                            socket = serverSocket.accept();
                                            socket.setSoTimeout(10000);
                                            stream = socket.getInputStream();
                                        } catch (SocketTimeoutException var69) {
                                            log.warn(sm.getString("standardServer.accept.timeout", new Object[]{System.currentTimeMillis() - acceptStartTime}), var69);
                                            continue;
                                        } catch (AccessControlException var70) {
                                            log.warn("StandardServer.accept security exception: " + var70.getMessage(), var70);
                                            continue;
                                        } catch (IOException var71) {
                                            if (this.stopAwait) {
                                                break label602;
                                            }

                                            log.error("StandardServer.await: accept: ", var71);
                                            break label618;
                                        }

                                        int expected;
                                        for(expected = 1024; expected < this.shutdown.length(); expected += this.random.nextInt() % 1024) {
                                            if (this.random == null) {
                                                this.random = new Random();
                                            }
                                        }

                                        while(true) {
                                            if (expected <= 0) {
                                                break label603;
                                            }

                                            boolean var8 = true;

                                            int ch;
                                            try {
                                                ch = stream.read();
                                            } catch (IOException var66) {
                                                log.warn("StandardServer.await: read: ", var66);
                                                ch = -1;
                                            }

                                            if (ch < 32) {
                                                break label603;
                                            }

                                            command.append((char)ch);
                                            --expected;
                                        }
                                    }
                                } finally {
                                    try {
                                        if (socket != null) {
                                            socket.close();
                                        }
                                    } catch (IOException var63) {
                                    }

                                }

                                var32 = false;
                                break;
                            }

                            var32 = false;
                            break;
                        }

                        boolean match = command.toString().equals(this.shutdown);
                        if (match) {
                            log.info(sm.getString("standardServer.shutdownViaPort"));
                            var32 = false;
                            break;
                        }

                        log.warn("StandardServer.await: Invalid command '" + command.toString() + "' received");
                    }
                } finally {
                    if (var32) {
                        ServerSocket serverSocket = this.awaitSocket;
                        this.awaitThread = null;
                        this.awaitSocket = null;
                        if (serverSocket != null) {
                            try {
                                serverSocket.close();
                            } catch (IOException var62) {
                            }
                        }

                    }
                }

                serverSocket = this.awaitSocket;
                this.awaitThread = null;
                this.awaitSocket = null;
                if (serverSocket != null) {
                    try {
                        serverSocket.close();
                    } catch (IOException var65) {
                    }
                }

            }
        }
    }

 

   StandardServer類中的await實現程式碼很長,他大概率的處理邏輯是這樣的:

    首先判斷port埠號,port=多少就進入哪個邏輯判斷:

    

 

 

 

  port為-1就進入一個while迴圈:

      

 

  程式碼中沒有break語句,只有在呼叫stop的時候,當stopAwait為true,才會退出迴圈

 

     當port為其他值得時候,走else語句:

    

 

 

     建立ServerSocket物件,跟進程式碼,發現是個繫結操作,繫結地址和埠:

        

 

  往下看程式碼,等待接收訊息,它會把等待接收訊息的資料儲存到StringBuilder command中:

    

StringBuilder command = new StringBuilder();

                        label603: {
                            label602: {
                                try {
                                    label618: {
                                        long acceptStartTime = System.currentTimeMillis();

                                        InputStream stream;
                                        try {
                                            socket = serverSocket.accept();
                                            socket.setSoTimeout(10000);
                                            stream = socket.getInputStream();
                                        } catch (SocketTimeoutException var69) {
                                            log.warn(sm.getString("standardServer.accept.timeout", new Object[]{System.currentTimeMillis() - acceptStartTime}), var69);
                                            continue;
                                        } catch (AccessControlException var70) {
                                            log.warn("StandardServer.accept security exception: " + var70.getMessage(), var70);
                                            continue;
                                        } catch (IOException var71) {
                                            if (this.stopAwait) {
                                                break label602;
                                            }

                                            log.error("StandardServer.await: accept: ", var71);
                                            break label618;
                                        }

                                        int expected;
                                        for(expected = 1024; expected < this.shutdown.length(); expected += this.random.nextInt() % 1024) {
                                            if (this.random == null) {
                                                this.random = new Random();
                                            }
                                        }

                                        while(true) {
                                            if (expected <= 0) {
                                                break label603;
                                            }

                                            boolean var8 = true;

                                            int ch;
                                            try {
                                                ch = stream.read();
                                            } catch (IOException var66) {
                                                log.warn("StandardServer.await: read: ", var66);
                                                ch = -1;
                                            }

                                            if (ch < 32) {
                                                break label603;
                                            }

                                            command.append((char)ch);
                                            --expected;
                                        }

 

  繼續往下看程式碼,就是把監聽接收到的命令和shutdown匹配,如果匹配上,就break退出迴圈:

//檢查在指定埠接收到的命令是否和shutdown匹配
boolean match = command.toString().equals(this.shutdown);
                        if (match) {
                            log.info(sm.getString("standardServer.shutdownViaPort"));
                            var32 = false;
                            break;
                        }

 

   

 

 

    這裡的shutdown和port對應的是conf/server.xml檔案中的:

      

      

 

 

 

  這時程會在8005埠監聽shutdown命令,如果接收到就關閉tomcat. 對接收到的資料,tomcat也是有要求的:

    

                          int ch;
                                            try {
                                                ch = stream.read();
                                            } catch (IOException var66) {
                                                log.warn("StandardServer.await: read: ", var66);
                                                ch = -1;
                                            }

                                            if (ch < 32) {
                                                break label603;
                                            }

  接收到的資料ascii<32,會自動截斷掉摒棄.

  

  現在已經講完了Server的啟動過程,以及上面講的tomcat管理類和tomcat入口類,繼續衝

    Service的啟動過程:

    Service的預設實現類是:org.apache.catalina.core.StandardService

    

 

    StandardService和StandardServer一樣,都繼承自LifecycleMBeanBase,而LifecycleMBeanBase繼承自LifecycleBase:

    所以StandardService也是會呼叫initInternal和startInternal方法:

    來看下這兩個方法:

      先看initInternal方法:

protected void initInternal() throws LifecycleException {
        super.initInternal();
        if (this.container != null) {
            this.container.init();
        }

        Executor[] arr$ = this.findExecutors();
        int len$ = arr$.length;

        int len$;
        for(len$ = 0; len$ < len$; ++len$) {
            Executor executor = arr$[len$];
            if (executor instanceof JmxEnabled) {
                ((JmxEnabled)executor).setDomain(this.getDomain());
            }

            executor.init();
        }

        this.mapperListener.init();
        synchronized(this.connectorsLock) {
            Connector[] arr$ = this.connectors;
            len$ = arr$.length;

            for(int i$ = 0; i$ < len$; ++i$) {
                Connector connector = arr$[i$];

                try {
                    connector.init();
                } catch (Exception var9) {
                    String message = sm.getString("standardService.connector.initFailed", new Object[]{connector});
                    log.error(message, var9);
                    if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
                        throw new LifecycleException(message);
                    }
                }
            }

        }
    }

     可以發現StandardService類的initInternl方法先呼叫父型別的initInternl方法,然後開始呼叫this.container.init(); executor.init();connector.init();

    完整程式碼:    

protected void startInternal() throws LifecycleException {
        if (log.isInfoEnabled()) {
            log.info(sm.getString("standardService.start.name", new Object[]{this.name}));
        }

        this.setState(LifecycleState.STARTING);
        if (this.container != null) {
            synchronized(this.container) {
                this.container.start();
            }
        }

        synchronized(this.executors) {
            Iterator i$ = this.executors.iterator();

            while(true) {
                if (!i$.hasNext()) {
                    break;
                }

                Executor executor = (Executor)i$.next();
                executor.start();
            }
        }

        this.mapperListener.start();
        synchronized(this.connectorsLock) {
            Connector[] arr$ = this.connectors;
            int len$ = arr$.length;

            for(int i$ = 0; i$ < len$; ++i$) {
                Connector connector = arr$[i$];

                try {
                    if (connector.getState() != LifecycleState.FAILED) {
                        connector.start();
                    }
                } catch (Exception var8) {
                    log.error(sm.getString("standardService.connector.startFailed", new Object[]{connector}), var8);
                }
            }

        }
    }

 

   可以發現StandardService類的startInternl方法,主要呼叫了: this.container.start(); executor.start();this.mapperListener.start();connector.start();

    mapperListener是Mapper的監聽器,可以監聽container容器的變化,executors是用在connectors中管理執行緒的執行緒池

    在server.xml配置檔案中有參考用法,不過預設是註釋掉的:

      內容如下:     

<Service name="Catalina">

    <!--The connectors can use a shared executor, you can define one or more named thread pools-->
    <!--
    <Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
        maxThreads="150" minSpareThreads="4"/>
    -->

      

 

  這樣Connector就配置了一個tomcatThreadPool執行緒池,最多可以同時啟動150個執行緒,最少4個可用執行緒

  整個tomcat的啟動流程如下:    

    tomcat 入口類:Bootstrap---->tomcat 管理類:Calalina----->Sever實現類:StandardServer ----->Service實現類:StandardSercice ----->MapperListencer----->Executor----->Connector   

 

 

 

 

      

  

    

 

 

  

 

相關文章