<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è)計

          共 8508字,需瀏覽 18分鐘

           ·

          2019-11-27 23:23

          66151cbcd7fb17b60a01234a117c2893.webp

          黑客技術(shù)點擊右側(cè)關(guān)注,了解黑客的世界!

          003f83b44c17d90a9fd1ac97162c3f1f.webp

          Java開發(fā)進(jìn)階點擊右側(cè)關(guān)注,掌握進(jìn)階之路!

          882475c04701475d8e312ca0c161ff2b.webp

          Python開發(fā)點擊右側(cè)關(guān)注,探討技術(shù)話題!


          作者丨ytao
          來源丨ytao(ytao-blog)

          858f112d468d001dc7534f404a5622f1.webp

          Tomcat 作為 servlet 容器實現(xiàn),它是基于 Java 語言開發(fā)的輕量級應(yīng)用服務(wù)器。因為 Tomcat 作為應(yīng)用服務(wù)器,它有著完全開源,輕量,性能穩(wěn)定,部署成本低等優(yōu)點,所以它成為目前 Java 開發(fā)應(yīng)用部署的首選,幾乎每個Java Web開發(fā)者都有使用過,但是,你對 Tomcat 的整體設(shè)計有進(jìn)行過了解和思考嗎?

          本文將基于 Tomcat8 進(jìn)行分析,具體版本為 Tomcat8 當(dāng)前官網(wǎng)最新修改(2019-11-21 09:28)的版本 v8.5.49

          總體結(jié)構(gòu)

          Tomcat 的總體結(jié)構(gòu)中有很多模塊,下圖列出我們將要進(jìn)行分析結(jié)構(gòu)中的主要模塊。其中主要分析的是Service,Connector,Engine,Host,Context,Wrapper。為避免圖層看著太亂,下圖中 n代表該組件可允許存在多個。

          606c5765c67d75eb35fdfbbb3b5ba8f2.webp

          如上圖所描述的是:Server 是 tomcat 服務(wù)器,在 Server 中可以存在多個服務(wù) Service 。每個服務(wù)中可有多個連接器和一個 Servlet 引擎 Engine,一個 Service 中多個連接器對應(yīng)一個 Engine。每個 Engine 中,可存在多個域名,這里可用虛擬主機(jī)的概念來表示 Host。每個 Host 中可以存在多個應(yīng)用 Context。Server,Service,Connector,Engine,Host,Context,Wrapper 它們之間的關(guān)系,除了Connector和Engine,它們是平行關(guān)系,其它的都是存在包含關(guān)系。同時,它們也都繼承了 Lifecycle 接口,該接口提供的是生命周期的管理,里面包括:初始化(init),啟動(start),停止(stop),銷毀(destroy)。當(dāng)它的父容器啟動時,會調(diào)用它子容器的啟動,停止也是一樣的。

          8ea3d2e08278d61fff79f8e1f8551b56.webp

          上圖中,還可以看到,Engine,Host,Context,Wrapper 都繼承自 Container。它有個 backgroundProcess()方法,后臺異步處理,所以繼承它后可以方便的創(chuàng)建異步線程。在 Tomcat7 中,有看到 Service 持有的是 Container,而不是 Engine。估計這也是為什么在當(dāng)前版本中添加 Engine 方法名叫 setContainer。

          Server

          Tomcat 源碼中有提供 org.apache.catalina.Server接口,對應(yīng)的默認(rèn)實現(xiàn)類為 org.apache.catalina.core.StandardServer,接口里面提供有如下圖方法。

          0658815e2d3b7ef77ba333de6aa12cbd.webp

          上圖中可以知道 Server 做的工作:對 Service,Address,Port,Catalina 以及全局命名資源的管理操作。Server 在進(jìn)行初始化的時候,會加載我們 server.xml 中配置的數(shù)據(jù)。

          54e1ef46180ab23541953e61b07049c2.webp

          這里對其中的 Service 操作的 addService向定義的服務(wù)集添加新服務(wù)進(jìn)行分析:

          1. // 保存服務(wù)的服務(wù)集

          2. privateService services[] = newService[0];


          3. finalPropertyChangeSupport support = newPropertyChangeSupport(this);


          4. @Override

          5. publicvoid addService(Service service) {

          6. // 相互關(guān)聯(lián)

          7. service.setServer(this);


          8. // 利用同步鎖,防止并發(fā)訪問 來源:https://ytao.top

          9. synchronized(servicesLock) {

          10. Service results[] = newService[services.length + 1];

          11. // copy 舊的服務(wù)到新的數(shù)組中

          12. System.arraycopy(services, 0, results, 0, services.length);

          13. // 添加新的 service

          14. results[services.length] = service;

          15. services = results;


          16. // 如果當(dāng)前 server 已經(jīng)啟動,那么當(dāng)前添加的 service 就開始啟動

          17. if(getState().isAvailable()) {

          18. try{

          19. service.start();

          20. } catch(LifecycleException e) {

          21. // Ignore

          22. }

          23. }


          24. // 使用觀察者模式,當(dāng)被監(jiān)聽對象屬性值發(fā)生變化時通知監(jiān)聽器,remove 是也會調(diào)用。

          25. support.firePropertyChange("service", null, service);

          26. }


          27. }

          源碼中可以看到,向服務(wù)器中添加服務(wù)后,隨機(jī)會啟動服務(wù),實則也服務(wù)啟動入口。

          Service

          Service 的主要職責(zé)就是將 Connector 和 Engine 的組裝在一起。兩者分開的目的也就是使請求監(jiān)聽和請求處理進(jìn)行解耦,能擁有更好的擴(kuò)展性。每個 Service 都是相互獨立的,但是共享一個JVM和系統(tǒng)類庫。這里提供了 org.apache.catalina.Service接口和默認(rèn)實現(xiàn)類 org.apache.catalina.coreStandardService。

          fc66c62dcdfc3260e0adf4d2f0102875.webp

          在實現(xiàn)類 StandardService 中,主要分析 setContaineraddConnector兩個方法。

          1. privateEngine engine = null;


          2. protectedfinalMapperListener mapperListener = newMapperListener(this);


          3. @Override

          4. publicvoid setContainer(Engine engine) {

          5. Engine oldEngine = this.engine;

          6. // 判斷當(dāng)前 Service 是否有關(guān)聯(lián) Engine

          7. if(oldEngine != null) {

          8. // 如果當(dāng)前 Service 有關(guān)聯(lián) Engine,就去掉當(dāng)前關(guān)聯(lián)的 Engine

          9. oldEngine.setService(null);

          10. }

          11. // 如果當(dāng)前新的 Engine 不為空,那么 Engine 關(guān)聯(lián)當(dāng)前 Service,這里是個雙向關(guān)聯(lián)

          12. this.engine = engine;

          13. if(this.engine != null) {

          14. this.engine.setService(this);

          15. }

          16. // 如果當(dāng)前 Service 啟動了,那么就開始啟動當(dāng)前新的 Engine

          17. if(getState().isAvailable()) {

          18. if(this.engine != null) {

          19. try{

          20. this.engine.start();

          21. } catch(LifecycleException e) {

          22. log.error(sm.getString("standardService.engine.startFailed"), e);

          23. }

          24. }

          25. // 重啟 MapperListener ,獲取一個新的 Engine ,一定是當(dāng)前入?yún)⒌?Engine

          26. try{

          27. mapperListener.stop();

          28. } catch(LifecycleException e) {

          29. log.error(sm.getString("standardService.mapperListener.stopFailed"), e);

          30. }

          31. try{

          32. mapperListener.start();

          33. } catch(LifecycleException e) {

          34. log.error(sm.getString("standardService.mapperListener.startFailed"), e);

          35. }


          36. // 如果當(dāng)前 Service 之前有 Engine 關(guān)聯(lián),那么停止之前的 Engine

          37. if(oldEngine != null) {

          38. try{

          39. oldEngine.stop();

          40. } catch(LifecycleException e) {

          41. log.error(sm.getString("standardService.engine.stopFailed"), e);

          42. }

          43. }

          44. }


          45. // Report this property change to interested listeners

          46. support.firePropertyChange("container", oldEngine, this.engine);

          47. }


          48. /**

          49. * 實現(xiàn)方式和 StandardServer#addService 類似,不在細(xì)述

          50. * 注意,Connector 這里沒有像 Engine 一樣與 Service 實現(xiàn)雙向關(guān)聯(lián)

          51. */

          52. @Override

          53. publicvoid addConnector(Connector connector) {


          54. synchronized(connectorsLock) {

          55. connector.setService(this);

          56. Connector results[] = newConnector[connectors.length + 1];

          57. System.arraycopy(connectors, 0, results, 0, connectors.length);

          58. results[connectors.length] = connector;

          59. connectors = results;


          60. if(getState().isAvailable()) {

          61. try{

          62. connector.start();

          63. } catch(LifecycleException e) {

          64. log.error(sm.getString(

          65. "standardService.connector.startFailed",

          66. connector), e);

          67. }

          68. }


          69. // Report this property change to interested listeners

          70. support.firePropertyChange("connector", null, connector);

          71. }


          72. }

          Connector

          Connector 主要用于接收請求,然后交給 Engine 處理請求,處理完后再給 Connector 去返回給客戶端。當(dāng)前使用版本支持的協(xié)議有:HTTP,HHTP/2,AJP,NIO,NIO2,APR主要的功能包括:

          • 監(jiān)聽服務(wù)器端口來讀取客戶端的請求。

          • 解析協(xié)議并交給對應(yīng)的容器處理請求。

          • 返回處理后的信息給客戶端

          Connector 對應(yīng)服務(wù)器 server.xml 中配置信息的例子:

          1. port="8080"protocol="HTTP/1.1"

          2. connectionTimeout="20000"

          3. redirectPort="8443"/>

          這里通過配置監(jiān)聽的端口號 port,指定處理協(xié)議 protocol,以及重定向地址 redirectPort。協(xié)議處理類型通過實例化連接器時設(shè)置:

          1. publicConnector() {

          2. // 無參構(gòu)造,下面 setProtocol 中默認(rèn)使用HTTP/1.1

          3. this(null);

          4. }


          5. publicConnector(String protocol) {

          6. // 設(shè)置當(dāng)前連接器協(xié)議處理類型

          7. setProtocol(protocol);

          8. // 實例化協(xié)議處理器,并保存到當(dāng)前 Connector 中

          9. ProtocolHandler p = null;

          10. try{

          11. Class clazz = Class.forName(protocolHandlerClassName);

          12. p = (ProtocolHandler) clazz.getConstructor().newInstance();

          13. } catch(Exception e) {

          14. log.error(sm.getString(

          15. "coyoteConnector.protocolHandlerInstantiationFailed"), e);

          16. } finally{

          17. this.protocolHandler = p;

          18. }


          19. if(Globals.STRICT_SERVLET_COMPLIANCE) {

          20. uriCharset = StandardCharsets.ISO_8859_1;

          21. } else{

          22. uriCharset = StandardCharsets.UTF_8;

          23. }

          24. }


          25. /**

          26. * 這個設(shè)置再 tomcat9 中被移除,改為必配項

          27. */

          28. publicvoid setProtocol(String protocol) {


          29. boolean aprConnector = AprLifecycleListener.isAprAvailable() &&

          30. AprLifecycleListener.getUseAprConnector();


          31. // 這里指定了默認(rèn)協(xié)議和 HTTP/1.1 一樣

          32. if("HTTP/1.1".equals(protocol) || protocol == null) {

          33. if(aprConnector) {

          34. setProtocolHandlerClassName("org.apache.coyote.http11.Http11AprProtocol");

          35. } else{

          36. setProtocolHandlerClassName("org.apache.coyote.http11.Http11NioProtocol");

          37. }

          38. } elseif("AJP/1.3".equals(protocol)) {

          39. if(aprConnector) {

          40. setProtocolHandlerClassName("org.apache.coyote.ajp.AjpAprProtocol");

          41. } else{

          42. setProtocolHandlerClassName("org.apache.coyote.ajp.AjpNioProtocol");

          43. }

          44. } else{

          45. // 最后如果不是通過指定 HTTP/1.1,AJP/1.3 類型的協(xié)議,就通過類名實例化一個協(xié)議處理器

          46. setProtocolHandlerClassName(protocol);

          47. }

          48. }

          ProtocolHandler 是一個協(xié)議處理器,針對不同的請求,提供不同實現(xiàn)。實現(xiàn)類 AbstractProtocol 在初始化時,會在最后調(diào)用一個抽象類 AbstractEndpoint 初始化來啟動線程來監(jiān)聽服務(wù)器端口,當(dāng)接收到請求后,調(diào)用 Processor 讀取請求,然后交給 Engine 處理請求。

          Engine

          Engine 對應(yīng)的是, org.apache.catalina.Engine接口和 org.apache.catalina.core.StandardEngine默認(rèn)實現(xiàn)類。Engine 的功能也比較簡單,處理容器關(guān)系的關(guān)聯(lián)。

          ecbb0e91a0ac219b6fd636826d13100d.webp

          但是實現(xiàn)類中的 addChild()不是指的子 Engine,而是只能是 Host。同時沒有父容器, setParent是不允許操作設(shè)置的。

          1. @Override

          2. publicvoid addChild(Container child) {

          3. // 添加的子容器必須是 Host

          4. if(!(child instanceofHost))

          5. thrownewIllegalArgumentException

          6. (sm.getString("standardEngine.notHost"));

          7. super.addChild(child);

          8. }


          9. @Override

          10. publicvoid setParent(Container container) {


          11. thrownewIllegalArgumentException

          12. (sm.getString("standardEngine.notParent"));


          13. }

          server.xml 可以配置我們的數(shù)據(jù):

          1. name="Catalina"defaultHost="localhost"jvmRoute="jvm1">

          Host

          Host 表示一個虛擬主機(jī)。應(yīng)為我們的服務(wù)器可設(shè)置多個域名,比如 demo.ytao.top,dev.ytao.top。那么我們就要設(shè)置兩個不同 Host 來處理不同域名的請求。當(dāng)過來的請求域名為 demo.ytao.top 時,那么它就會去找該域名 Host 下的 Context。所以我們的 server.xml 配置文件也提供該配置:

          1. name="localhost"appBase="webapps"

          2. unpackWARs="true"autoDeploy="true">

          Context

          到 Context 這里來,就擁有 Servlet 的運行環(huán)境,Engine,Host都是主要維護(hù)容器關(guān)系,不具備運行環(huán)境。我們暫且可將 Context 理解為一個應(yīng)用,例如我們在根目錄下有 ytao-demo-1 和 ytao-demo-2 兩個應(yīng)用,那么這里就是有兩個 Context。這里主要介紹的 addChild方法,該添加的子容器是 Wrapper:

          1. @Override

          2. publicvoid addChild(Container child) {


          3. // Global JspServlet

          4. Wrapper oldJspServlet = null;


          5. // 這里添加的子容器只能時 Wrapper

          6. if(!(child instanceofWrapper)) {

          7. thrownewIllegalArgumentException

          8. (sm.getString("standardContext.notWrapper"));

          9. }


          10. // 判斷子容器 Wrapper 是否為 JspServlet

          11. boolean isJspServlet = "jsp".equals(child.getName());


          12. // Allow webapp to override JspServlet inherited from global web.xml.

          13. if(isJspServlet) {

          14. oldJspServlet = (Wrapper) findChild("jsp");

          15. if(oldJspServlet != null) {

          16. removeChild(oldJspServlet);

          17. }

          18. }


          19. super.addChild(child);


          20. // 將servlet映射添加到Context組件

          21. if(isJspServlet && oldJspServlet != null) {

          22. /*

          23. * The webapp-specific JspServlet inherits all the mappings

          24. * specified in the global web.xml, and may add additional ones.

          25. */

          26. String[] jspMappings = oldJspServlet.findMappings();

          27. for(int i=0; jspMappings!=null&& i

          28. addServletMappingDecoded(jspMappings[i], child.getName());

          29. }

          30. }

          31. }

          這里也就是每個應(yīng)用中的 Servlet 管理中心。

          Wrapper

          Wrapper 是一個 Servlet 的管理中心,它擁有 Servlet 的整個生命周期,它是沒有子容器的,因為它自己就是最底層的容器了。這里主要對 Servlet 加載的分析:

          1. publicsynchronizedServlet loadServlet() throwsServletException{


          2. // 如果已經(jīng)實例化或者用實例化池,就直接返回

          3. if(!singleThreadModel && (instance != null))

          4. return instance;


          5. PrintStream out = System.out;

          6. if(swallowOutput) {

          7. SystemLogHandler.startCapture();

          8. }


          9. Servlet servlet;

          10. try{

          11. long t1=System.currentTimeMillis();

          12. // 如果 servlet 類名為空,直接拋出 Servlet 異常

          13. if(servletClass == null) {

          14. unavailable(null);

          15. thrownewServletException

          16. (sm.getString("standardWrapper.notClass", getName()));

          17. }


          18. // 從 Context 中獲取 Servlet

          19. InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();

          20. try{

          21. servlet = (Servlet) instanceManager.newInstance(servletClass);

          22. } catch(ClassCastException e) {

          23. unavailable(null);

          24. // Restore the context ClassLoader

          25. thrownewServletException

          26. (sm.getString("standardWrapper.notServlet", servletClass), e);

          27. } catch(Throwable e) {

          28. e = ExceptionUtils.unwrapInvocationTargetException(e);

          29. ExceptionUtils.handleThrowable(e);

          30. unavailable(null);


          31. // Added extra log statement for Bugzilla 36630:

          32. // https://bz.apache.org/bugzilla/show_bug.cgi?id=36630

          33. if(log.isDebugEnabled()) {

          34. log.debug(sm.getString("standardWrapper.instantiate", servletClass), e);

          35. }


          36. // Restore the context ClassLoader

          37. thrownewServletException

          38. (sm.getString("standardWrapper.instantiate", servletClass), e);

          39. }


          40. // 加載聲明了 MultipartConfig 注解的信息

          41. if(multipartConfigElement == null) {

          42. MultipartConfig annotation =

          43. servlet.getClass().getAnnotation(MultipartConfig.class);

          44. if(annotation != null) {

          45. multipartConfigElement =

          46. newMultipartConfigElement(annotation);

          47. }

          48. }


          49. // 對 servlet 類型進(jìn)行檢查

          50. if(servlet instanceofContainerServlet) {

          51. ((ContainerServlet) servlet).setWrapper(this);

          52. }


          53. classLoadTime=(int) (System.currentTimeMillis() -t1);


          54. if(servlet instanceofSingleThreadModel) {

          55. if(instancePool == null) {

          56. instancePool = newStack<>();

          57. }

          58. singleThreadModel = true;

          59. }


          60. // 初始化 servlet

          61. initServlet(servlet);


          62. fireContainerEvent("load", this);


          63. loadTime=System.currentTimeMillis() -t1;

          64. } finally{

          65. if(swallowOutput) {

          66. String log = SystemLogHandler.stopCapture();

          67. if(log != null&& log.length() > 0) {

          68. if(getServletContext() != null) {

          69. getServletContext().log(log);

          70. } else{

          71. out.println(log);

          72. }

          73. }

          74. }

          75. }

          76. return servlet;


          77. }

          這里加載 Servlet,如果該 Servlet 沒有被實例化過,那么一定要加載一個。

          到目前為止,大致介紹了 Tomcat8 的主要組件,對 Tomcat 的整體架構(gòu)也有個大致了解了,Tomcat 源碼進(jìn)行重構(gòu)后,可讀性確實要好很多,建議大家可以去嘗試分析下,里面的使用的一些設(shè)計模式,我們在實際編碼過程中,還是有一定的借鑒意義。


          ?推薦↓↓↓?

          關(guān)

          ?16個技術(shù)公眾號】都在這里!

          涵蓋:程序員大咖、源碼共讀、程序員共讀、數(shù)據(jù)結(jié)構(gòu)與算法、黑客技術(shù)和網(wǎng)絡(luò)安全、大數(shù)據(jù)科技、編程前端、Java、Python、Web編程開發(fā)、Android、iOS開發(fā)、Linux、數(shù)據(jù)庫研發(fā)、幽默程序員等。

          b5bcac6b2c378962f6cfe123e5472163.webp萬水千山總是情,點個 “在看” 行不行
          瀏覽 41
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報
          <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>
                  大香蕉伊人网在线 | 成人AV中文字幕 | 青操网| 免费成人在线观看视频 | 中国毛片a |