Tomcat源碼分析 -- StandardContext的啟動

本篇結構:

  • 前言
  • StandContext的啟動過程
  • ContextConfig
  • 總結

一、前言

根據上篇的介紹,我們知道創建StandContext的三種方式:

直接解析server.xml中的Context標簽創建、解析Context文件描述符(位于$CATALINA-BASE/conf/Catalina/localhost目錄下或者META-INF目錄下)、創建默認StandardContext(即沒有Context文件描述符,又沒在server.xml中配置)。

了解了Web應用的創建和部署后,下面再來看看Web應用的初始化和啟動工作。StandardHost和HostConfig只是根據不同情況創建Context對象,具體初始化和啟動工作由組件Context自身完成。

二、StandContext的啟動過程

Tomcat的生命周期機制告訴我們,一個組件的啟動過程應該關注它的start方法,這個start方法是典型的模板方法設計模式。LifecycleBase是所有組件都繼承的抽象類,該類提供了生命周期相關的通用方法,start()方法也可以在LifecycleBase中找到。

觀察start方法,在該方法中定義了組件啟動的應進行的操作,又留出一個抽象方法startInternal()方法供子類實現組件自身的操作。

所以來看StandContext的startInternal()方法。

該方法所做的操作很多很復雜,下面簡單列下,就不深究了。主要是:

1.發布正在啟動的JMX通知,這樣可以通過NotificationListener來監聽Web應用的啟動。

// Send j2ee.state.starting notification
if (this.getObjectName() != null) {
    Notification notification = new Notification("j2ee.state.starting",
            this.getObjectName(), sequenceNumber.getAndIncrement());
    broadcaster.sendNotification(notification);
}

2.啟動當前維護的JNDI資源。(哼,不懂JNDI)

if (namingResources != null) {
    namingResources.start();
}

3.初始化臨時工作目錄,即設置的workDir,默認為$CATALINA-BASE/work/<Engine名稱>/<Host名稱>/<Context名稱>。

postWorkDirectory();

private void postWorkDirectory() {

// Acquire (or calculate) the work directory path
String workDir = getWorkDir();
if (workDir == null || workDir.length() == 0) {

    // Retrieve our parent (normally a host) name
    String hostName = null;
    String engineName = null;
    String hostWorkDir = null;
    Container parentHost = getParent();
    if (parentHost != null) {
        hostName = parentHost.getName();
        if (parentHost instanceof StandardHost) {
            hostWorkDir = ((StandardHost)parentHost).getWorkDir();
        }
        Container parentEngine = parentHost.getParent();
        if (parentEngine != null) {
           engineName = parentEngine.getName();
        }
    }
    if ((hostName == null) || (hostName.length() < 1))
        hostName = "_";
    if ((engineName == null) || (engineName.length() < 1))
        engineName = "_";

    String temp = getBaseName();
    if (temp.startsWith("/"))
        temp = temp.substring(1);
    temp = temp.replace('/', '_');
    temp = temp.replace('\\', '_');
    if (temp.length() < 1)
        temp = ContextName.ROOT_NAME;
    if (hostWorkDir != null ) {
        workDir = hostWorkDir + File.separator + temp;
    } else {
        workDir = "work" + File.separator + engineName +
            File.separator + hostName + File.separator + temp;
    }
    setWorkDir(workDir);
}

4.初始化當前Context使用的WebResouceRoot并啟動。WebResouceRoot維護了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();
}

5.創建Web應用類加載器webappLoader,webappLoader繼承自LifecycleMBeanBase,在其啟動后會去創建Web應用類加載器(ParallelWebappClassLoader)。

if (getLoader() == null) {
    WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
    webappLoader.setDelegate(getDelegate());
    setLoader(webappLoader);
}

同時webappLoader提供了backgroundProcess方法,用于Context后臺處理,當檢測到Web應用的類文件、Jar包發生變化時,重新加載Context。

public void backgroundProcess() {
    if (reloadable && modified()) {
        try {
            Thread.currentThread().setContextClassLoader
                (WebappLoader.class.getClassLoader());
            if (context != null) {
                context.reload();
            }
        } finally {
            if (context != null && context.getLoader() != null) {
                Thread.currentThread().setContextClassLoader
                    (context.getLoader().getClassLoader());
            }
        }
    }
}

6.如果沒有設置Cookie處理器,默認為Rfc6265CookieProcessor。

if (cookieProcessor == null) {
    cookieProcessor = new Rfc6265CookieProcessor();
}

7.設置字符集映射,用于根據Locale獲取字符集編碼。

getCharsetMapper()

public CharsetMapper getCharsetMapper() {

    // Create a mapper the first time it is requested
    if (this.charsetMapper == null) {
        try {
            Class<?> clazz = Class.forName(charsetMapperClass);
            this.charsetMapper = (CharsetMapper) clazz.getConstructor().newInstance();
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            this.charsetMapper = new CharsetMapper();
        }
    }

    return this.charsetMapper;

}

8.web應用的依賴檢測。
9.NamingContextListener注冊。
10.啟動Web應用類加載器,此時真正創建出ParallelWebappClassLoader實例。

Loader loader = getLoader();
if (loader instanceof Lifecycle) {
    ((Lifecycle) loader).start();
}

11.啟動安全組件。

Realm realm = getRealmInternal();
if(null != realm) {
    if (realm instanceof Lifecycle) {
        ((Lifecycle) realm).start();
    }

12.發布CONFIGURE_START_EVENT事件,ContextConfig監聽該事件以完成Servlet的創建。

fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);

13.啟動Context子節點Wrapper。

for (Container child : findChildren()) {
    if (!child.getState().isAvailable()) {
        child.start();
    }
}

14.啟動Context的pipeline。

if (pipeline instanceof Lifecycle) {
    ((Lifecycle) pipeline).start();
}

15.創建會話管理器。

Manager contextManager = null;
Manager manager = getManager();
if (manager == null) {
    if (log.isDebugEnabled()) {
        log.debug(sm.getString("standardContext.cluster.noManager",
                Boolean.valueOf((getCluster() != null)),
                Boolean.valueOf(distributable)));
    }
    if ( (getCluster() != null) && distributable) {
        try {
            contextManager = getCluster().createManager(getName());
        } catch (Exception ex) {
            log.error("standardContext.clusterFail", ex);
            ok = false;
        }
    } else {
        contextManager = new StandardManager();
    }
}

16.將Context的Web資源集合添加到ServletContext。

if (ok)
    getServletContext().setAttribute
        (Globals.RESOURCES_ATTR, getResources());

17.創建實例管理器instanceManager,用于創建對象實例,如Servlet、Filter等。

if (ok ) {
    if (getInstanceManager() == null) {
        javax.naming.Context context = null;
        if (isUseNaming() && getNamingContextListener() != null) {
            context = getNamingContextListener().getEnvContext();
        }
        Map<String, Map<String, String>> injectionMap = buildInjectionMap(
                getIgnoreAnnotations() ? new NamingResourcesImpl(): getNamingResources());
        setInstanceManager(new DefaultInstanceManager(context,
                injectionMap, this, this.getClass().getClassLoader()));
    }
    getServletContext().setAttribute(
            InstanceManager.class.getName(), getInstanceManager());
    InstanceManagerBindings.bind(getLoader().getClassLoader(), getInstanceManager());
}

18.將Jar包掃描器添加到ServletContext。

if (ok) {
    getServletContext().setAttribute(
            JarScanner.class.getName(), getJarScanner());
}

19.合并參數。

private void mergeParameters() {
    Map<String,String> mergedParams = new HashMap<>();

    String names[] = findParameters();
    for (int i = 0; i < names.length; i++) {
        mergedParams.put(names[i], findParameter(names[i]));
    }

    ApplicationParameter params[] = findApplicationParameters();
    for (int i = 0; i < params.length; i++) {
        if (params[i].getOverride()) {
            if (mergedParams.get(params[i].getName()) == null) {
                mergedParams.put(params[i].getName(),
                        params[i].getValue());
            }
        } else {
            mergedParams.put(params[i].getName(), params[i].getValue());
        }
    }

    ServletContext sc = getServletContext();
    for (Map.Entry<String,String> entry : mergedParams.entrySet()) {
        sc.setInitParameter(entry.getKey(), entry.getValue());
    }

}

20.啟動添加到Context的ServletContainerInitializer。
21.實例化應用類監聽器ApplicationListener。

if (ok) {
    if (!listenerStart()) {
        log.error(sm.getString("standardContext.listenerFail"));
        ok = false;
    }
}

22.啟動會話管理器。

Manager manager = getManager();
if (manager instanceof Lifecycle) {
    ((Lifecycle) manager).start();
}

23.實例化FilterConfig、Filter并調用Filter.init()。

if (ok) {
    if (!filterStart()) {
        log.error(sm.getString("standardContext.filterFail"));
        ok = false;
    }
}

24.對于loadOnStartup大于等于0的Wrapper,調用Wrapper.load(),該方法負責實例化Servlet,并調用Servlet.init()進行初始化。

if (ok) {
    if (!loadOnStartup(findChildren())){
        log.error(sm.getString("standardContext.servletFail"));
        ok = false;
    }
}

25.啟動后臺定時處理程序,只有backgroundProcessorDelay>0才啟動,用于監控守護文件的變更。

// Start ContainerBackgroundProcessor thread
super.threadStart();

26.發布正在運行的JMX通知。

// Send j2ee.state.running notification
if (ok && (this.getObjectName() != null)) {
Notification notification =
    new Notification("j2ee.state.running", this.getObjectName(),
                     sequenceNumber.getAndIncrement());
broadcaster.sendNotification(notification);
}

27.釋放資源,如關閉jar文件。

// The WebResources implementation caches references to JAR files. On
// some platforms these references may lock the JAR files. Since web
// application start is likely to have read from lots of JARs, trigger
// a clean-up now.
getResources().gc();

28.設置Context狀態。

if (!ok) {
    setState(LifecycleState.FAILED);
} else {
    setState(LifecycleState.STARTING);
}

StandContext啟動很復雜,涉及很多知識面,我是很模糊的,也不打算深究了。

三、ContextConfig

ContextConfig是創建Context時默認添的一個生命周期監聽器。它監聽6個事件,其中三個和Context啟動關系密切:AFTER_INIT_EVENT、BEFORE_START_EVENT、CONFIGURE_START_EVENT。

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;
}

// Process the event that has occurred
if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
    configureStart();
} 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();
} else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) {
    init();
} else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) {
    destroy();
}

}

3.1、AFTER_INIT_EVENT事件

嚴格說,該事件屬于Context事件初始化階段,主要用于Context屬性的配置工作。

根據前面講的,再來回顧一下Context的創建,有以下來源:

  1. 解析server.xml中的Context元素。
  2. 通過HostConfig部署Web應用時,解析Web應用(或者WAR包)根目錄下的META-INF/context.xml文件。如果不存在,則自動創建一個默認的Context對象,只設置name,path,docBase等幾個屬性。
  3. 通諾HostConfig部署Web應用時,解析$CATALINA-BASE/conf/Catalina/localhost目錄下的Context部署文件描述符創建。

除了Context創建時的屬性配置,Tomcat提供的默認配置也要一并添加到Context實例中,AFTER_INIT_EVENT事件就是要完成這部分工作的。

來看該事件觸發時執行的init()方法:

protected void init() {
    // Called from StandardContext.init()

    Digester contextDigester = createContextDigester();
    contextDigester.getParser();

    if (log.isDebugEnabled()) {
        log.debug(sm.getString("contextConfig.init"));
    }
    context.setConfigured(false);
    ok = true;

    contextConfig(contextDigester);
}

init首先會創建createContextDigester創建解析規則,點進去看可以發現會回到之前講Server解析時提到的ContextRuleSet,只不過這時傳進去的create參數值為false。

不多說,重點來看contextConfig()方法:

protected void contextConfig(Digester digester) {

    String defaultContextXml = null;

    // Open the default context.xml file, if it exists
    if (context instanceof StandardContext) {
        defaultContextXml = ((StandardContext)context).getDefaultContextXml();
    }
    // set the default if we don't have any overrides
    if (defaultContextXml == null) {
        defaultContextXml = Constants.DefaultContextXml;
    }

    if (!context.getOverride()) {
        File defaultContextFile = new File(defaultContextXml);
        if (!defaultContextFile.isAbsolute()) {
            defaultContextFile =
                    new File(context.getCatalinaBase(), defaultContextXml);
        }
        if (defaultContextFile.exists()) {
            try {
                URL defaultContextUrl = defaultContextFile.toURI().toURL();
                processContextConfig(digester, defaultContextUrl);
            } catch (MalformedURLException e) {
                log.error(sm.getString(
                        "contextConfig.badUrl", defaultContextFile), e);
            }
        }

        File hostContextFile = new File(getHostConfigBase(), Constants.HostContextXml);
        if (hostContextFile.exists()) {
            try {
                URL hostContextUrl = hostContextFile.toURI().toURL();
                processContextConfig(digester, hostContextUrl);
            } catch (MalformedURLException e) {
                log.error(sm.getString(
                        "contextConfig.badUrl", hostContextFile), e);
            }
        }
    }
    if (context.getConfigFile() != null) {
        processContextConfig(digester, context.getConfigFile());
    }

}

(1)如果Context的override屬性為false(默認配置):

①如果存在defaultContextXml即conf/context.xml(Catalina容器級默認配置文件),那么解析該文件,更新Context實例屬性。
②如果存在hostContextXml即$CATALINA-BASE/conf/Catalina/localhost/context.xml.default文件(Host級的默認配置),則解析該文件,更新Context實例屬性。

(2)如果context的configFile不為空(即$CATALINA-BASE/conf/Catalina/localhost下的Context部署描述文件或者Web應用根目錄下的META-INF/context.xml文件),那么解析該文件,更新Context實例屬性。

看到這會發現configFile其實被解析了兩遍,在創建Context時會先解析一遍,這里再被解析一遍,為什么?

因為這里會解析conf/context.xml和context.xml.default文件,配置默認屬性,如果之前創建Context時已經配置了某個屬性,而這個屬性又在conf/context.xml和context.xml.default中存在,顯然這時會被覆蓋,想要配置Context級別的屬性不被覆蓋,所以這時再解析一遍。

根據上述,可以得出結論:

Tomcat中Context屬性的優先級為:configFile > $CATALINA-BASE/conf/Catalina/localhost/context.xml.default > conf/context.xml,即Web應用配置優先級最高,Host級別配置次之,Catalina容器級別最低。

3.2、BEFORE_START_EVENT

該事件在Context啟動之前觸發,主要用于更新docBase屬性,解決Web目錄鎖的問題。

protected synchronized void beforeStart() {

    try {
        fixDocBase();
    } catch (IOException e) {
        log.error(sm.getString(
                "contextConfig.fixDocBase", context.getName()), e);
    }

    antiLocking();
}

更新Context的docBase屬性是為了滿足WAR部署的情況。當Web應用為一個WAR壓縮且需要解壓部署(Hot的unpackWAR=true,且Context的unpackWAR=true)時,docBase屬性指向的是解壓后的文件夾目錄,而非WAR包的路徑。

具體過程是在fixDocBase()方法中:

protected void fixDocBase() throws IOException {

    Host host = (Host) context.getParent();
    File appBase = host.getAppBaseFile();

    String docBase = context.getDocBase();
    if (docBase == null) {
        // Trying to guess the docBase according to the path
        String path = context.getPath();
        if (path == null) {
            return;
        }
        ContextName cn = new ContextName(path, context.getWebappVersion());
        docBase = cn.getBaseName();
    }

    File file = new File(docBase);
    if (!file.isAbsolute()) {
        docBase = (new File(appBase, docBase)).getPath();
    } else {
        docBase = file.getCanonicalPath();
    }
    file = new File(docBase);
    String origDocBase = docBase;

    ContextName cn = new ContextName(context.getPath(), context.getWebappVersion());
    String pathName = cn.getBaseName();

    boolean unpackWARs = true;
    if (host instanceof StandardHost) {
        unpackWARs = ((StandardHost) host).isUnpackWARs();
        if (unpackWARs && context instanceof StandardContext) {
            unpackWARs =  ((StandardContext) context).getUnpackWAR();
        }
    }

    boolean docBaseInAppBase = docBase.startsWith(appBase.getPath() + File.separatorChar);

    if (docBase.toLowerCase(Locale.ENGLISH).endsWith(".war") && !file.isDirectory()) {
        URL war = UriUtil.buildJarUrl(new File(docBase));
        if (unpackWARs) {
            docBase = ExpandWar.expand(host, war, pathName);
            file = new File(docBase);
            docBase = file.getCanonicalPath();
            if (context instanceof StandardContext) {
                ((StandardContext) context).setOriginalDocBase(origDocBase);
            }
        } else {
            ExpandWar.validate(host, war, pathName);
        }
    } else {
        File docDir = new File(docBase);
        File warFile = new File(docBase + ".war");
        URL war = null;
        if (warFile.exists() && docBaseInAppBase) {
            war = UriUtil.buildJarUrl(warFile);
        }
        if (docDir.exists()) {
            if (war != null && unpackWARs) {
                // Check if WAR needs to be re-expanded (e.g. if it has
                // changed). Note: HostConfig.deployWar() takes care of
                // ensuring that the correct XML file is used.
                // This will be a NO-OP if the WAR is unchanged.
                ExpandWar.expand(host, war, pathName);
            }
        } else {
            if (war != null) {
                if (unpackWARs) {
                    docBase = ExpandWar.expand(host, war, pathName);
                    file = new File(docBase);
                    docBase = file.getCanonicalPath();
                } else {
                    docBase = warFile.getCanonicalPath();
                    ExpandWar.validate(host, war, pathName);
                }
            }
            if (context instanceof StandardContext) {
                ((StandardContext) context).setOriginalDocBase(origDocBase);
            }
        }
    }

    // Re-calculate now docBase is a canonical path
    docBaseInAppBase = docBase.startsWith(appBase.getPath() + File.separatorChar);

    if (docBaseInAppBase) {
        docBase = docBase.substring(appBase.getPath().length());
        docBase = docBase.replace(File.separatorChar, '/');
        if (docBase.startsWith("/")) {
            docBase = docBase.substring(1);
        }
    } else {
        docBase = docBase.replace(File.separatorChar, '/');
    }

    context.setDocBase(docBase);
}

(1)根據Host的appBase以及Context的docBase計算docBase的絕對路徑。
(2)如果docBase指向WAR包:
需要解壓部署:
①解壓WAR文件;
②將Context的docBase設置為解壓后的路徑。
不需要解壓部署:
只檢測WAR包,不更新docBase。
(3)如果docBase指向目錄:
①如果docBase指向的是有效目錄,且存在與該目錄同名的WAR包,同時需要解壓部署,則重新解壓WAR包;
②如果docBase指向的是無效目錄,即不存在,但是存在與該目錄同名的WAR包,如果需要解壓,則解壓WAR包,更新Context的docBase為解壓路徑。
③如果不需要解壓部署,則只檢測WAR包。

當Context的antiResourceLocking屬性為true時,Tomcat會將當前Web應用目錄復制到臨時文件夾下,以避免對原目錄的資源加鎖。

3.3、CONFIGURE_START_EVENT

Context在啟動之前,會觸發CONFIGURE_START_EVENT事件,ContextConfig通過該事件解析web.xml,創建Wrapper(Servlet)、Filter、ServletContextListener等,完成web容器的初始化。

CONFIGURE_START_EVENT觸發該方法:

protected synchronized void configureStart() {
    // Called from StandardContext.start()

    if (log.isDebugEnabled()) {
        log.debug(sm.getString("contextConfig.start"));
    }

    if (log.isDebugEnabled()) {
        log.debug(sm.getString("contextConfig.xmlSettings",
                context.getName(),
                Boolean.valueOf(context.getXmlValidation()),
                Boolean.valueOf(context.getXmlNamespaceAware())));
    }

    webConfig();

    if (!context.getIgnoreAnnotations()) {
        applicationAnnotationsConfig();
    }
    if (ok) {
        validateSecurityRoles();
    }

    // Configure an authenticator if we need one
    if (ok) {
        authenticatorConfig();
    }

    // Dump the contents of this pipeline if requested
    if (log.isDebugEnabled()) {
        log.debug("Pipeline Configuration:");
        Pipeline pipeline = context.getPipeline();
        Valve valves[] = null;
        if (pipeline != null) {
            valves = pipeline.getValves();
        }
        if (valves != null) {
            for (int i = 0; i < valves.length; i++) {
                log.debug("  " + valves[i].getClass().getName());
            }
        }
        log.debug("======================");
    }

    // Make our application available if no problems were encountered
    if (ok) {
        context.setConfigured(true);
    } else {
        log.error(sm.getString("contextConfig.unavailable"));
        context.setConfigured(false);
    }

}

該事件主要工作內容:

根據配置創建Wrapper(Servlet)、Filter、ServletContextListener等,完成Web容器的初始化。除了解析Web應用目錄下的web.xml外,還包括Tomcat的默認配置、web-fragment.xml、ServletContainerInitializer,以及相關XML文件的排序和合并。

根據Servlet規范,Web應用部署描述可來源于WEB-IN/web.xml、Web應用JAR包中的META-INF/web-fragment.xml和META-INF/services/javax.servlet.ServletContainerInitializer。

除了Servlet規范中提到的部署描述方式,Tomcat還支持默認配置,以簡化Web應用的配置工作。這些默認配置有容器級別的(conf/web.xml)和Host級別(conf/Engine名稱/Host名稱/web.xml.default)。解析中Web應用中的配置優先級最高,Host其次,最后是容器級。

來看webConfig方法:

protected void webConfig() {
/*
 * Anything and everything can override the global and host defaults.
 * This is implemented in two parts
 * - Handle as a web fragment that gets added after everything else so
 *   everything else takes priority
 * - Mark Servlets as overridable so SCI configuration can replace
 *   configuration from the defaults
 */

/*
 * The rules for annotation scanning are not as clear-cut as one might
 * think. Tomcat implements the following process:
 * - As per SRV.1.6.2, Tomcat will scan for annotations regardless of
 *   which Servlet spec version is declared in web.xml. The EG has
 *   confirmed this is the expected behaviour.
 * - As per http://java.net/jira/browse/SERVLET_SPEC-36, if the main
 *   web.xml is marked as metadata-complete, JARs are still processed
 *   for SCIs.
 * - If metadata-complete=true and an absolute ordering is specified,
 *   JARs excluded from the ordering are also excluded from the SCI
 *   processing.
 * - If an SCI has a @HandlesType annotation then all classes (except
 *   those in JARs excluded from an absolute ordering) need to be
 *   scanned to check if they match.
 */
WebXmlParser webXmlParser = new WebXmlParser(context.getXmlNamespaceAware(),
        context.getXmlValidation(), context.getXmlBlockExternal());

Set<WebXml> defaults = new HashSet<>();
defaults.add(getDefaultWebXmlFragment(webXmlParser));

Set<WebXml> tomcatWebXml = new HashSet<>();
tomcatWebXml.add(getTomcatWebXmlFragment(webXmlParser));

WebXml webXml = createWebXml();

// Parse context level web.xml
InputSource contextWebXml = getContextWebXmlSource();
if (!webXmlParser.parseWebXml(contextWebXml, webXml, false)) {
    ok = false;
}

ServletContext sContext = context.getServletContext();

// Ordering is important here

// Step 1. Identify all the JARs packaged with the application and those
// provided by the container. If any of the application JARs have a
// web-fragment.xml it will be parsed at this point. web-fragment.xml
// files are ignored for container provided JARs.
Map<String,WebXml> fragments = processJarsForWebFragments(webXml, webXmlParser);

// Step 2. Order the fragments.
Set<WebXml> orderedFragments = null;
orderedFragments =
        WebXml.orderWebFragments(webXml, fragments, sContext);

// Step 3. Look for ServletContainerInitializer implementations
if (ok) {
    processServletContainerInitializers();
}

if  (!webXml.isMetadataComplete() || typeInitializerMap.size() > 0) {
    // Step 4. Process /WEB-INF/classes for annotations and
    // @HandlesTypes matches
    Map<String,JavaClassCacheEntry> javaClassCache = new HashMap<>();

    if (ok) {
        WebResource[] webResources =
                context.getResources().listResources("/WEB-INF/classes");

        for (WebResource webResource : webResources) {
            // Skip the META-INF directory from any JARs that have been
            // expanded in to WEB-INF/classes (sometimes IDEs do this).
            if ("META-INF".equals(webResource.getName())) {
                continue;
            }
            processAnnotationsWebResource(webResource, webXml,
                    webXml.isMetadataComplete(), javaClassCache);
        }
    }

    // Step 5. Process JARs for annotations and
    // @HandlesTypes matches - only need to process those fragments we
    // are going to use (remember orderedFragments includes any
    // container fragments)
    if (ok) {
        processAnnotations(
                orderedFragments, webXml.isMetadataComplete(), javaClassCache);
    }

    // Cache, if used, is no longer required so clear it
    javaClassCache.clear();
}

if (!webXml.isMetadataComplete()) {
    // Step 6. Merge web-fragment.xml files into the main web.xml
    // file.
    if (ok) {
        ok = webXml.merge(orderedFragments);
    }

    // Step 7a
    // merge tomcat-web.xml
    webXml.merge(tomcatWebXml);

    // Step 7b. Apply global defaults
    // Have to merge defaults before JSP conversion since defaults
    // provide JSP servlet definition.
    webXml.merge(defaults);

    // Step 8. Convert explicitly mentioned jsps to servlets
    if (ok) {
        convertJsps(webXml);
    }

    // Step 9. Apply merged web.xml to Context
    if (ok) {
        configureContext(webXml);
    }
} else {
    webXml.merge(tomcatWebXml);
    webXml.merge(defaults);
    convertJsps(webXml);
    configureContext(webXml);
}

if (context.getLogEffectiveWebXml()) {
    log.info("web.xml:\n" + webXml.toXml());
}

// Always need to look for static resources
// Step 10. Look for static resources packaged in JARs
if (ok) {
    // Spec does not define an order.
    // Use ordered JARs followed by remaining JARs
    Set<WebXml> resourceJars = new LinkedHashSet<>();
    for (WebXml fragment : orderedFragments) {
        resourceJars.add(fragment);
    }
    for (WebXml fragment : fragments.values()) {
        if (!resourceJars.contains(fragment)) {
            resourceJars.add(fragment);
        }
    }
    processResourceJARs(resourceJars);
    // See also StandardContext.resourcesStart() for
    // WEB-INF/classes/META-INF/resources configuration
}

// Step 11. Apply the ServletContainerInitializer config to the
// context
if (ok) {
    for (Map.Entry<ServletContainerInitializer,
            Set<Class<?>>> entry :
                initializerClassMap.entrySet()) {
        if (entry.getValue().isEmpty()) {
            context.addServletContainerInitializer(
                    entry.getKey(), null);
        } else {
            context.addServletContainerInitializer(
                    entry.getKey(), entry.getValue());
        }
    }
}
}

這個方法是Web容器的初始化,過程涉及內容較多,就簡單描述下:

主要是解析默認配置,先解析容器級的配置(conf/web.xml),然后解析Host級別的配置(web.xml.default);

解析Web應用的web.xml,其他的解析結果均會合并到該解析結果中。

掃描素有JAR包,如果有META-INF/web-fragment.xml,解析該分件。

...

最關注的應該是使用web.xml配置Context實例,包括Servlet、Filter、Listener等Servelt規范中支持的組件,這些可以在configureContext()中找到。

四、總結

這篇很模糊不清,很潦草,沒有太大參考價值,更多的還是自己去看源碼吧。

當然看到這,至少希望明白web.xml是在這里解析,Servlet是在這里被包裝成StandardWrapper,Filter是在這里創建的。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容