Tomcat源碼分析——請求原理分析(上)


前言

  談起Tomcat的誕生,最早可以追溯到1995年。近20年來,Tomcat始終是使用最廣泛的Web服務器,由於其使用Java語言開發,所以廣為Java程序員所熟悉。很多人早期的J2EE項目,由程序員自己實現Jsp頁面或者Servlet接受請求,后來借助Struts1、Struts2、Spring等中間件后,實際也是利用Filter或者Servlet處理請求,大家肯定要問了,這些Servlet處理的請求來自哪里?Tomcat作為Web服務器是怎樣將HTTP請求交給Servlet的呢?

  本文就Tomcat對HTTP的請求處理細節進行分析。

  提示:閱讀本文前,請確保首先理解了《Tomcat源碼分析——生命周期管理》中的內容。

Connector的初始化

  根據《Tomcat源碼分析——生命周期管理》一文的內容,我們知道Tomcat中有很多容器,包括Server、Service、Connector等。其中Connector正是與HTTP請求處理相關的容器。Service是Server的子容器,而Connector又是Service的子容器。那么這三個容器的初始化順序為:Server->Service->Connector。Connector的實現分為以下幾種:

  • Http Connector:基於HTTP協議,負責建立HTTP連接。它又分為BIO Http Connector與NIO Http Connector兩種,后者提供非阻塞IO與長連接Comet支持。
  • AJP Connector:基於AJP協議,AJP是專門設計用於Tomcat與HTTP服務器通信定制的協議,能提供較高的通信速度和效率。如與Apache服務器集成時,采用這個協議。
  • APR HTTP Connector:用C實現,通過JNI調用的。主要提升對靜態資源(如HTML、圖片、CSS、JS等)的訪問性能。現在這個庫已獨立出來可用在任何項目中。由於APR性能較前兩類有很大提升,所以目前是Tomcat的默認Connector。

現在我們直接來看Connector的initInternal方法吧,見代碼清單1。

代碼清單1

    @Override
    protected void initInternal() throws LifecycleException {

        super.initInternal();
        
        // Initialize adapter
        adapter = new CoyoteAdapter(this);
        protocolHandler.setAdapter(adapter);

        IntrospectionUtils.setProperty(protocolHandler, "jkHome",
                                       System.getProperty("catalina.base"));

        onameProtocolHandler = register(protocolHandler,
                createObjectNameKeyProperties("ProtocolHandler"));
        
        mapperListener.setDomain(getDomain());

        onameMapper = register(mapperListener,
                createObjectNameKeyProperties("Mapper"));
    }

 

代碼清單1說明了Connector的初始化步驟如下:

步驟一 構造網絡協議處理的CoyoteAdapter

  代碼清單1構造了CoyoteAdapter對象,並且將其設置為ProtocolHandler的Adapter。ProtocolHandler是做什么的呢?Tomcat處理HTTP請求,需要有一個ServerSocket監聽網絡端口來完成任務。接口ProtocolHandler被設計成控制網絡端口監聽組件運行,負責組件的生命周期控制,這個接口實際並沒有定義網絡端口監聽功能的規范,而是用於負責維護組件的生命周期。從ProtocolHandler的名字來看,它應該是網絡協議的處理者,但它實際不負責這個功能,而是將其交給org.apache.coyote.Adapter來完成,這么設計估計是為了方便維護和拓展新功能。Http11Protocol是ProtocolHandler接口的一個實現(是Connector的默認處理協議),被設計用來處理HTTP1.1網絡協議的請求,通過該類可以完成在某個網絡端口上面的監聽,同時以HTTP1.1的協議來解析請求內容,然后將請求傳遞到Connector所寄居的Container容器pipeline流水工作線上處理。此處的ProtocolHandler是何時生成的呢?還記得《TOMCAT源碼分析——SERVER.XML文件的加載與解析》一文中的Digester和Rule嗎?Digester在解析到<Connector>標簽的時候,會執行startElement方法,startElement中會調用Rule的begin(String namespace, String name, Attributes attributes)方法,Connector對應的Rule包括ConnectorCreateRule,ConnectorCreateRule的begin方法的實現見代碼清單2。

代碼清單2

 

    @Override
    public void begin(String namespace, String name, Attributes attributes)
            throws Exception {
        Service svc = (Service)digester.peek();
        Executor ex = null;
        if ( attributes.getValue("executor")!=null ) {
            ex = svc.getExecutor(attributes.getValue("executor"));
        }
        Connector con = new Connector(attributes.getValue("protocol"));
        if ( ex != null )  _setExecutor(con,ex);
        
        digester.push(con);
    }

 

代碼清單2中調用了Connector的構造器,傳遞的參數為屬性protocol。我們知道server.xml中的Connector有兩個:

    <Connector port="8080" protocol="HTTP/1.1" 
               connectionTimeout="20000" 
               redirectPort="8443" />
    <!-- Define an AJP 1.3 Connector on port 8009 -->
    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

我們看看Connector的構造器實現,見代碼清單3。

代碼清單3

    public Connector(String protocol) {
        setProtocol(protocol);
        // Instantiate protocol handler
        try {
            Class<?> clazz = Class.forName(protocolHandlerClassName);
            this.protocolHandler = (ProtocolHandler) clazz.newInstance();
        } catch (Exception e) {
            log.error
                (sm.getString
                 ("coyoteConnector.protocolHandlerInstantiationFailed", e));
        }
    }

setProtocol方法(見代碼清單4)根據protocol參數的不同,調用setProtocolHandlerClassName方法(見代碼清單5)設置protocolHandlerClassName屬性。以HTTP/1.1為例,由於默認情況下Apr不可用,所以protocolHandlerClassName會被設置為org.apache.coyote.http11.Http11Protocol,那么反射生成的protocolHandler就是Http11Protocol實例。Tomcat默認還會配置協議是AJP/1.3的Connector,那么此Connector的protocolHandler就是org.apache.coyote.ajp.AjpProtocol。

 代碼清單4

    /**
     * Set the Coyote protocol which will be used by the connector.
     *
     * @param protocol The Coyote protocol name
     */
    public void setProtocol(String protocol) {

        if (AprLifecycleListener.isAprAvailable()) {
            if ("HTTP/1.1".equals(protocol)) {
                setProtocolHandlerClassName
                    ("org.apache.coyote.http11.Http11AprProtocol");
            } else if ("AJP/1.3".equals(protocol)) {
                setProtocolHandlerClassName
                    ("org.apache.coyote.ajp.AjpAprProtocol");
            } else if (protocol != null) {
                setProtocolHandlerClassName(protocol);
            } else {
                setProtocolHandlerClassName
                    ("org.apache.coyote.http11.Http11AprProtocol");
            }
        } else {
            if ("HTTP/1.1".equals(protocol)) {
                setProtocolHandlerClassName
                    ("org.apache.coyote.http11.Http11Protocol");
            } else if ("AJP/1.3".equals(protocol)) {
                setProtocolHandlerClassName
                    ("org.apache.coyote.ajp.AjpProtocol");
            } else if (protocol != null) {
                setProtocolHandlerClassName(protocol);
            }
        }

    }

代碼清單5

    public void setProtocolHandlerClassName(String protocolHandlerClassName) {

        this.protocolHandlerClassName = protocolHandlerClassName;

    }

 

除此之外,ProtocolHandler還有其它實現,如圖1所示。

 

圖1  ProtocolHandler類繼承體系

圖1中有關ProtocolHandler的實現類都在org.apache.coyote包中 。前面所說的BIO Http Connector實際就是Http11Protocol,NIO Http Connector實際就是Http11NioProtocol,AJP Connector包括AjpProtocol和AjpAprProtocol,APR HTTP Connector包括AjpAprProtocol、Http11AprProtocol,此外還有一個MemoryProtocolHandler(這個是做什么的,目前沒搞清楚,有知道的同學告訴我下啊!)。

步驟二 將ProtocolHandler、MapperListener注冊到JMX

  BIO Http Connector的ProtocolHandler(即Http11Protocol)的JMX注冊名為Catalina:type=ProtocolHandler,port=8080。BIO Http Connector的MapperListener的注冊名為Catalina:type=Mapper,port=8080。AJP Connector的ProtocolHandler(即AjpProtocol)的JMX注冊名為Catalina:type=ProtocolHandler,port=8009。AJP Connector的MapperListener的注冊名為Catalina:type=Mapper,port=8009。有關Tomcat中JMX注冊的內容,請閱讀《TOMCAT源碼分析——生命周期管理》一文。

Connector的啟動

  根據《Tomcat源碼分析——生命周期管理》一文的內容,我們知道Tomcat中有很多容器。ProtocolHandler的初始化稍微有些特殊,Server、Service、Connector這三個容器的初始化順序為:Server->Service->Connector。值得注意的是,ProtocolHandler作為Connector的子容器,其初始化過程並不是由Connector的initInternal方法調用的,而是與啟動過程一道被Connector的startInternal方法所調用。由於本文的目的是分析請求,所以直接從Connector的startInternal方法(見代碼清單6)開始。

代碼清單6

    /**
     * Begin processing requests via this Connector.
     *
     * @exception LifecycleException if a fatal startup error occurs
     */
    @Override
    protected void startInternal() throws LifecycleException {

        setState(LifecycleState.STARTING);

        // Protocol handlers do not follow Lifecycle conventions.
        // protocolHandler.init() needs to wait until the connector.start()
        try {
            protocolHandler.init();
        } catch (Exception e) {
            throw new LifecycleException
                (sm.getString
                 ("coyoteConnector.protocolHandlerInitializationFailed", e));
        }

        try {
            protocolHandler.start();
        } catch (Exception e) {
            String errPrefix = "";
            if(this.service != null) {
                errPrefix += "service.getName(): \"" + this.service.getName() + "\"; ";
            }

            throw new LifecycleException
                (errPrefix + " " + sm.getString
                 ("coyoteConnector.protocolHandlerStartFailed", e));
        }

        // MapperListener doesn't follow Lifecycle conventions either gja
        mapperListener.init();
    }

代碼清單6說明了Connector的startInternal方法的執行順序如下:

  1. 將Connector容器的狀態更改為啟動中(LifecycleState.STARTING);
  2. 初始化ProtocolHandler;
  3. 啟動ProtocolHandler;
  4. 初始化MapperListener。

初始化ProtocolHandler

  簡單起見,我們以Http11Protocol為例剖析ProtocolHandler的init方法,其實現見代碼清單7。

代碼清單7

    @Override
    public void init() throws Exception {
        ((JIoEndpoint)endpoint).setName(getName());
        ((JIoEndpoint)endpoint).setHandler(cHandler);

        // Verify the validity of the configured socket factory
        try {
            if (isSSLEnabled()) {
                sslImplementation =
                    SSLImplementation.getInstance(sslImplementationName);
                socketFactory = sslImplementation.getServerSocketFactory();
                ((JIoEndpoint)endpoint).setServerSocketFactory(socketFactory);
            } else if (socketFactoryName != null) {
                socketFactory = (ServerSocketFactory) Class.forName(socketFactoryName).newInstance();
                ((JIoEndpoint)endpoint).setServerSocketFactory(socketFactory);
            }
        } catch (Exception ex) {
            log.error(sm.getString("http11protocol.socketfactory.initerror"),
                      ex);
            throw ex;
        }

        if (socketFactory!=null) {
            Iterator<String> attE = attributes.keySet().iterator();
            while( attE.hasNext() ) {
                String key = attE.next();
                Object v=attributes.get(key);
                socketFactory.setAttribute(key, v);
            }
        }
        
        try {
            endpoint.init();
        } catch (Exception ex) {
            log.error(sm.getString("http11protocol.endpoint.initerror"), ex);
            throw ex;
        }
        if (log.isInfoEnabled())
            log.info(sm.getString("http11protocol.init", getName()));

    }

從代碼清單7看到,Http11Protocol的初始化步驟如下:

步驟一 設置JIoEndpoint的名稱

  JIoEndpoint的名稱默認為http-8080,這里的JIoEndpoint是在調用Http11Protocol的構造器時創建的,Http11Protocol的構造器中還設置了socket的延遲關閉選項soLingerOn、socket的延時關閉秒數soLingerTime、socket連接超時時間soTimeout、提高socket性能的tcpNoDelay等選項,見代碼清單8。

代碼清單8

    public Http11Protocol() {
        endpoint = new JIoEndpoint();
        setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
        setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
        //setServerSoTimeout(Constants.DEFAULT_SERVER_SOCKET_TIMEOUT);
        setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
        
    }

步驟二 設置JIoEndpoint的Handler

  JIoEndpoint的handler被設置為cHandler,此cHandler的定義如下:

    protected Http11ConnectionHandler cHandler = new Http11ConnectionHandler(this);

步驟三 配置ServerSocketFactory

從代碼清單7看到,生成ServerSocketFactory有三種方式:
  • 如果在server.xml中配置Connector時指定了SSLEnabled="true"的屬性,那么創建帶有SSL(Secure Sockets Layer 安全套接層)的ServerSocketFactory;

  • 如果Http11Protocol指定了socketFactoryName,則使用socketFactoryName反射生成ServerSocketFactory實例;
  • 如果不滿足以上2個條件,那么JIoEndpoint的init方法(見代碼清單9)將創建ServerSocketFactory。當SSLEnabled="true"時,JIoEndpoint的init方法還會給ServerSocketFactory設置一些SSL相關的屬性。最后使用此ServerSocketFactory創建serverSocket。此外,acceptorThreadCount屬性用於指定接受連接的線程數,可以通過給Connector設置acceptorThreadCount屬性進行調整,默認值為1。

代碼清單9

    @Override
    public void init()
        throws Exception {

        if (initialized)
            return;
        
        // Initialize thread count defaults for acceptor
        if (acceptorThreadCount == 0) {
            acceptorThreadCount = 1;
        }
        if (serverSocketFactory == null) {
            serverSocketFactory = ServerSocketFactory.getDefault();
        }
        if (isSSLEnabled()) {
            serverSocketFactory.setAttribute(SSL_ATTR_ALGORITHM,
                    getAlgorithm());
            serverSocketFactory.setAttribute(SSL_ATTR_CLIENT_AUTH,
                    getClientAuth());
            serverSocketFactory.setAttribute(SSL_ATTR_KEYSTORE_FILE,
                    getKeystoreFile());
            serverSocketFactory.setAttribute(SSL_ATTR_KEYSTORE_PASS,
                    getKeystorePass());
            serverSocketFactory.setAttribute(SSL_ATTR_KEYSTORE_TYPE,
                    getKeystoreType());
            serverSocketFactory.setAttribute(SSL_ATTR_KEYSTORE_PROVIDER,
                    getKeystoreProvider());
            serverSocketFactory.setAttribute(SSL_ATTR_SSL_PROTOCOL,
                    getSslProtocol());
            serverSocketFactory.setAttribute(SSL_ATTR_CIPHERS,
                    getCiphers());
            serverSocketFactory.setAttribute(SSL_ATTR_KEY_ALIAS,
                    getKeyAlias());
            serverSocketFactory.setAttribute(SSL_ATTR_KEY_PASS,
                    getKeyPass());
            serverSocketFactory.setAttribute(SSL_ATTR_TRUSTSTORE_FILE,
                    getTruststoreFile());
            serverSocketFactory.setAttribute(SSL_ATTR_TRUSTSTORE_PASS,
                    getTruststorePass());
            serverSocketFactory.setAttribute(SSL_ATTR_TRUSTSTORE_TYPE,
                    getTruststoreType());
            serverSocketFactory.setAttribute(SSL_ATTR_TRUSTSTORE_PROVIDER,
                    getTruststoreProvider());
            serverSocketFactory.setAttribute(SSL_ATTR_TRUSTSTORE_ALGORITHM,
                    getTruststoreAlgorithm());
            serverSocketFactory.setAttribute(SSL_ATTR_CRL_FILE,
                    getCrlFile());
            serverSocketFactory.setAttribute(SSL_ATTR_TRUST_MAX_CERT_LENGTH,
                    getTrustMaxCertLength());
            serverSocketFactory.setAttribute(SSL_ATTR_SESSION_CACHE_SIZE,
                    getSessionCacheSize());
            serverSocketFactory.setAttribute(SSL_ATTR_SESSION_TIMEOUT,
                    getSessionTimeout());
            serverSocketFactory.setAttribute(SSL_ATTR_ALLOW_UNSAFE_RENEG,
                    getAllowUnsafeLegacyRenegotiation());
        }

        if (serverSocket == null) {
            try {
                if (getAddress() == null) {
                    serverSocket = serverSocketFactory.createSocket(getPort(), getBacklog());
                } else {
                    serverSocket = serverSocketFactory.createSocket(getPort(), getBacklog(), getAddress());
                }
            } catch (BindException orig) {
                String msg;
                if (getAddress() == null)
                    msg = orig.getMessage() + " <null>:" + getPort();
                else
                    msg = orig.getMessage() + " " +
                            getAddress().toString() + ":" + getPort();
                BindException be = new BindException(msg);
                be.initCause(orig);
                throw be;
            }
        }
        //if( serverTimeout >= 0 )
        //    serverSocket.setSoTimeout( serverTimeout );
        
        initialized = true;
        
    }

啟動ProtocolHandler

  我們繼續以Http11Protocol為例,剖析ProtocolHandler的start方法,其實現見代碼清單10。

代碼清單10

    @Override
    public void start() throws Exception {
        if (this.domain != null) {
            try {
                tpOname = new ObjectName
                    (domain + ":" + "type=ThreadPool,name=" + getName());
                Registry.getRegistry(null, null)
                    .registerComponent(endpoint, tpOname, null );
            } catch (Exception e) {
                log.error("Can't register endpoint");
            }
            rgOname=new ObjectName
                (domain + ":type=GlobalRequestProcessor,name=" + getName());
            Registry.getRegistry(null, null).registerComponent
                ( cHandler.global, rgOname, null );
        }

        try {
            endpoint.start();
        } catch (Exception ex) {
            log.error(sm.getString("http11protocol.endpoint.starterror"), ex);
            throw ex;
        }
        if (log.isInfoEnabled())
            log.info(sm.getString("http11protocol.start", getName()));
    }

 從代碼清單10可以知道JIoEndpoint以Catalina:type=ThreadPool,name=http-8080注冊到JMX,cHandler.global(Http11ConnectionHandler的對象屬性,類型為RequestGroupInfo)以Catalina:type=GlobalRequestProcessor,name=http-8080注冊到JMX。最后調用JIoEndpoint的start方法(見代碼清單11)接受請求的創建線程池並創建一定數量的接收請求線程。

代碼清單11

    @Override
    public void start() throws Exception {
        // Initialize socket if not done before
        if (!initialized) {
            init();
        }
        if (!running) {
            running = true;
            paused = false;

            // Create worker collection
            if (getExecutor() == null) {
                createExecutor();
            }

            // Start acceptor threads
            for (int i = 0; i < acceptorThreadCount; i++) {
                Thread acceptorThread = new Thread(new Acceptor(), getName() + "-Acceptor-" + i);
                acceptorThread.setPriority(threadPriority);
                acceptorThread.setDaemon(getDaemon());
                acceptorThread.start();
            }
        }
    }

從代碼清單11看出JIoEndpoint的start方法的執行步驟如下:

步驟一 對JIoEndpoint做初始化檢查

  這一步實際就是判斷是否已經初始化(即initialized是否為true),如果沒有初始化則需要調用JIoEndpoint的init方法進行初始化。

步驟二 創建線程池與任務隊列

  如果JIoEndpoint尚未處於運行中(即running等於true),才會創建線程池和任務隊列。如果尚未創建線程池(即調用getExecutor方法等於null),則需要調用createExecutor方法(見代碼清單12)創建線程池和任務隊列TaskQueue

代碼清單12

    public void createExecutor() {
        internalExecutor = true;
        TaskQueue taskqueue = new TaskQueue();
        TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());
        executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);
        taskqueue.setParent( (ThreadPoolExecutor) executor);
    }

  

步驟三 創建接收請線程

  如果JIoEndpoint尚未處於運行中(即running等於true),才會創建接收請求線程。從代碼清單11可以看出接收請求線程的數量主要由acceptorThreadCount控制,代碼清單9已經告訴我們acceptorThreadCount的默認值為1,但是我們可以通過給Connector增加acceptorThreadCount屬性來修改接收請求線程的數量。這些接收請求線程的主要工作由Acceptor完成,Acceptor的實質是一個Runnable,見代碼清單13。

代碼清單13

    /**
     * Server socket acceptor thread.
     */
    protected class Acceptor implements Runnable {


        /**
         * The background thread that listens for incoming TCP/IP connections and
         * hands them off to an appropriate processor. gja
         */
        public void run() {

            // Loop until we receive a shutdown command
            while (running) {

                // Loop if endpoint is paused
                while (paused) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        // Ignore
                    }
                }

                // Accept the next incoming connection from the server socket
                try {
                    Socket socket = serverSocketFactory.acceptSocket(serverSocket);
                    serverSocketFactory.initSocket(socket);
                    // Hand this socket off to an appropriate processor
                    if (!processSocket(socket)) {
                        // Close socket right away
                        try {
                            socket.close();
                        } catch (IOException e) {
                            // Ignore
                        }
                    }
                }catch ( IOException x ) {
                    if ( running ) log.error(sm.getString("endpoint.accept.fail"), x);
                } catch (Throwable t) {
                    log.error(sm.getString("endpoint.accept.fail"), t);
                }

                // The processor will recycle itself when it finishes

            }

        }

    }

 

初始化MapperListener

  MapperListener的init方法用於初始化,見代碼清單14。

代碼清單14

 

    /**
     * Initialize associated mapper.
     */
    public void init() {

        // Find any components that have already been initialized since the
        // MBean listener won't be notified as those components will have
        // already registered their MBeans jiaan
        findDefaultHost();
        
        Engine engine = (Engine) connector.getService().getContainer();
        engine.addContainerListener(this);
        
        Container[] conHosts = engine.findChildren();
        for (Container conHost : conHosts) {
            Host host = (Host) conHost;
            if (!LifecycleState.NEW.equals(host.getState())) {
                host.addLifecycleListener(this);
                // Registering the host will register the context and wrappers
                registerHost(host);
            }
        }
    }

 

從代碼清單14看到MapperListener的初始化步驟如下:

步驟一 查找默認Host

  StandardService的子容器包括:StandardEngine、Connector和Executor。MapperListener本身會持有Connector,所以可以通過各個容器的父子關系,找到Connector的同級容器StandardEngine。StandardHost是StandardEngine的子容器,Engine和Host的默認配置如下:

    <Engine name="Catalina" defaultHost="localhost">

      <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
             resourceName="UserDatabase"/>


      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">

        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"  
               prefix="localhost_access_log." suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" resolveHosts="false"/>

      </Host>
    </Engine>

findDefaultHost方法(見代碼清單15)可以獲取上面配置中的默認Host,Engine元素的defaultHost屬性值必須要與配置的某個Host元素的name屬性值相同。如果defaultHost的屬性值配置無誤,則會添加為MapperListener的Mapper對象屬性的defaultHostName。

代碼清單15

    private void findDefaultHost() {

        Engine engine = (Engine) connector.getService().getContainer();
        String defaultHost = engine.getDefaultHost();

        boolean found = false;

        if (defaultHost != null && defaultHost.length() >0) {
            Container[] containers = engine.findChildren();
            
            for (Container container : containers) {
                Host host = (Host) container;
                if (defaultHost.equalsIgnoreCase(host.getName())) {
                    found = true;
                    break;
                }
                
                String[] aliases = host.findAliases();
                for (String alias : aliases) {
                    if (defaultHost.equalsIgnoreCase(alias)) {
                        found = true;
                        break;
                    }
                }
            }
        }

        if(found) {
            mapper.setDefaultHostName(defaultHost);
        } else {
            log.warn(sm.getString("mapperListener.unknownDefaultHost",
                    defaultHost));
        }
    }

 

 步驟二 將Host及其子容器Context,Context的子容器Wrapper注冊到MapperListener的Mapper對象

  Mapper的數據結構,見代碼清單16。

                          代碼清單16
    /**
     * Array containing the virtual hosts definitions.
     */
    protected Host[] hosts = new Host[0];


    /**
     * Default host name.
     */
    protected String defaultHostName = null;

    /**
     * Context associated with this wrapper, used for wrapper mapping.
     */
    protected Context context = new Context();

    protected static abstract class MapElement {

        public String name = null;
        public Object object = null;

    }

    protected static final class Host
        extends MapElement {

        public ContextList contextList = null;

    }

    protected static final class ContextList {

        public Context[] contexts = new Context[0];
        public int nesting = 0;

    }

    protected static final class Context
        extends MapElement {

        public String path = null;
        public String[] welcomeResources = new String[0];
        public javax.naming.Context resources = null;
        public Wrapper defaultWrapper = null;
        public Wrapper[] exactWrappers = new Wrapper[0];
        public Wrapper[] wildcardWrappers = new Wrapper[0];
        public Wrapper[] extensionWrappers = new Wrapper[0];
        public int nesting = 0;

    }

    protected static class Wrapper
        extends MapElement {

        public String path = null;
        public boolean jspWildCard = false;
    }
 
        

根據代碼清單16,我們知道Mapper中維護着一個Host數組,每個Host中有一個ContextList,這個ContextList中維護着一個Context數組。每個Context維護着一個defaultWrapper,三個Wrapper數組(exactWrappers、wildcardWrappers、extensionWrappers)。下面對Host、Context及Wrapper進行功能上的介紹:

  • Host:代表一個虛擬主機,各Host的name不能相同,appBase代表各虛擬主機的應用發布位置;
  • Context:代表一個應用,Context可以根據應用的/WEB-INF/web.xml文件中定義的servlet來處理請求。一個Host下可以有多個Context;
  • Wrapper: 代表一個Servlet或者jsp,它負責管理一個 Servlet,包括的 Servlet 的裝載、初始化、執行以及資源回收。

 以我本地為例,注冊到Mapper中的Host及其子容器如圖2所示。

圖2  注冊到Mapper中的Host及其Context子容器

 圖2說明Host內一共5個Context,由於我的Tomcat是從svn拉下來的,所以webapps目錄下的.svn文件夾也是一個Context,除了這個天外來客,我將其它與請求有關的容器整理后用圖3來展示。

圖3  我本地的Host、Context及Wrapper

  至此,Tomcat中為請求處理的准備工作已經完成。有關請求的處理過程請繼續閱讀《Tomcat源碼分析——請求原理分析(中)》一文。


如需轉載,請標明本文作者及出處——作者:jiaan.gja,本文原創首發:博客園,原文鏈接:http://www.cnblogs.com/jiaan-geng/p/4875249.html


免責聲明!

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



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