1. 程式人生 > >Tomcat原始碼解析:Catalina原始碼解析

Tomcat原始碼解析:Catalina原始碼解析

1.Catalina

    對於Tomcat來說,Catalina是其核心元件,所有基於JSP/Servlet的Java Web應用均需要依託Servlet容器執行並對外提供服務。

    4.0版本後,Tomcat完全重新設計了其Servlet容器的架構,新版本的Servlet容器被命名為Catalina。

    Catalina包含了前面講到的所有容器元件。它通過鬆耦合的方式繼承Coyote,以完成按照請求協議進行資料讀寫。同時還包括我們的啟動入口、Shell程式等

 

    1)Tomcat分層示意圖

    2)Digester

        Catalina使用Digester解析XML配置檔案並建立應用伺服器

        Digester是一款將XML轉換為java物件的事件驅動型工具,是對SAX的高層次封裝。通過流讀取XML檔案,當識別出XML節點後便執行特定的動作,或者建立java物件,或者執行物件的某個方法。

        Digester路徑為:tomcat-util-scan.jar包下 org.apache.tomcat.util.digester包路徑下

 

    3)Server

        以下是有關Server的結構

 

2.tomcat8.0.52 有關於Catalina原始碼解析

    1)%TOMCAT_HOME%/bin/startup.bat

        作為tomcat的啟動檔案,可以看到,其直接呼叫了catalina.bat檔案

set _EXECJAVA=%_RUNJAVA%
set MAINCLASS=org.apache.catalina.startup.Bootstrap
set ACTION=start
set SECURITY_POLICY_FILE=
set DEBUG_OPTS=
set JPDA=

    可以看到主類為Bootstrap,執行方法為start

 

    2)Bootstrap

    反射的絕佳用例

    /**
     * Start the Catalina daemon.
     */
    public void start()
        throws Exception {
        if( catalinaDaemon==null ) init();
        Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
        method.invoke(catalinaDaemon, (Object [])null);
    }

    其主要作用就是啟動一個catalinaDaemon,在其init()方法中可以看到

        Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
        Object startupInstance = startupClass.getConstructor().newInstance();
...
        catalinaDaemon = startupInstance;

    3)Catalina.start()

/**
     * Start a new server instance.
     */
    public void start() {

        if (getServer() == null) {
            load();
        }

        if (getServer() == null) {
            log.fatal("Cannot start server. Server instance is not configured.");
            return;
        }

        long t1 = System.nanoTime();

        // Start the new server
        try {
            getServer().start();
...
/**
     * Start a new server instance.
     */
    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();
...
    /**
     * Create and configure the Digester we will be using for startup.
     */
    protected Digester createStartDigester() {
        long t1=System.currentTimeMillis();
        // Initialize the digester
        Digester digester = new Digester();
        digester.setValidating(false);
        digester.setRulesValidation(true);
        HashMap<Class<?>, List<String>> fakeAttributes = new HashMap<>();
        ArrayList<String> attrs = new ArrayList<>();
        attrs.add("className");
        fakeAttributes.put(Object.class, attrs);
        digester.setFakeAttributes(fakeAttributes);
        digester.setUseContextClassLoader(true);

        // Configure the actions we will be using
        digester.addObjectCreate("Server",
                                 "org.apache.catalina.core.StandardServer",
                                 "className");
        digester.addSetProperties("Server");
...

    在初始化的時候根據server.xml配置來載入Server、Service等物件

 

3.Digester

    Catalina使用Digester來解析XML(server.xml)配置檔案並建立應用伺服器。

    Digester是一款用於將XML轉換為java物件的事件驅動型工具。是對SAX的高層次封裝。

    現在Digester已經移到了Apache Commons專案,我們可以通過以下來獲取對Digester的使用

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-digester3 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-digester3</artifactId>
    <version>3.2</version>
</dependency>

    具體的使用可參考:Apache Commons Digester 一 (基礎內容、核心API) 這篇文章

 

4.Web應用載入

    Web應用載入屬於Server啟動的核心處理過程。

    Catalina對web應用的載入主要由StandardHost、HostConfig、StandardContext、ContextConfig、StandardWrapper這五個類來完成。時序圖如下:

5.StandardServer

    Catalina在解析server.xml時呼叫Catalina.createStartDigester()方法,具體內容如下:

// 1.關於Server的解析,建立        
digester.addObjectCreate("Server",
"org.apache.catalina.core.StandardServer",
"className");
digester.addSetProperties("Server");

// 2.關於Service的解析建立
digester.addObjectCreate("Server/Service",
                         "org.apache.catalina.core.StandardService",
                         "className");
digester.addSetProperties("Server/Service");

// 呼叫其Server.addService方法,將該service新增到Server中
digester.addSetNext("Server/Service",
                    "addService",
                    "org.apache.catalina.Service");

    預設會載入StandardServer類。

    並且會建立Service實現類,並新增到Server中。

 

    1)StandardServer.addService()如下所示

public void addService(Service service) {

    service.setServer(this);

    synchronized (servicesLock) {
        Service results[] = new Service[services.length + 1];
        System.arraycopy(services, 0, results, 0, services.length);
        results[services.length] = service;
        services = results;

        if (getState().isAvailable()) {
            try {
                // 啟動service
                service.start();
            } catch (LifecycleException e) {
                // Ignore
            }
        }

        // Report this property change to interested listeners
        support.firePropertyChange("service", null, service);
    }
}

    2)StandardService.start()

    預設實現在LifecycleBase類中,每個元件都使用到了這個方法,各自實現其startInternal()方法即可

 

6.StandardService

    Catalina在解析server.xml時呼叫Catalina.createStartDigester()方法,有關於Service具體內容如下:

// 1.建立Service
digester.addObjectCreate("Server/Service",
                         "org.apache.catalina.core.StandardService",
                         "className");

// 2.建立Listener 並新增到Service
digester.addObjectCreate("Server/Service/Listener",
                         null, // MUST be specified in the element
                         "className");
digester.addSetProperties("Server/Service/Listener");
digester.addSetNext("Server/Service/Listener",
                    "addLifecycleListener",
                    "org.apache.catalina.LifecycleListener");

// 3.建立Executor,並新增到Service
digester.addObjectCreate("Server/Service/Executor",
                         "org.apache.catalina.core.StandardThreadExecutor",
                         "className");
digester.addSetProperties("Server/Service/Executor");

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

// 4.建立Connector並新增到Service
digester.addRule("Server/Service/Connector",
                 new ConnectorCreateRule());
digester.addRule("Server/Service/Connector",
                 new SetAllPropertiesRule(new String[]{"executor", "sslImplementationName"}));
digester.addSetNext("Server/Service/Connector",
                    "addConnector",
                    "org.apache.catalina.connector.Connector");

    1)StandardService.startInternal()

protected void startInternal() throws LifecycleException {

    if(log.isInfoEnabled())
        log.info(sm.getString("standardService.start.name", this.name));
    setState(LifecycleState.STARTING);

    // 1.啟動engine
    if (engine != null) {
        synchronized (engine) {
            engine.start();
        }
    }

    // 2.啟動executor
    synchronized (executors) {
        for (Executor executor: executors) {
            executor.start();
        }
    }

    mapperListener.start();

    // 3.啟動connector
    synchronized (connectorsLock) {
        for (Connector connector: connectors) {
            try {
                // If it has already failed, don't try and start it
                if (connector.getState() != LifecycleState.FAILED) {
                    connector.start();
                }
            } catch (Exception e) {
                log.error(sm.getString(
                    "standardService.connector.startFailed",
                    connector), e);
            }
        }
    }
}

7.StandardEngine

    同理,我們通過分析以上的方式來分析StandardEngine

 

8.StandardHost

    StandardHost載入web應用(即StandardContext)的入口有兩個:

    * Catalina在構造Server例項時,如果在server.xml中Host元素存在Context子元素,那麼Context元素就會作為Host容器的子容器新增到Host例項中

    * HostConfig自動掃描部署目錄,建立Context例項並啟動。這是大多數Web應用的載入方式

 

    1)StandardHost.startInternal()啟動虛擬主機

protected synchronized void startInternal() throws LifecycleException {

    // Set error report valve
    String errorValve = getErrorReportValveClass();
    if ((errorValve != null) && (!errorValve.equals(""))) {
        try {
            boolean found = false;
            Valve[] valves = getPipeline().getValves();
            for (Valve valve : valves) {
                if (errorValve.equals(valve.getClass().getName())) {
                    found = true;
                    break;
                }
            }
            if(!found) {
                Valve valve =
                    (Valve) Class.forName(errorValve).newInstance();
                getPipeline().addValve(valve);
            }
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            log.error(sm.getString(
                "standardHost.invalidErrorReportValveClass",
                errorValve), t);
        }
    }
    super.startInternal();
}

9.HostConfig

    HostConfig類註釋如下:

/**
 * Startup event listener for a <b>Host</b> that configures the properties
 * of that Host, and the associated defined contexts.
 *
 * @author Craig R. McClanahan
 * @author Remy Maucherat
 */
public class HostConfig implements LifecycleListener {

    主要用於監聽Host的事件,實現了lifecycleEvent方法,具體內容如下:

public void lifecycleEvent(LifecycleEvent event) {

    // Identify the host we are associated with
    try {
        host = (Host) event.getLifecycle();
        if (host instanceof StandardHost) {
            setCopyXML(((StandardHost) host).isCopyXML());
            setDeployXML(((StandardHost) host).isDeployXML());
            setUnpackWARs(((StandardHost) host).isUnpackWARs());
            setContextClass(((StandardHost) host).getContextClass());
        }
    } catch (ClassCastException e) {
        log.error(sm.getString("hostConfig.cce", event.getLifecycle()), e);
        return;
    }

    // 定時掃描Web應用的變更,並進行重新載入
    if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
        check();
    } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
        beforeStart();
        
    // 該事件在Host啟動時觸發,host啟動後掃描web應用包進行部署
    } else if (event.getType().equals(Lifecycle.START_EVENT)) {
        start();
    } else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
        stop();
    }
}

    既然說是Host的監聽器,那麼我們就從原始碼的角度來分析一下HostConfig是如何監控的

    1)尋找lifecycleEvent方法的被呼叫場景

    發現只有LifecycleBase.fireLifecycleEvent有呼叫

 

    2)尋找LifecycleBase.fireLifecycleEvent方法被呼叫場景

        分析:既然是Host的監聽器,那就應該是StandardHost的某個方法呼叫的,我們來看下StandardHost的類結構圖

        通過上面兩個圖結合來看,可以確定在以下兩個方法中

        而backgroundProcess方法中監聽的是Lifecycle.PERIODIC_EVENT事件,所以我們最後就確定LifecycleBase.setStateInternal方法

        LifecycleBase.setState方法有關於呼叫LifecycleBase.setStateInternal()

        

    3)尋找LifecycleBase.setState方法被呼叫場景

        我們直接去其子類ContainerBase中看setState被呼叫的場景

        發現有兩個方法startInternal、stopInternal呼叫了setState方法,這兩個方法就很熟悉了,startInternal是抽象類LifecycleBase的抽象方法,需要子類實現的

        startInternal方法在StandardHost中也有實現,內容如下:

protected synchronized void startInternal() throws LifecycleException {

    // Set error report valve
    String errorValve = getErrorReportValveClass();
    if ((errorValve != null) && (!errorValve.equals(""))) {
        try {
            boolean found = false;
            Valve[] valves = getPipeline().getValves();
            for (Valve valve : valves) {
                if (errorValve.equals(valve.getClass().getName())) {
                    found = true;
                    break;
                }
            }
            if(!found) {
                Valve valve =
                    (Valve) Class.forName(errorValve).newInstance();
                getPipeline().addValve(valve);
            }
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            log.error(sm.getString(
                "standardHost.invalidErrorReportValveClass",
                errorValve), t);
        }
    }
    // 呼叫ContainerBase.startInternal()
    super.startInternal();
}

    

    4)那麼HostConfig的監聽器是什麼時候關聯到StandardHost的呢?

    Catalina.createStartDigester()方法是解析server.xml時被呼叫的,裡面有一句

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

// addRuleSet方法
public void addRuleSet(RuleSet ruleSet) {

    String oldNamespaceURI = getRuleNamespaceURI();
    String newNamespaceURI = ruleSet.getNamespaceURI();
    if (log.isDebugEnabled()) {
        if (newNamespaceURI == null) {
            log.debug("addRuleSet() with no namespace URI");
        } else {
            log.debug("addRuleSet() with namespace URI " + newNamespaceURI);
        }
    }
    setRuleNamespaceURI(newNamespaceURI);
    // 主要是這句,會呼叫HostRuleSet.addRuleInstances方法
    ruleSet.addRuleInstances(this);
    setRuleNamespaceURI(oldNamespaceURI);
}

// 我們來看下這個HostRuleSet.addRuleInstances方法
public void addRuleInstances(Digester digester) {

    // 1.建立StandardHost
    digester.addObjectCreate(prefix + "Host",
                             "org.apache.catalina.core.StandardHost",
                             "className");
    digester.addSetProperties(prefix + "Host");
    digester.addRule(prefix + "Host",
                     new CopyParentClassLoaderRule());
    // 2.建立並新增HostConfig
    digester.addRule(prefix + "Host",
                     new LifecycleListenerRule
                     ("org.apache.catalina.startup.HostConfig",
                      "hostConfigClass"));
    digester.addSetNext(prefix + "Host",
                        "addChild",
                        "org.apache.catalina.Container");

    digester.addCallMethod(prefix + "Host/Alias",
                           "addAlias", 0);

    //Cluster configuration start
    digester.addObjectCreate(prefix + "Host/Cluster",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties(prefix + "Host/Cluster");
    digester.addSetNext(prefix + "Host/Cluster",
                        "setCluster",
                        "org.apache.catalina.Cluster");
    //Cluster configuration end

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

    digester.addRuleSet(new RealmRuleSet(prefix + "Host/"));

    digester.addObjectCreate(prefix + "Host/Valve",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties(prefix + "Host/Valve");
    digester.addSetNext(prefix + "Host/Valve",
                        "addValve",
                        "org.apache.catalina.Valve");
}

    所以:HostConfig是在使用Digester解析server.xml時就關聯到StandardHost的

 

    至此,我們就看到一條完整的呼叫鏈:

    StandardHost.startInternal() -> ContainerBase.startInternal() -> LifecycleBase.setState() -> LifecycleBase.setStateInternal() -> LifecycleBase.fireLifecycleEvent() -> HostConfig.lifecycleEvent()

 

    

10.StandardContext    

    StandardContext包含了具體的web應用初始化及啟動工作,該部分工作由元件Context完成

    Tomcat提供的ServletContext實現類為ApplicationContext,該類僅為Tomcat伺服器使用;Web應用使用的是其門面類ApplicationContextFacade。

    

    1)StandardContext的啟動過程(被StandardHost呼叫start方法)

        實際呼叫為LifecycleBase.start(),真正StandardContext的實現方法為startInternal(),

        具體內容有點長,可具體參考原始碼,部落格參考:https://blog.csdn.net/w1992wishes/article/details/79499867 

 

        重點過程包括:

        * 初始化當前Context使用的WebResourceRoot並啟動,WebResourceRoot維護了web應用所有的資源集合(Class檔案、jar包以及其他資源),主要用於類載入和按照路徑查詢資原始檔

// Add missing components as necessary
if (getResources() == null) {   // (1) Required by Loader
    if (log.isDebugEnabled())
        log.debug("Configuring default Resources");

    try {
        setResources(new StandardRoot(this));
    } catch (IllegalArgumentException e) {
        log.error(sm.getString("standardContext.resourcesInit"), e);
        ok = false;
    }
}
if (ok) {
    resourcesStart();
}

        * 建立web應用類載入器(WebappLoader),並啟動

        * 建立會話管理器

        * 建立例項管理器(InstanceManager),用於建立物件例項,如Servlet、Filter等

        * 例項化應用監聽器,分為事件監聽器(ServletContextAttributeListener、ServletRequestAttributeListener、ServletRequestListener)和生命週期監聽器(HttPSessionListener、ServletContextListener)

// Configure and call application event listeners
if (ok) {
    if (!listenerStart()) {
        log.error(sm.getString("standardContext.listenerFail"));
        ok = false;
    }
}

        * 對於loadOnStartup>=0的Wrapper,呼叫wrapper.load(),該方法負責例項化Servlet,並呼叫Servlet.init進行初始化

        注意:這個只是針對於啟動就載入的Servlet

// Load and initialize all "load on startup" servlets
if (ok) {
    if (!loadOnStartup(findChildren())){
        log.error(sm.getString("standardContext.servletFail"));
        ok = false;
    }
}

11.ContextConfig

    上述StandardContext的啟動過程,並不包含web.xml中Servlet、請求對映、Filter等相關配置

    這部分的工作是由ContextConfig負責的。

    至於ContextConfig是如何關聯StandardContext的,可以參考上面 那麼HostConfig的監聽器是什麼時候關聯到StandardHost的呢?

 

    1)ContextConfig.lifecycleEvent(用於監聽各種事件)

public void lifecycleEvent(LifecycleEvent event) {

    // Identify the context we are associated with
    try {
        context = (Context) event.getLifecycle();
    } catch (ClassCastException e) {
        log.error(sm.getString("contextConfig.cce", event.getLifecycle()), e);
        return;
    }

    // 建立Wrapper(重要階段)
    if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
        configureStart();
        
    // 在Context啟動之前觸發,用於更新Context的docBase屬性和解決Web目錄鎖的問題
    } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
        beforeStart();
    } else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
        // Restore docBase for management tools
        if (originalDocBase != null) {
            context.setDocBase(originalDocBase);
        }
    } else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) {
        configureStop();
     
    // Context初始化階段
    } else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) {
        init();
    } else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) {
        destroy();
    }

}

    2)詳細介紹CONFIGURE_START_EVENT事件觸發的configureStart()方法

    ContextConfig.configureStart()方法根據配置建立Wrapper(Servlet)、Filter、ServletContextListener,具體步驟如下:

protected synchronized void configureStart() {
    ...
    // 重要方法   
    webConfig();
    ...
}

// webConfig()
/**
     * Scan the web.xml files that apply to the web application and merge them
     * using the rules defined in the spec. For the global web.xml files,
     * where there is duplicate configuration, the most specific level wins. ie
     * an application's web.xml takes precedence over the host level or global
     * web.xml file.
     */
protected void webConfig() {
    ...
    // Step 8. Convert explicitly mentioned jsps to servlets
    if (ok) {
        convertJsps(webXml);
    }

    // Step 9. Apply merged web.xml to Context
    if (ok) {
        configureContext(webXml);
    }
}

// configureContext()解析web.xml並載入Servlet/Filter
private void configureContext(WebXml webxml) {
    // 載入Servlet
    for (ServletDef servlet : webxml.getServlets().values()) {
        // 建立Wrapper,具體在3)中繼續分析
        Wrapper wrapper = context.createWrapper();

        if (servlet.getLoadOnStartup() != null) {
            wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
        }
        if (servlet.getEnabled() != null) {
            wrapper.setEnabled(servlet.getEnabled().booleanValue());
        }
        wrapper.setName(servlet.getServletName());
        Map<String,String> params = servlet.getParameterMap();
        for (Entry<String, String> entry : params.entrySet()) {
            wrapper.addInitParameter(entry.getKey(), entry.getValue());
        }
        wrapper.setRunAs(servlet.getRunAs());
        ...
        wrapper.setOverridable(servlet.isOverridable());
        context.addChild(wrapper);
    }
    
    // 載入servletMapping
    for (Entry<String, String> entry :
         webxml.getServletMappings().entrySet()) {
        context.addServletMappingDecoded(entry.getKey(), entry.getValue());
    }
    ...
}

    總結:完成2)步驟的時候,ServletWrapper與Context的關係就建立起來了

 

    3)論Wrapper與Servlet的關係

// StandardContext.createWrapper()
public Wrapper createWrapper() {

    Wrapper wrapper = null;
    if (wrapperClass != null) {
        try {
            wrapper = (Wrapper) wrapperClass.newInstance();
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            log.error("createWrapper", t);
            return (null);
        }
    } else {
        wrapper = new StandardWrapper();
    }
    ...
}

12.StandardWrapper

    StandardWrapper具體維護了Servlet例項

    StandardWrapper與Servlet的關係,下面來看下StandardWrapper原始碼:

    1)StandardWrapper是Servlet的包裝類,下面是一些主要成員變數

public class StandardWrapper extends ContainerBase
    implements ServletConfig, Wrapper, NotificationEmitter {
    /**
     * The (single) possibly uninitialized instance of this servlet.
     */
    protected volatile Servlet instance = null;


    /**
     * Flag that indicates if this instance has been initialized
     */
    protected volatile boolean instanceInitialized = false;


    /**
     * The load-on-startup order value (negative value means load on
     * first call) for this servlet.
     */
    protected int loadOnStartup = -1;


    /**
     * Mappings associated with the wrapper.
     */
    protected final ArrayList<String> mappings = new ArrayList<>();


    /**
     * The initialization parameters for this servlet, keyed by
     * parameter name.
     */
    protected HashMap<String, String> parameters = new HashMap<>();


    /**
     * The security role references for this servlet, keyed by role name
     * used in the servlet.  The corresponding value is the role name of
     * the web application itself.
     */
    protected HashMap<String, String> references = new HashMap<>();

    2)StandardWrapper.load()

    如果該servlet配置load-on-startup>=0,則需要呼叫其load方法,完成Servlet的載入(如果沒有配置,則等到使用者首次呼叫Servlet的時候,才會載入)

    原始碼如下:

public synchronized void load() throws ServletException {
    // 載入Servlet,建立instance例項,並呼叫Servlet.init方法
    instance = loadServlet();

    if (!instanceInitialized) {
        initServlet(instance);
    }

    // jsp處理
    if (isJspServlet) {
        StringBuilder oname = new StringBuilder(getDomain());

        oname.append(":type=JspMonitor");

        oname.append(getWebModuleKeyProperties());

        oname.append(",name=");
        oname.append(getName());

        oname.append(getJ2EEKeyProperties());

        try {
            jspMonitorON = new ObjectName(oname.toString());
            Registry.getRegistry(null, null)
                .registerComponent(instance, jspMonitorON, null);
        } catch( Exception ex ) {
            log.info("Error registering JSP monitoring with jmx " +
                     instance);
        }
    }
}

// loadServlet()
public synchronized Servlet loadServlet() throws ServletException {

    // Nothing to do if we already have an instance or an instance pool
    if (!singleThreadModel && (instance != null))
        return instance;
    ...

    Servlet servlet;
    try {
        ...
        InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
        try {
            // 1.建立對應的servletClass物件
            servlet = (Servlet) instanceManager.newInstance(servletClass);
        } catch (ClassCastException e) {
           ...
        } 
        ...
        // 2.呼叫servlet.init方法
        initServlet(servlet);

        // 3.觸發load監聽
        fireContainerEvent("load", this);

        loadTime=System.currentTimeMillis() -t1;
    } finally {
        ...
    }
    return servlet;

}

 

參考:Tomcat架構解析(劉光瑞)