探秘Tomcat——連接篇


  前兩篇我們分別粗線條和細粒度的講解了tomcat的服務是如何啟動以及連接器Connector和容器Container又分別是如何被啟動的

  本篇我們主要側重tomcat中server、service以及connector和container之間是如何相互關聯起來的。在此之前,我們分別看下這個類中的一些主要方法,尤其是用於相互關聯綁定的方法。

 

  Server:(Server代表了整個Catalina容器,一個server可以包含一個或多個Services)

 

 1   getInfo                    //獲取server的版本
 2     getGlobalNamingResources
 3     setGlobalNamingResources
 4     getPort                    //返回監聽關閉server的端口
 5     setPort
 6     getShutdown                //返回關閉server的命令字符串比如"SHUTDOWN"
 7     setShutdown
 8     addService                //在該server上添加一個service
 9     await                    //一直監聽,直到出現shutdown指令
10     findService                //返回指定名稱的service
11     findServices            //返回所有在這個server上的services集合
12     removeService
13     initialize

 

 

  Service:(Service是一組包含了一個Container和一個或多個Connector的集合)

 1   getContainer            //返回容器,該容器用於處理Service上的Connectors發送過來請求
 2     setContainer
 3     getInfo                    //返回Service的版本信息
 4     getName                    //返回該Service的名字
 5     setName
 6     getServer                //返回與此Service關聯的Server,這與Server中的addService遙相呼應
 7     setServer                //綁定一個Server
 8     addConnector            //添加一個Connector
 9     findConnectors            //返回該Service上的所有Connector    
10     removeConnector            //刪除指定的Connector,同時也以為該Connector與Container也解除聯系
11     initialize
12     addExecutor                //添加一個執行器
13     findExecutors
14     getExecutor
15     removeExecutor

 

  Connector:(前面已經說過,一個Service中可以包含多個Container,但是只會有一個Connector,而Container有多層實現關系,並且有自己的實現規范,所以定義成了接口,而這里的Connector就是一個類而非接口)

 1 Connector
 2     Connector                    //構造函數,其中有設置Connector要用到的協議
 3     getProperty                    //根據屬性名,返回屬性值
 4     setProperty
 5     getAttribute                //也是根據屬性名,返回屬性值,但是getProperty返回的是String類型,這里是Object對象
 6     setAttribute
 7     removeProperty
 8     getService                    //返回與之綁定的Service
 9     setService                    //綁定Service
10     getAllowTrace
11     setAllowTrace                //設置allowTrace,用於跟蹤http的信息
12     isAvailable                    //判斷是否可用於處理request,里面判斷的標記是started,這意味着只有Connector啟動了才能用於處理request
13     getBufferSize                
14     setBufferSize
15     getContainer                //返回當前Connector移交request的接收Container對象
16     setContainer
17     getEmptySessionPath
18     setEmptySessionPath
19     getEnableLookups
20     setEnableLookups
21     getInfo                        //返回Connector的版本信息
22     getMapper
23     getMaxHeaderCount            //返回Container允許的最大headers個數
24     setMaxHeaderCount
25     getMaxParameterCount        //返回GET和POST方法的最大個數
26     setMaxParameterCount
27     ...
28     getPort                        //返回監聽request的端口
29     setPort
30     getProtocol                    //返回使用到的protocol handler,有Http/1.1和AJP/1.3
31     setProtocol
32     getProtocolHandlerClassName
33     setProtocolHandlerClassName
34     getProtocolHandler
35     getProxyName                //設置代理的名字
36     setProxyName
37     getProxyPort
38     setProxyPort
39     getRedirectPort                //重定向端口,如果
40     setRedirectPort
41     getScheme
42     setScheme
43     getSecure
44     setSecure
45     getURIEncoding                //返回URI編碼
46     setURIEncoding
47     getUseBodyEncodingForURI
48     setUseBodyEncodingForURI
49     getXpoweredBy
50     setXpoweredBy
51     setUseIPVHosts
52     getUseIPVHosts
53     getExecutorName
54     createRequest                //創建或指派並返回Request對象,這里的Request有和Container關聯
55     createResponse
56     addLifecycleListener
57     findLifecycleListeners
58     removeLifecycleListener
59     createObjectName
60     initialize                    //初始化Connector對象
61     pause                        
62     resume
63     start
64     stop
65     ...
66     init
67     destroy
68     toString

 

  Container:(Container可以執行來自客戶端的Request請求,並返回相應的Response)

getInfo
    getLoader                    //返回與此Container相關的Loader對象,如果沒有Loader,則返回與其父Container關聯的Loader
    setLoader
    getLogger                    //返回Logger對象,用於打印log,同理如果當前沒有Logger對象,則尋找父級Logger
    getManager                    //返回Manager對象
    setManager
    getMappingObject            //返回JMX對象名字
    getObjectName
    getPipeline                    //返回與此Container相關的用於管理Valves的Pipeline
    getCluster
    setCluster
    getBackgroundProcessorDelay
    setBackgroundProcessorDelay
    getName
    setName
    getParent                    //返回父級Container
    setParent
    getParentClassLoader        //返回父級類加載器    
    setParentClassLoader
    getRealm
    setRealm
    getResources
    setResources
    backgroundProcess
    addChild                    //添加一個子容器,在添加之前,需要在子容器中先調用setParent方法
    addContainerListener        //添加事件監聽器
    addPropertyChangeListener    //添加屬性值變化監聽器    
    findChild
    findChildren
    findContainerListeners
    invoke                        //執行具體的Request,並得到具體的Response對象
    removeChild
    removeContainerListener
    removePropertyChangeListener
    logAccess

 

1.連接原理舉例

 

 

  首先我們在Catalina類的load方法中調用了方法createStartDigester,該方法在之前幾篇有介紹過,主要是對於加載的server.xml文件中定義各個組件之間的關系。

  比如方法中的片段:

digester.addObjectCreate("Server",
                         "org.apache.catalina.core.StandardServer",
                         "className");
digester.addSetProperties("Server");
digester.addSetNext("Server",
                    "setServer",
                    "org.apache.catalina.Server");

 

  • addObjectCreate就是添加一個模式,當解析server.xml遇到Server的時候,就根據Server的className實例化一個Server對象,而默認實例化的類就是org.apache.catalina.core.StandardServer;
  • addSetProperties用於設置Server的一些屬性,具體屬性在server.xml中有定義;
  • addSetNext用於調用Server類的setServer方法,把當前Server添加進去。

 

 

  再比如這幾行代碼:

digester.addObjectCreate("Server/Listener",
                         null, // MUST be specified in the element
                         "className");
digester.addSetProperties("Server/Listener");
digester.addSetNext("Server/Listener",
                    "addLifecycleListener",
                    "org.apache.catalina.LifecycleListener");

 

  對應在server.xml中就是這幾行

 

1 <!--APR library loader. Documentation at /docs/apr.html -->
2   <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
3   <!--Initialize Jasper prior to webapps are loaded. Documentation at /docs/jasper-howto.html -->
4   <Listener className="org.apache.catalina.core.JasperListener" />
5   <!-- Prevent memory leaks due to use of particular java/javax APIs-->
6   <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
7   <!-- JMX Support for the Tomcat server. Documentation at /docs/non-existent.html -->
8   <Listener className="org.apache.catalina.mbeans.ServerLifecycleListener" />
9   <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />

 

 

  • 同理addObjectCreate說的是在Server下的Listeners,根據className創建listener對象;
  • addSetProperties用於設置Server的一些屬性,具體屬性在server.xml中有定義;
  • addSetNext用於調用Server類的addLifecycleLisntener方法,把server.xml中定義的5個監聽器都實例化並添加到server上。

 

  還有關於Server和Service之間關系的代碼

 

digester.addObjectCreate("Server/Service",
                         "org.apache.catalina.core.StandardService",
                         "className");
digester.addSetProperties("Server/Service");
digester.addSetNext("Server/Service",
                    "addService",
                    "org.apache.catalina.Service");

 

  通過這些代碼我們很容易理解Server和Service之間的關聯關系(后面會詳細介紹)

 

 

  除了Server和Service之間的從屬關系,我們還可以看到Service和Connector之間的關系

 

digester.addRule("Server/Service/Connector",
                 new ConnectorCreateRule());
digester.addRule("Server/Service/Connector", 
                 new SetAllPropertiesRule(new String[]{"executor"}));
digester.addSetNext("Server/Service/Connector",
                    "addConnector",
                    "org.apache.catalina.connector.Connector");

 

  同理這里也是在Service下調用addConnector添加Connector(后面會詳細介紹)

 

2.Connector和Container以及Connector和Service何時連接?

 

 

  我們從Catalina的load方法開始,當執行到load中的digester.parse(inputSource)時,即跳轉到Digester類的parse方法中,之后開始解析server.xml中依次遇到的各個元素。

 

  當遇到server元素的時候,在代碼中方法的執行順序為Catalina.load->Digester.parse->Digester.startElement

 

  startElement方法如下:

 

 1 public void startElement(String namespaceURI, String localName,
 2                          String qName, Attributes list)
 3         throws SAXException {
 4     boolean debug = log.isDebugEnabled();
 5 
 6     if (saxLog.isDebugEnabled()) {
 7         saxLog.debug("startElement(" + namespaceURI + "," + localName + "," +
 8                 qName + ")");
 9     }
10 
11     // Parse system properties
12     list = updateAttributes(list);
13 
14     // Save the body text accumulated for our surrounding element
15     bodyTexts.push(bodyText);
16     if (debug) {
17         log.debug("  Pushing body text '" + bodyText.toString() + "'");
18     }
19     bodyText = new StringBuffer();
20 
21     // the actual element name is either in localName or qName, depending
22     // on whether the parser is namespace aware
23     String name = localName;
24     if ((name == null) || (name.length() < 1)) {
25         name = qName;
26     }
27 
28     // Compute the current matching rule
29     StringBuffer sb = new StringBuffer(match);
30     if (match.length() > 0) {
31         sb.append('/');
32     }
33     sb.append(name);
34     match = sb.toString();
35     if (debug) {
36         log.debug("  New match='" + match + "'");
37     }
38 
39     // Fire "begin" events for all relevant rules
40     List rules = getRules().match(namespaceURI, match);
41     matches.push(rules);
42     if ((rules != null) && (rules.size() > 0)) {
43         for (int i = 0; i < rules.size(); i++) {
44             try {
45                 Rule rule = (Rule) rules.get(i);
46                 if (debug) {
47                     log.debug("  Fire begin() for " + rule);
48                 }
49                 rule.begin(namespaceURI, name, list);
50             } catch (Exception e) {
51                 log.error("Begin event threw exception", e);
52                 throw createSAXException(e);
53             } catch (Error e) {
54                 log.error("Begin event threw error", e);
55                 throw e;
56             }
57         }
58     } else {
59         if (debug) {
60             log.debug("  No rules found matching '" + match + "'.");
61         }
62     }
63 
64 }
Digester.startElement

 

 

當執行到rule.begin(namespaceURI, name, list)這行的時候,通過調試信息可以看到該rule的className為org.apache.catalina.core.StandardServer,所以最終會進入StandardServer的構造函數中。

 

  另外,當解析server.xml到5個listener的時候,就會調用StandardServer的addLifecycleListener分別將這5個監聽器實例化並添加到server上。

 

 

  繼續解析,當解析到

igester.addSetNext("Server/Service/Connector",
                    "addConnector",
                    "org.apache.catalina.connector.Connector");

  的時候就會跳轉到StandardService的addConnector方法中

 

 

 1 public void addConnector(Connector connector) {
 2 
 3     synchronized (connectors) {
 4         connector.setContainer(this.container);
 5         connector.setService(this);
 6         Connector results[] = new Connector[connectors.length + 1];
 7         System.arraycopy(connectors, 0, results, 0, connectors.length);
 8         results[connectors.length] = connector;
 9         connectors = results;
10 
11         if (initialized) {
12             try {
13                 connector.initialize();
14             } catch (LifecycleException e) {
15                 log.error(sm.getString(
16                         "standardService.connector.initFailed",
17                         connector), e);
18             }
19         }
20 
21         if (started && (connector instanceof Lifecycle)) {
22             try {
23                 ((Lifecycle) connector).start();
24             } catch (LifecycleException e) {
25                 log.error(sm.getString(
26                         "standardService.connector.startFailed",
27                         connector), e);
28             }
29         }
30 
31         // Report this property change to interested listeners
32         support.firePropertyChange("connector", null, connector);
33     }
34 
35 }

 

 

  首先解析到的是server.xml中的這個connector

 

<Connector port="8080" protocol="HTTP/1.1" 
               connectionTimeout="20000" 
               redirectPort="8443" />

 

  下面的兩行代碼交代了一個Connector是如何關聯上Container和Service的:

  • connector.setContainer(this.container):說明了connector和container是如何關聯的,調用connector對象的setContainer方法,而傳進的值為this.Container,也就是當前StandardService的Container對象,這樣就完成了Connector和Container之間的連接
  • connector.setService(this):對外這里綁定了當前的StandardService作為其從屬的service。

 

3.Service和Container是何時連接的?

 

  繼續解析直到Catalina.createStartDigester定義的

digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));

 

  這時候會調用StandardService的setContainer方法:

 

 1 public void setContainer(Container container) {
 2 
 3     Container oldContainer = this.container;
 4     if ((oldContainer != null) && (oldContainer instanceof Engine))
 5         ((Engine) oldContainer).setService(null);
 6     this.container = container;
 7     if ((this.container != null) && (this.container instanceof Engine))
 8         ((Engine) this.container).setService(this);
 9     if (started && (this.container != null) &&
10         (this.container instanceof Lifecycle)) {
11         try {
12             ((Lifecycle) this.container).start();
13         } catch (LifecycleException e) {
14             ;
15         }
16     }
17     synchronized (connectors) {
18         for (int i = 0; i < connectors.length; i++)
19             connectors[i].setContainer(this.container);
20     }
21     if (started && (oldContainer != null) &&
22         (oldContainer instanceof Lifecycle)) {
23         try {
24             ((Lifecycle) oldContainer).stop();
25         } catch (LifecycleException e) {
26             ;
27         }
28     }
29 
30     // Report this property change to interested listeners
31     support.firePropertyChange("container", oldContainer, this.container);
32 
33 }

 

  當執行到((Engine) this.container).setService(this);這里會跳轉到StandardEngine的setService交代了container是如何綁定StandardService的。

 

  並且在代碼

1 synchronized (connectors) {
2     for (int i = 0; i < connectors.length; i++)
3         connectors[i].setContainer(this.container);
4 }

  我們可以看到通過遍歷所有的connector,將其與container綁定。

 

4.Server和Service又是何時連接的?

 

 

  繼續解析直到Catalina.createStartDigerster中的

digester.addSetNext("Server/Service",
                    "addService",
                    "org.apache.catalina.Service");

 

  會調用StandardServer的addService方法

 

 1 public void addService(Service service) {
 2 
 3     service.setServer(this);
 4 
 5     synchronized (services) {
 6         Service results[] = new Service[services.length + 1];
 7         System.arraycopy(services, 0, results, 0, services.length);
 8         results[services.length] = service;
 9         services = results;
10 
11         if (initialized) {
12             try {
13                 service.initialize();
14             } catch (LifecycleException e) {
15                 log.error(e);
16             }
17         }
18 
19         if (started && (service instanceof Lifecycle)) {
20             try {
21                 ((Lifecycle) service).start();
22             } catch (LifecycleException e) {
23                 ;
24             }
25         }
26 
27         // Report this property change to interested listeners
28         support.firePropertyChange("service", null, service);
29     }
30 
31 }

 

  service.setServer(this):該行會調用StandardService中的setServer為service綁定當前的StandardServer對象

 

5.小結

 

 

  當server.xml中的rule解析完畢后,我們起碼明白了:

 

  • Server和Service是如何關聯的;
  • Service和Connector是如何關聯的;
  • Service和Container是如何關聯的;
  • Connector和Container是如何關聯的

 

 

如果您覺得閱讀本文對您有幫助,請點一下“推薦”按鈕,您的“推薦”將是我最大的寫作動力!如果您想持續關注我的文章,請掃描二維碼,關注JackieZheng的微信公眾號,我會將我的文章推送給您,並和您一起分享我日常閱讀過的優質文章。

 




友情贊助

如果你覺得博主的文章對你那么一點小幫助,恰巧你又有想打賞博主的小沖動,那么事不宜遲,趕緊掃一掃,小額地贊助下,攢個奶粉錢,也是讓博主有動力繼續努力,寫出更好的文章^^。

    1. 支付寶                          2. 微信

                      

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM