Tomcat 源碼分析(三)-(一)-Context 的構建,發佈加載WEB應用事件-解析加載web.xml

Tomcat 源碼分析(三)-WEB加載原理(一)

簡要說明

之前的學習,是從Tomcat的啓動腳本,到 server.xml解析,然後分析了一個請求的訪問數據流轉。

到此,只是分析了流轉用到的組件的閥之類的方法,並沒有分析說明組件的創建,尤其是Context這個組件,對應的就是我們的WEB應用。

然後,這份的學習就是:接着請求流轉的具體到WEB應用的分析

可以和數據流轉整合一起理解請求到我們代碼中的流程

  • 包括WEB的加載,解析web.xml,記載監聽攔截器服務方法等

這裏纔是我最想知道的東西-容器是怎麼個加載我的WEB應用的 (:3[」_]

一、Context 的構建,發佈加載WEB應用事件

介紹

Tomcat 中啓動的時候,默認就會有好幾個線程,其中mainhttp-bio-8080-Acceptor-0http-bio-8080-AsyncTimeoutajp-bio-8009-Acceptor-0ajp-bio-8009-AsyncTimeout,線程已經說明過了【是做爲監聽和守護進程的存在】。然後,這個還有一個線程:ContainerBackgroundProcessor[StandardEngine[Catalina]] ,這個線程直接就是服務的線程,接下來分析的就是它了。。。

線程的創建

說明從這個線程開始。

Tomcat7 中默認的容器( StandardEngine、StandardHost、StandardContext、StandardWrapper )這些,都會繼承一個父類: org.apache.catalina.core.ContainerBase。在Tomcat組件啓動的時候,會調用自己內部的startInternal方法,而這個方法中,會調用父類的方法:super.startInternal();比如StandardEngine的方法:

protected synchronized void startInternal() throws LifecycleException {
    if(log.isInfoEnabled())
        log.info( "Starting Servlet Engine: " + ServerInfo.getServerInfo());
    super.startInternal();   //←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←
}

這裏會調用父類的方法,也就是:ContainerBase.startInternal():

//啓動該組件並實現需求    
protected synchronized void startInternal() throws LifecycleException {

        // Start our subordinate components, if any
        Loader loader = getLoaderInternal();
        if ((loader != null) && (loader instanceof Lifecycle))
            ((Lifecycle) loader).start();
        logger = null;
        getLogger();
        Manager manager = getManagerInternal();
        if ((manager != null) && (manager instanceof Lifecycle))
            ((Lifecycle) manager).start();
        Cluster cluster = getClusterInternal();
        ........

        // Start our child containers, if any
         .........
        // Start the Valves in our pipeline (including the basic), if any
    	//這裏啓動了管道中的閥 包括基礎閥
        if (pipeline instanceof Lifecycle) {
            ((Lifecycle) pipeline).start();
        }
    //↓↓↓↓這裏發佈了一個STARTING的事件,這個的作用後面點會說明↓↓↓↓↓
        setState(LifecycleState.STARTING);  
        // ↓↓↓↓↓↓↓↓↓↓↓ Start our thread  這裏開始了一個線程 ****↓↓↓↓↓
        threadStart();
    }

然後,看看這個啓動線程的方法:threadStart();

/**
 * Start the background thread that will periodically check for
 * session timeouts. 啓動將定期檢查會話超時的後臺線程。
 */
protected void threadStart() {
    if (thread != null)
        return;
    if (backgroundProcessorDelay <= 0)
        return;

    threadDone = false;
    String threadName = "ContainerBackgroundProcessor[" + toString() + "]";
    thread = new Thread(new ContainerBackgroundProcessor(), threadName);
    thread.setDaemon(true);
    thread.start();
}

說明一下,threadName 這裏就是現成的名字,而toString()方法 :以org.apache.catalina.core.StandardEngine爲例:

public String toString() {
 StringBuilder sb = new StringBuilder("StandardEngine[");
 sb.append(getName());
 sb.append("]");
 return (sb.toString());
}

所以,這個線程的啓動以及名字已經找到出處了。

這個threadStar()t在所有組件初始化的時候多會調用到,而這裏只會存在一個線程,

分析一下:

/* 默認值 : */ thread = null;backgroundProcessorDelay= -1

然而在幾個的組件方法中,只有StandardEngine的構造方法對backgroundProcessorDelay進行修改:

public StandardEngine() {
    super();
    pipeline.setBasic(new StandardEngineValve());
    try {
        setJvmRoute(System.getProperty("jvmRoute"));
	。。。。。
    // By default, the engine will hold the reloading thread
    backgroundProcessorDelay = 10;  //←←←←←←←←←←←←←←←←←←←←←←
}

SO,在Tomcat 解析xml到一個Engine節點的時候就會產生一個後臺處理線程。

線程的處理事務

這裏接下來,就是來分析一下這個線程具體是幹什麼的了。

new Thread(new ContainerBackgroundProcessor(), threadName);

這裏可以看到,將會啓動的線程是內部類ContainerBackgroundProcessor.run();

/** 私有線程類,用於在固定延遲後調用此容器及其子級的BackgroundProcess方法 
 * Private thread class to invoke the backgroundProcess method
 * of this container and its children after a fixed delay.
 */
protected class ContainerBackgroundProcessor implements Runnable {

    @Override
    public void run() {
        Throwable t = null;
        String unexpectedDeathMessage = sm.getString(
                "containerBase.backgroundProcess.unexpectedThreadDeath",
                Thread.currentThread().getName());
        try {
            while (!threadDone) {
                try {
                    Thread.sleep(backgroundProcessorDelay * 1000L);
                } catch (InterruptedException e) {
                    // Ignore
                }
                if (!threadDone) {
                    Container parent = (Container) getMappingObject();
                    ClassLoader cl =
                        Thread.currentThread().getContextClassLoader();
                    if (parent.getLoader() != null) {
                        cl = parent.getLoader().getClassLoader();
                    }
                    processChildren(parent, cl);   //這裏就是爲了定時的調用這個方法
                }
            }
        } catch (RuntimeException e) {
          .........
        }
    }

    protected void processChildren(Container container, ClassLoader cl) {
        try {
            if (container.getLoader() != null) {
                Thread.currentThread().setContextClassLoader
                    (container.getLoader().getClassLoader());
            }
            //調用本身容器的backgroundProcess方法
            container.backgroundProcess();
        } catch (Throwable t) {
         ......
        }
        //取出並調用容器的所以子容器的processChildren 方法
        Container[] children = container.findChildren();
        for (int i = 0; i < children.length; i++) {
            if (children[i].getBackgroundProcessorDelay() <= 0) {
                processChildren(children[i], cl);
            }
        }
    }
}

總結來說:歸結起來這個線程的實現就是定期通過遞歸的方式調用當前容器及其所有子容器的 backgroundProcess 方法。

**解析下這個backgroundProcess方法:**而這個 backgroundProcess 方法在 ContainerBase 內部已經給出了實現:

@Override
public void backgroundProcess() {

    if (!getState().isAvailable())
        return;

    Cluster cluster = getClusterInternal();
    if (cluster != null) {
        try {
            cluster.backgroundProcess();
        } catch (Exception e) {
            log.warn(sm.getString("containerBase.backgroundProcess.cluster", cluster), e);
        }
    }
    //刪減後的↓↓↓↓↓↓↓ 逐個調用內部相關的backgroundProcess()方法
    Loader loader = getLoaderInternal();
            loader.backgroundProcess();
    Manager manager = getManagerInternal();
            manager.backgroundProcess();
    Realm realm = getRealmInternal();
            realm.backgroundProcess();
	//調用管道內左右閥的backgroundProcess()方法
    Valve current = pipeline.getFirst();
    while (current != null) {
            current.backgroundProcess();
        current = current.getNext();
    }
    //最後這裏註冊了一個Lifecycle.PERIODIC_EVENT事件
    fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null);
}

說明一下大概的功能:逐個調用內部相關的backgroundProcess()方法,包括管道內左右閥的backgroundProcess()方法.

加載WEB應用事件分析

這裏接着上面,默認ContainerBackgroundProcessor[StandardEngine[Catalina]]的進程會定期執行各個容器組件的相關的backgroundProcess方法,也會定期發佈PERIODIC_EVENT事件,所有的組件都會收到的。

其中,Host組件也會收到PERIODIC_EVENT事件【劃重點】來分析這個Host組件對事件的處理

事件處理監聽創建

這個處理的方法是在啓動解析Host的時候【也就是org.apache.catalina.startup.Catalina類中】:

digester.addRuleSet(new HostRuleSet("Server/Service/Engine/")); //這一行代碼 爲嵌套元素添加規則集

這裏的HostRuleSet中的規則集addRuleInstances 方法:

public void addRuleInstances(Digester digester) {

    digester.addObjectCreate(prefix + "Host",
                             "org.apache.catalina.core.StandardHost",
                             "className");
    digester.addSetProperties(prefix + "Host");
    digester.addRule(prefix + "Host",
                     new CopyParentClassLoaderRule());
    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
	.........
}

這裏可以看到,在Host的節點中,會添加HostConfig作爲StandardHost對象的監聽器。

這裏響應的事件的處理方法就是在這個HostConfig的處理時間的lifecycleEvent 方法中。

分析事件的處理-加載web應用

處理事件的方法是在HostConfigd的lifecycleRent方法中:

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) {
       ......
    } 
    // Process the event that has occurred ↓↓↓↓要處理的事件就是這一個↓↓
    if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
        check();
    } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
        beforeStart();
    } else if (event.getType().equals(Lifecycle.START_EVENT)) {
        start();
    } else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
        stop();
    }
}

這裏處理事件,如果是PERIODIC_EVENT事件則check()方法,START_EVENT事件則start()

而這兩個方法的最後都有一個:

 deployApps();

這個就是加載web應用的東東了,來看一下具體的實現代碼:

/** 爲在我們的“應用程序根目錄”中找到的任何目錄或war文件部署應用程序。
 * Deploy applications for any directories or WAR files that are found
 * in our "application root" directory.
 */
protected void deployApps() {
    File appBase = appBase();
    File configBase = configBase();
    String[] filteredAppPaths = filterAppPaths(appBase.list());
    // Deploy XML descriptors from configBase
    deployDescriptors(configBase, configBase.list());
    // Deploy WARs
    deployWARs(appBase, filteredAppPaths);
    // Deploy expanded folders
    deployDirectories(appBase, filteredAppPaths);
}

這裏就是各種方式來發布WEB應用的地方了;【加載WEB應用的地方了】

說明一下,默認情況下組件啓動的時候會發佈一個Lifecycle.START_EVENT事件(在org.apache.catalina.core.ContainerBase類的 startInternal 方法倒數第二行),所以默認啓動時將會執行 HostConfig 的 start 方法,在該方法的也會調用deployApps()這個加載應用的方法。

if (host.getDeployOnStartup())
 deployApps();

默認配置 host.getDeployOnStartup() 返回 true ,這樣容器就會在啓動的時候直接加載相應的 web 應用。

當然,如果在 server.xml 中 Host 節點的 deployOnStartup 屬性設置爲 false ,則容器啓動時不會加載應用,啓動完之後不能立即提供 web 應用的服務。但因爲有上面提到的後臺處理線程在運行,會定期執行 HostConfig 的 check 方法;所以web 應用也會被加載。。

2019–04-30


二、解析加載web.xml

加載web.xml這個就開始一直想了解的Tomcat對WEB應用的加載方式了。

獲取到war包,啓動線程處理

最開始的入口位置:HostConfig.lifecycleEvent.deployApps()

org.apache.catalina.startup.HostConfig的 lifecycleEvent 方法中,對Tomcat啓動或之後加載web應用進行了實現。就是**deployApps()**這個方法。

//爲在我們的“應用程序根目錄”中找到的任何目錄或war文件部署應用程序。
protected void deployApps() {
    File appBase = appBase();
    File configBase = configBase();
    String[] filteredAppPaths = filterAppPaths(appBase.list());
    // Deploy XML descriptors from configBase   xml文件描述
    deployDescriptors(configBase, configBase.list());
    // Deploy WARs  	WAR包
    deployWARs(appBase, filteredAppPaths);
    // Deploy expanded folders  文件目錄
    deployDirectories(appBase, filteredAppPaths);
}

可以看到這裏部署應用有三種方式:XML 文件描述符、WAR 包、文件目錄。三種方式部署的總體流程很相似,都是一個 web 應用分配一個線程來處理,這裏統一放到與 Host 內部的線程池對象中( startStopExecutor ),所以有時會看到在默認配置下 Tomcat 啓動後可能有一個叫-startStop-的線程還會運行一段時間才結束。

這三個的加載方法有很大一部分是一樣的,我只想看WAR包加載的,就是deployWARs這個方法:

方法的代碼超級多,都不是啥看得懂的 ,刪減到只有重點就行:

protected void deployWARs(File appBase, String[] files) {
    ExecutorService es = host.getStartStopExecutor();
    List<Future<?>> results = new ArrayList<Future<?>>();

    for (int i = 0; i < files.length; i++) {
      ..........
        File war = new File(appBase, files[i]);
        if (files[i].toLowerCase(Locale.ENGLISH).endsWith(".war") &&
                war.isFile() && !invalidWars.contains(files[i]) ) {
			//這裏獲取到web應用的名稱↓↓↓↓↓↓↓
            ContextName cn = new ContextName(files[i], true);
......
    		//下面這個纔是要看的,這裏使用了ExecutorService線程池進行執行DeployWar類
            results.add(es.submit(new DeployWar(this, cn, war)));
        }
    }
  ......
}

然後看一下這個DeployWar類的相關代碼:

private static class DeployWar implements Runnable {

    private HostConfig config;
    private ContextName cn;
    private File war;

    public DeployWar(HostConfig config, ContextName cn, File war) {
        this.config = config;
        this.cn = cn;
        this.war = war;
    }

    @Override
    public void run() {
        config.deployWAR(cn, war);
    }
}

代碼相當的簡單,這裏就是使用線程池執行config.deployWAR(cn, war);方法。

這個裏面就是創建Context的實現的地方了,接下來要分析的東東。

創建應用對象Context

deployWAR(ContextName cn, File war)方法中,會有這麼一段代碼:

host.addChild(context);

這裏,會創建Context 對象,並且綁定到Host中去了。

然而,到這裏,容器還是無法響應瀏覽器的請求,這需要WEB應用中具體的Servlet來處理的,中間還有相應的過濾器( filter )、監聽器( listener )等。

這些配置的信息,是在 web 應用的WEB-INF\web.xml文件的。

servlet3 中已經支持將這些配置信息放到 Java 文件的註解中,但萬變不離其宗,總歸要在 web 應用的某個地方說明,並在容器啓動時加載,這樣才能真正提供 web 服務,響應請求

構造處理的監聽器

在上面↑提到的三種部署應用的實現代碼中,都有下面的這個共通的代碼:

Class<?> clazz = Class.forName(host.getConfigClass());
LifecycleListener listener =
    (LifecycleListener) clazz.newInstance();
context.addLifecycleListener(listener);
........
host.addChild(context);

這裏的host.getConfigClass()獲取的就是StandardHost類的變量configClass,就是:org.apache.catalina.startup.ContextConfig。這裏會在構建的context中添加新的監聽器。

開始分析

先看StandardHost 的 addChild 方法的:

public void addChild(Container child) {
    child.addLifecycleListener(new MemoryLeakTrackingListener());
    if (!(child instanceof Context))
        throw new IllegalArgumentException
            (sm.getString("standardHost.notContext"));
    super.addChild(child);  //←←←←←←←←←←←←←看這一個←←←←←←←←←←←←←←←←←
}

父類的addChild方法:

public void addChild(Container child) {
    if (Globals.IS_SECURITY_ENABLED) {
        PrivilegedAction<Void> dp =
            new PrivilegedAddChild(child);
        AccessController.doPrivileged(dp);
    } else {
        addChildInternal(child);//←←←←←←←←←←←看這裏←←←←←←←←←←←←←←←←←←←
    }
}

addChildInternal(child) 方法:

private void addChildInternal(Container child) {

    if( log.isDebugEnabled() )
        log.debug("Add child " + child + " " + this);
    synchronized(children) {
        if (children.get(child.getName()) != null)
            throw new IllegalArgumentException("addChild:  Child name '" +
                                               child.getName() +
                                               "' is not unique");
        child.setParent(this);  // May throw IAE
        children.put(child.getName(), child);
    }
    try {
        if ((getState().isAvailable() ||
                LifecycleState.STARTING_PREP.equals(getState())) &&
                startChildren) {
            child.start();   //←←←←←←←←←看這裏←←←←←←←←←←←
        }
    } catch (LifecycleException e) {	.......
    } finally {
        fireContainerEvent(ADD_CHILD_EVENT, child);
    }
}

最終會調用子容器start 方法,這裏就是StandardContext 的 start 方法。

即給 host 對象添加子容器時將會調用子容器的 start 方法,調用 StandardContext 的 start 方法最終會調用org.apache.catalina.core.StandardContext類的 startInternal 方法。並且發佈一系列的事件:包括:

BEFORE_INIT_EVENTAFTER_INIT_EVENTBEFORE_START_EVENTCONFIGURE_START_EVENTSTART_EVENTAFTER_START_EVENT

而在上面的代碼(執行host.addChild(context);之前),在Context中註冊了一個監聽器:org.apache.catalina.startup.ContextConfig。看一下這個監聽器對start發佈的一系列事件的處理。

監聽器對加載事件的處理

直接來看一下監聽器的處理方法,也就是org.apache.catalina.startup.ContextConfig.lifecycleEvent:

public void lifecycleEvent(LifecycleEvent event) {
    try {
        context = (Context) event.getLifecycle();
    } catch (ClassCastException e) ......   }
    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();
    }
}

這裏按照發布時間的順序,會響應調用對應的處理方法,如上面標註的順序。

這裏來看一下第三個處理事件,configureStart()方法:

public class ContextConfig implements LifecycleListener {
protected synchronized void configureStart() {
    // Called from StandardContext.start()
    ......
    webConfig();  //這個方法就是解析web.xml的實現了 *****看這裏*****

    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()) && (context instanceof ContainerBase)) {
        log.debug("Pipeline Configuration:");
        Pipeline pipeline = ((ContainerBase) 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].getInfo());
            }
        }
    }
    // Make our application available if no problems were encountered
  .......

}

解析web.xml

這裏的處理方法中,直接就調動了webConfig(),這個方法是具體的處理web.xml的實現方法:

/**
*掃描應用於Web應用程序的web.xml文件併合並它們
*使用全局web.xml文件規範中定義的規則,
*如果存在重複配置,則最特定的級別將獲勝。工業工程
*應用程序的web.xml優先於主機級別或全局級別
* Web.xml文件。
*/
protected void webConfig() {

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

    WebXml webXml = createWebXml();

    // Parse context level web.xml
    InputSource contextWebXml = getContextWebXmlSource();
    parseWebXml(contextWebXml, webXml, false);

    ServletContext sContext = context.getServletContext();

    // Ordering is important here

    // Step 1. Identify all the JARs packaged with the application
    // If the JARs have a web-fragment.xml it will be parsed at this
    // point.
    Map<String,WebXml> fragments = processJarsForWebFragments(webXml);

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

    // Step 3. Look for ServletContainerInitializer implementations 
    if (ok) {   //看servletcontainerinitializer的實現等
        processServletContainerInitializers();
    }

    if  (!webXml.isMetadataComplete() || typeInitializerMap.size() > 0) {
        // Step 4. Process /WEB-INF/classes for annotations 註釋的進程/WEB-INF/類
        if (ok) {
            // Hack required by Eclipse's "serve modules without
            // publishing" feature since this backs WEB-INF/classes by
            // multiple locations rather than one.
            NamingEnumeration<Binding> listBindings = null;
            try {
                try {
                    listBindings = context.getResources().listBindings(
                            "/WEB-INF/classes");
                } catch (NameNotFoundException ignore) {
                    // Safe to ignore
                }
                while (listBindings != null &&
                        listBindings.hasMoreElements()) {
                    Binding binding = listBindings.nextElement();
                    if (binding.getObject() instanceof FileDirContext) {
                        File webInfClassDir = new File(
                                ((FileDirContext) binding.getObject()).getDocBase());
                        processAnnotationsFile(webInfClassDir, webXml,
                                webXml.isMetadataComplete());
                    } else if ("META-INF".equals(binding.getName())) {
                        // Skip the META-INF directory from any JARs that have been
                        // expanded in to WEB-INF/classes (sometimes IDEs do this).
                    } else {
                        String resource =
                                "/WEB-INF/classes/" + binding.getName();
                        try {
                            URL url = sContext.getResource(resource);
                            processAnnotationsUrl(url, webXml,
                                    webXml.isMetadataComplete());
                        } catch (MalformedURLException e) {
                            log.error(sm.getString(
                                    "contextConfig.webinfClassesUrl",
                                    resource), e);
                        }
                    }
                }
            } catch (NamingException e) {
                log.error(sm.getString(
                        "contextConfig.webinfClassesUrl",
                        "/WEB-INF/classes"), e);
            }
        }

        // Step 5. Process JARs for annotations - only need to process 爲註釋處理JAR-只需要處理
        // those fragments we are going to use
        if (ok) {
            processAnnotations(
                    orderedFragments, webXml.isMetadataComplete());
        }

        // 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. 將web-fragment.xml文件合併到主web.xml中
        if (ok) {
            ok = webXml.merge(orderedFragments);
        }

        // Step 7. Apply global defaults 應用全局默認值
        // Have to merge defaults before JSP conversion since defaults  
        //在JSP轉換之前必須合併默認值,因爲默認值
        // provide JSP servlet definition.提供JSP servlet定義
        webXml.merge(defaults);

        // Step 8. Convert explicitly mentioned jsps to servlets 將顯式提到的JSP轉換爲servlet
        if (ok) {
            convertJsps(webXml);
        }

        // Step 9. Apply merged web.xml to Context
        if (ok) {
            webXml.configureContext(context);  //++++++看下這裏+++++
        }
    } else {
        webXml.merge(defaults);
        convertJsps(webXml);
        webXml.configureContext(context);   //++++++看下這裏+++++
    }

    // Step 9a. Make the merged web.xml available to other
    // components, specifically Jasper, to save those components
    // from having to re-generate it.
    // TODO Use a ServletContainerInitializer for Jasper
    String mergedWebXml = webXml.toXml();
    sContext.setAttribute(
           org.apache.tomcat.util.scan.Constants.MERGED_WEB_XML,
           mergedWebXml);
    if (context.getLogEffectiveWebXml()) {
        log.info("web.xml:\n" + mergedWebXml);
    }

    // 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<WebXml>();
        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());
            }
        }
    }
}

對以上代碼的總結:

概括起來包括合併 Tomcat 全局 web.xml 、當前應用中的 web.xml 、web-fragment.xml 和 web 應用的註解中的配置信息,並將解析出的各種配置信息(如 servlet 配置、filter 配置等)關聯到 Context 對象中。

配置信息取到之後對配置信息中的對象進行實例化,我找到的地方是在這一段代碼:

processAnnotationsFile(webInfClassDir, webXml,
                      webXml.isMetadataComplete());

然後調用:processAnnotationsStream(fis, fragment, handlesTypesOnly);;

processAnnotationsStream的方法實現:

protected void processAnnotationsStream(InputStream is, WebXml fragment,
  boolean handlesTypesOnly)
  throws ClassFormatException, IOException {

ClassParser parser = new ClassParser(is);
JavaClass clazz = parser.parse();
checkHandlesTypes(clazz);

if (handlesTypesOnly) {
  return;
}

String className = clazz.getClassName();

AnnotationEntry[] annotationsEntries = clazz.getAnnotationEntries();
if (annotationsEntries != null) {
  for (AnnotationEntry ae : annotationsEntries) {
      String type = ae.getAnnotationType();
      if ("Ljavax/servlet/annotation/WebServlet;".equals(type)) {
          processAnnotationWebServlet(className, ae, fragment);
      }else if ("Ljavax/servlet/annotation/WebFilter;".equals(type)) {
          processAnnotationWebFilter(className, ae, fragment);
      }else if ("Ljavax/servlet/annotation/WebListener;".equals(type)) {
          fragment.addListener(className);
      } else {
          // Unknown annotation - ignore
      }
  }
}
}

這裏最後的三個方法processAnnotationWebServlet,processAnnotationWebFilter,fragment.addListener,會創建的實例對象添加到WebXml對象的成員變量中。

//在processAnnotationWebServlet方法中:
fragment.addServlet(servletDef);
//在processAnnotationWebFilter方法中:
fragment.addFilter(filterDef);

對,這裏只是把配置的Filter,Listener,Servlet 的配置信息保存下來以便之後關聯到Context中去【這裏只是封裝類,並沒有構造出web 應用中的 Servlet、Listener、Filter 的實例】

這個在後面一個章節會有具體的說明。

web配置信息實例化關聯Context

這裏特別說明一下,獲取到所有的配置信息時候,關聯到Context對象的處理方法

代碼爲114行的,以及具體的實現方法

webXml.configureContext(context);

超級長的代碼【所以都刪了 反正不會認真看╮(╯_╰)╭】:

/**
 * Configure a {@link Context} using the stored web.xml representation.
 * 使用存儲的web.xml表示形式配置@link上下文。
 * @param context   The context to be configured
 * @param 上下文要配置的上下文*/
public void configureContext(Context context) {
    // As far as possible, process in alphabetical order so it is easy to
    // check everything is present
    // Some validation depends on correct public ID
    context.setPublicId(publicId);

    // Everything else in order
    context.setEffectiveMajorVersion(getMajorVersion());
    context.setEffectiveMinorVersion(getMinorVersion());

    for (Entry<String, String> entry : contextParams.entrySet()) {
        context.addParameter(entry.getKey(), entry.getValue());
    }
    context.setDisplayName(displayName);
    context.setDistributable(distributable);
    for (ContextLocalEjb ejbLocalRef : ejbLocalRefs.values()) {
        context.getNamingResources().addLocalEjb(ejbLocalRef);
    }
    for (ContextEjb ejbRef : ejbRefs.values()) {
        context.getNamingResources().addEjb(ejbRef);
    }
    for (ContextEnvironment environment : envEntries.values()) {
        context.getNamingResources().addEnvironment(environment);
    }
    for (ErrorPage errorPage : errorPages.values()) {
        context.addErrorPage(errorPage);
    }
    for (FilterDef filter : filters.values()) {
        if (filter.getAsyncSupported() == null) {
            filter.setAsyncSupported("false");
        }
        context.addFilterDef(filter);
    }
   ......

這裏就沒什麼好看的了,就是用了各種 set、add 方法,從而將 web.xml 中的各種配置信息與表示一個 web 應用的 context 對象關聯起來。

Context 就是一個WEB應用,裏面的東東就是web應用的各種東東了。

注意:這裏只是關聯上,並沒有具體的實現的實例化哦。。。。


2019-05-08 小杭 ε=(´ο`*)))唉 學習好累

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章