1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Tomcat 第三篇:總體架構(gòu)設(shè)計(jì)

        共 11952字,需瀏覽 24分鐘

         ·

        2020-09-22 20:43

        Tomcat 總體架構(gòu)設(shè)計(jì)

        在開始這篇文章的時(shí)候,忽然發(fā)現(xiàn)上一篇內(nèi)容的題目不是很合適,不應(yīng)該叫啟動流程,更確切的應(yīng)該是叫啟動腳本。

        在最開始,先介紹下 Tomcat 的總體設(shè)計(jì),先有一個(gè)大概的印象,對 Tomcat 不至于那么陌生。

        先介紹下 Tomcat 的一些基礎(chǔ)組件(以下內(nèi)容來自劉光瑞老師的「tomcat 架構(gòu)解析」):

        組件名稱介紹
        Server這個(gè)其實(shí)就是 Servlet 容器,一個(gè) Tomcat 中只能有一個(gè) Server
        ServiceService 表示一個(gè)或多個(gè) Connector 的集合,這些 Connector 共享同一個(gè) Container 來處理其請求。在同一個(gè) Tomcat 實(shí)例內(nèi)可以包含任意多個(gè) Service 實(shí)例,它們彼此獨(dú)立
        Connector這個(gè)是 Tomcat 的連接器,用于監(jiān)聽并轉(zhuǎn)化 Servlet 的請求,同時(shí)將讀取的 Socket 請求轉(zhuǎn)交由 Container 處理,并且支持不同的協(xié)議以及不同的 I/O 方式。
        Container表示能夠執(zhí)行客戶端請求并返回響應(yīng)的一類對象。在 Tomcat 中存在不同級別的容器 Engine, Host, Context, Wrapper
        Engine表示整個(gè) Servlet 引擎。在 Tomcat 中, Engine 為最高層級的容器對象。盡管 Engine 不是直接處理請求的容器,卻是獲取目標(biāo)容器的入口。
        HostHost 作為一類容器,表示 Servlet 引擎(即 Engine 中的虛擬機(jī),與一個(gè)服務(wù)器的網(wǎng)絡(luò)名有關(guān),如域名等。客戶端可以使用這個(gè)網(wǎng)絡(luò)名連接服務(wù)器,這個(gè)名稱必須要在 DNS 服務(wù)器上注冊
        ContextContext 作為一類容器,用于表示 Servletcontext ,在 Servlet 規(guī)范中,一個(gè) Servletcontext 即表示一個(gè)獨(dú)立的 Web 應(yīng)用。
        WapperWapper 作為一類容器,用于表示 Web 應(yīng)用中定義的 Servlet。
        Executor表示 Tomcat 組件間可以共享的線程池。

        這個(gè)表格大致看一下,了解下 Tomcat 的一些組件,無需記憶,先有個(gè)印象,后面的內(nèi)容會慢慢的聊到每一個(gè)組件。

        作為一款 Web 容器, Tomcat 大體上實(shí)現(xiàn)了兩個(gè)核心功能:

        • 處理 Socket 連接,負(fù)責(zé)網(wǎng)絡(luò)字節(jié)流與 RequestResponse 對象的轉(zhuǎn)化。
        • 加載并管理 Servlet ,以及處理具體的 Request 請求。

        「所以 Tomcat 設(shè)計(jì)了兩個(gè)核心組件連接器(Connector)和容器(Container)。連接器負(fù)責(zé)對外交流,容器負(fù)責(zé)內(nèi)部處理?!?/strong>

        Tomcat 為了實(shí)現(xiàn)多種支持多種 I/O 模型和應(yīng)用層協(xié)議,將 連接器(Connector)容器(Container) 進(jìn)行了分離,

        在 Tomcat 中,總會有一個(gè) Service ,一個(gè) Service 包含著多個(gè) Connector 和一個(gè) Container(或者說是它的頂層容器 Engine ) 。

        而一個(gè) Container 可以包含多個(gè) Host ,一個(gè) Host 內(nèi)部又可以有多個(gè) Context ,一個(gè) Context 內(nèi)部又會有多個(gè) Servlet 。

        Tomcat 的設(shè)計(jì)感覺上和 「俄羅斯套娃」 很像,一層包著一層。

        Tomcat 啟動初始化流程

        先放一張啟動流程圖,然后我們再從源碼的角度來驗(yàn)證這個(gè)啟動流程。

        從上面這張圖可以清晰的了解到 Tomcat 的初始化的核心過程如下:

        「BootStrap -> Catalina -> Server -> Service -> Excutor -> Container (Engine -> Host -> Context -> Container)」

        1 BootStrap.main()

        首先,整個(gè)程序是從 BootStrap 開始啟動的,執(zhí)行的是 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)?{
        ????????????????handleThrowable(t);
        ????????????????t.printStackTrace();
        ????????????????return;
        ????????????}
        ????????????daemon?=?bootstrap;
        ????????}?else?{
        ????????????//?When?running?as?a?service?the?call?to?stop?will?be?on?a?new
        ????????????//?thread?so?make?sure?the?correct?class?loader?is?used?to
        ????????????//?prevent?a?range?of?class?not?found?exceptions.
        ????????????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();
        ????????????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);
        ????}
        }

        在最開始,執(zhí)行的是一段 synchronized 的同步代碼,這里執(zhí)行代碼 bootstrap.init() 初始化了 bootstrap 對象,具體做了什么跟進(jìn)去看下:

        public?void?init()?throws?Exception?{
        ????//?初始化類加載器
        ????initClassLoaders();
        ????//?設(shè)置線程類加載器,將容器的加載器傳入
        ????Thread.currentThread().setContextClassLoader(catalinaLoader);
        ????//?加載安全類
        ????SecurityClassLoad.securityClassLoad(catalinaLoader);
        ????//?初始化日志
        ????//?Load?our?startup?class?and?call?its?process()?method
        ????if?(log.isDebugEnabled())
        ????????log.debug("Loading?startup?class");
        ????//?注意這里,通過反射加載了?Catalina
        ????Class?startupClass?=?catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
        ????Object?startupInstance?=?startupClass.getConstructor().newInstance();

        ????//?Set?the?shared?extensions?class?loader
        ????if?(log.isDebugEnabled())
        ????????log.debug("Setting?startup?class?properties");
        ????//?調(diào)用了上面加載的?Catalina?中的?setParentClassLoader?方法
        ????String?methodName?=?"setParentClassLoader";
        ????Class?paramTypes[]?=?new?Class[1];
        ????//?將類加載器賦值成一個(gè)參數(shù)
        ????paramTypes[0]?=?Class.forName("java.lang.ClassLoader");
        ????Object?paramValues[]?=?new?Object[1];
        ????//?看名字意思是一個(gè)共享加載器
        ????paramValues[0]?=?sharedLoader;
        ????Method?method?=
        ????????startupInstance.getClass().getMethod(methodName,?paramTypes);
        ????//?調(diào)用了?Catalina?內(nèi)的?setParentClassLoader?方法對?Catalina?類內(nèi)的類加載器賦值
        ????method.invoke(startupInstance,?paramValues);

        ????catalinaDaemon?=?startupInstance;
        }

        注釋都加好了,就不多解釋了,我們接著回到之前的 main ,接下來還有一句是 daemon = bootstrap ,就是把我們剛才初始化過的 bootstrap 賦值給了 daemon 這個(gè)變量。

        接下來是一大段的判斷,主要是用來判斷輸入?yún)?shù),這里面我們關(guān)注的就中間那一小段,當(dāng)輸入?yún)?shù)為 start 的時(shí)候,總共做了兩件大事:

        • daemon.load(args)
        • daemon.start()

        先跟進(jìn)去看下 daemon.load(args) 都干了點(diǎn)啥:

        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;
        ????}
        ????//?這里的?catalinaDaemon?剛才在?init()?方法里面進(jìn)行了初始化,所以這里調(diào)用的實(shí)際上是?Catalina?的?load?方法
        ????Method?method?=
        ????????catalinaDaemon.getClass().getMethod(methodName,?paramTypes);
        ????if?(log.isDebugEnabled())?{
        ????????log.debug("Calling?startup?class?"?+?method);
        ????}
        ????method.invoke(catalinaDaemon,?param);
        }

        這里實(shí)際上是開啟了整個(gè)鏈?zhǔn)秸{(diào)用,接著往下追,看下 Catalina 里面的 load 方法。

        在這里,會有一個(gè)方法的重載 load(String args[])load() ,不過關(guān)系不大,最終都是會調(diào)用到那個(gè)無參的方法上的。

        public?void?load()?{

        ????if?(loaded)?{
        ????????return;
        ????}
        ????loaded?=?true;

        ????long?t1?=?System.nanoTime();

        ????initDirs();

        ????//?Before?digester?-?it?may?be?needed
        ????initNaming();

        ????//?Create?and?execute?our?Digester
        ????Digester?digester?=?createStartDigester();

        ????InputSource?inputSource?=?null;
        ????InputStream?inputStream?=?null;
        ????File?file?=?null;
        ????try?{
        ????????try?{
        ????????????file?=?configFile();
        ????????????inputStream?=?new?FileInputStream(file);
        ????????????inputSource?=?new?InputSource(file.toURI().toURL().toString());
        ????????}?catch?(Exception?e)?{
        ????????????if?(log.isDebugEnabled())?{
        ????????????????log.debug(sm.getString("catalina.configFail",?file),?e);
        ????????????}
        ????????}
        ????????if?(inputStream?==?null)?{
        ????????????try?{
        ????????????????inputStream?=?getClass().getClassLoader()
        ????????????????????.getResourceAsStream(getConfigFile());
        ????????????????inputSource?=?new?InputSource
        ????????????????????(getClass().getClassLoader()
        ????????????????????????.getResource(getConfigFile()).toString());
        ????????????}?catch?(Exception?e)?{
        ????????????????if?(log.isDebugEnabled())?{
        ????????????????????log.debug(sm.getString("catalina.configFail",
        ????????????????????????????getConfigFile()),?e);
        ????????????????}
        ????????????}
        ????????}

        ????????//?This?should?be?included?in?catalina.jar
        ????????//?Alternative:?don't?bother?with?xml,?just?create?it?manually.
        ????????if?(inputStream?==?null)?{
        ????????????try?{
        ????????????????inputStream?=?getClass().getClassLoader()
        ????????????????????????.getResourceAsStream("server-embed.xml");
        ????????????????inputSource?=?new?InputSource
        ????????????????(getClass().getClassLoader()
        ????????????????????????.getResource("server-embed.xml").toString());
        ????????????}?catch?(Exception?e)?{
        ????????????????if?(log.isDebugEnabled())?{
        ????????????????????log.debug(sm.getString("catalina.configFail",
        ????????????????????????????"server-embed.xml"),?e);
        ????????????????}
        ????????????}
        ????????}


        ????????if?(inputStream?==?null?||?inputSource?==?null)?{
        ????????????if??(file?==?null)?{
        ????????????????log.warn(sm.getString("catalina.configFail",
        ????????????????????????getConfigFile()?+?"]?or?[server-embed.xml]"));
        ????????????}?else?{
        ????????????????log.warn(sm.getString("catalina.configFail",
        ????????????????????????file.getAbsolutePath()));
        ????????????????if?(file.exists()?&&?!file.canRead())?{
        ????????????????????log.warn("Permissions?incorrect,?read?permission?is?not?allowed?on?the?file.");
        ????????????????}
        ????????????}
        ????????????return;
        ????????}

        ????????try?{
        ????????????inputSource.setByteStream(inputStream);
        ????????????digester.push(this);
        ????????????digester.parse(inputSource);
        ????????}?catch?(SAXParseException?spe)?{
        ????????????log.warn("Catalina.start?using?"?+?getConfigFile()?+?":?"?+
        ????????????????????spe.getMessage());
        ????????????return;
        ????????}?catch?(Exception?e)?{
        ????????????log.warn("Catalina.start?using?"?+?getConfigFile()?+?":?"?,?e);
        ????????????return;
        ????????}
        ????}?finally?{
        ????????if?(inputStream?!=?null)?{
        ????????????try?{
        ????????????????inputStream.close();
        ????????????}?catch?(IOException?e)?{
        ????????????????//?Ignore
        ????????????}
        ????????}
        ????}

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

        ????//?前面都是在初始化加載各種配置文件,使用了?Digester
        ????//?Stream?redirection
        ????initStreams();

        ????//?Start?the?new?server
        ????try?{
        ????????//?開始調(diào)用的?Server?的初始化方法
        ????????//?Server?是一個(gè)接口,并且繼承了?Lifecycle?,進(jìn)行生命周期的管理
        ????????getServer().init();
        ????}?catch?(LifecycleException?e)?{
        ????????if?(Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"))?{
        ????????????throw?new?java.lang.Error(e);
        ????????}?else?{
        ????????????log.error("Catalina.start",?e);
        ????????}
        ????}

        ????long?t2?=?System.nanoTime();
        ????if(log.isInfoEnabled())?{
        ????????log.info("Initialization?processed?in?"?+?((t2?-?t1)?/?1000000)?+?"?ms");
        ????}
        }

        這一大坨大多數(shù)都是在加載各種配置文件,在這里,會完成對 server.xml 文件的解析。

        我們關(guān)心的其實(shí)就只有最后那段 try...catch 里面的那一句 getServer().init() ,開始調(diào)用 Server 的初始化方法,這個(gè)方法就是開啟一系列容器組件的加載方法。

        不過看到最后一句的 log 打印,忽然有印象了,之前在啟動 Tomcat 的時(shí)候,在日志的最后部分,都會看到這句打印,而且可以看到的是,這里并不是我之前以為的是使用毫秒數(shù)算出來的,而是取的納秒數(shù)通過除 1000000 計(jì)算得出的,難道是這樣算的更準(zhǔn)?

        getServer().init() 實(shí)際上是調(diào)用了 org.apache.catalina.Server 中的 init() 方法,而 org.apache.catalina.Server 則是一個(gè)接口,還繼承了 org.apache.catalina.Lifecycle 進(jìn)行容器生命周期的管理。

        而抽象類 org.apache.catalina.util.LifecycleBase 則是實(shí)現(xiàn)了 org.apache.catalina.Lifecycle 接口,我們在 org.apache.catalina.Lifecycle 中打開 init() 方法:

        public?final?synchronized?void?init()?throws?LifecycleException?{
        ????if?(!state.equals(LifecycleState.NEW))?{
        ????????invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
        ????}

        ????try?{
        ????????setStateInternal(LifecycleState.INITIALIZING,?null,?false);
        ????????initInternal();
        ????????setStateInternal(LifecycleState.INITIALIZED,?null,?false);
        ????}?catch?(Throwable?t)?{
        ????????handleSubClassException(t,?"lifecycleBase.initFail",?toString());
        ????}
        }

        這里我們的關(guān)注點(diǎn)是 initInternal() 這個(gè)方法,從名字上來看就是一個(gè)初始化方法,點(diǎn)過去一看,結(jié)果是一個(gè)抽象方法,實(shí)現(xiàn)類如下:

        我們到 org.apache.catalina.core.StandardServer 中去看下其中的 initInternal() 方法,在這個(gè)方法的最后,看到了循環(huán) Service 并且調(diào)用了 service.init()

        for?(Service?service?:?services)?{
        ????service.init();
        }

        因?yàn)橐粋€(gè) Server 是可以有多個(gè) Service 的,所以這里用了一個(gè)循環(huán),接下來就是一個(gè)順序的容器初始化的調(diào)用過程:

        「StandardServer -> StandardService -> StandardEngine -> Connector」

        每個(gè)容器都在初始化自身相關(guān)設(shè)置的同時(shí),將子容器初始化。

        Tomcat 在啟動初始化的時(shí)候,是通過鏈條式的調(diào)用,每初始化一個(gè)完成一個(gè)組件,就會在組件內(nèi)調(diào)用下一個(gè)組件的初始化方法。

        同樣的操作在啟動的時(shí)候也是一樣的,不知道各位是否還記得我們在 Bootstrap.main() 中還有另一句代碼 daemon.start()

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

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

        操作嘛都是一樣的操作,從這段代碼開始,調(diào)用了 Catalina.start() 的方法,然后開啟了另一個(gè)鏈?zhǔn)秸{(diào)用。

        我就不多說了,留給各位讀者自己去翻翻看源碼吧。

        參考

        https://segmentfault.com/a/1190000023475177

        https://zhuanlan.zhihu.com/p/75723328




        感謝閱讀



        瀏覽 66
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評論
        圖片
        表情
        推薦
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

          <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            午夜福利爱爱视频 | 亚洲国产亚洲精品伦第一区 | 日韩一区在线视频 | 少妇被躁c至高潮hd | 国产理论片午午午伦夜理片2021 | 国产精品国产三级国产专业不 | 久久久女女女女999久久 | 外国三级黄色片 | 国精产品av | 瘦精品无码一区二区三区四区五区六区七区八区 |