<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 11952字,需瀏覽 24分鐘

           ·

          2020-09-22 20:43

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

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

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

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

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

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

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

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

          「所以 Tomcat 設(shè)計了兩個核心組件連接器(Connector)和容器(Container)。連接器負責(zé)對外交流,容器負責(zé)內(nèi)部處理。」

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

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

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

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

          Tomcat 啟動初始化流程

          先放一張啟動流程圖,然后我們再從源碼的角度來驗證這個啟動流程。

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

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

          1 BootStrap.main()

          首先,整個程序是從 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 對象,具體做了什么跟進去看下:

          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];
          ????//?將類加載器賦值成一個參數(shù)
          ????paramTypes[0]?=?Class.forName("java.lang.ClassLoader");
          ????Object?paramValues[]?=?new?Object[1];
          ????//?看名字意思是一個共享加載器
          ????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 這個變量。

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

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

          先跟進去看下 daemon.load(args) 都干了點啥:

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

          這里實際上是開啟了整個鏈式調(diào)用,接著往下追,看下 Catalina 里面的 load 方法。

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

          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?是一個接口,并且繼承了?Lifecycle?,進行生命周期的管理
          ????????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)心的其實就只有最后那段 try...catch 里面的那一句 getServer().init() ,開始調(diào)用 Server 的初始化方法,這個方法就是開啟一系列容器組件的加載方法。

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

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

          而抽象類 org.apache.catalina.util.LifecycleBase 則是實現(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)注點是 initInternal() 這個方法,從名字上來看就是一個初始化方法,點過去一看,結(jié)果是一個抽象方法,實現(xiàn)類如下:

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

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

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

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

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

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

          同樣的操作在啟動的時候也是一樣的,不知道各位是否還記得我們在 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() 的方法,然后開啟了另一個鏈式調(diào)用。

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

          參考

          https://segmentfault.com/a/1190000023475177

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




          感謝閱讀



          瀏覽 66
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲成人内射 | 九哥超逼网| 青色色网| 波多野吉衣久久 | 91精品国产综合久久蜜芽解析速度 |