Tomcat源碼解析:啓動


啓動階段也開始於Bootstrap的main方法裏的start,我們都知道,初始化時只是到容器Engine,所以start時主要對Engine的子容器以下這一部分進行操作。

而start方法反射調用了Catalina的start方法。

daemon.start();

public void start() throws Exception {
    if (catalinaDaemon == null) {
        init();
    }

    Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
    method.invoke(catalinaDaemon, (Object [])null);
}

1.Catalina

開啓start

public void start() {

    if (getServer() == null) {
        load();
    }
	//...
    long t1 = System.nanoTime();

    // Start the new server
    try {
        //執行start
        getServer().start();
    } catch (LifecycleException e) {
        //...
    }

    // Register shutdown hook
    if (useShutdownHook) {
        if (shutdownHook == null) {
            shutdownHook = new CatalinaShutdownHook();
        }
        Runtime.getRuntime().addShutdownHook(shutdownHook);
    }

    if (await) {
        await();
        stop();
    }
}
  1. 調用server的start,開始進行啓動。
  2. 註冊jvm關閉鉤子,以便優雅停機。
  3. 開啓shutdown端口的監聽並阻塞,用於監聽關閉指令。

這裏的start同樣會調用LifeCycleBase的start方法,start較init來說多了一種狀態STARTING,對於正常流程來說,先由LifecycleBase改變當前狀態值爲STARTING_PREP,併發出事件通知,即執行當前事件的監聽器,然後執行各個節點的start方法,在啓動過程中,將狀態值設置爲STARTING,並執行事件監聽器,啓動結束後,將狀態設置爲初始化完成STARTED,併發出事件通知。

LifecycleBase#start

public final synchronized void start() throws LifecycleException {

    //...

    try {
        setStateInternal(LifecycleState.STARTING_PREP, null, false);
        startInternal();
        if (state.equals(LifecycleState.FAILED)) {
            // This is a 'controlled' failure. The component put itself into the
            // FAILED state so call stop() to complete the clean-up.
            stop();
        } else if (!state.equals(LifecycleState.STARTING)) {
            // Shouldn't be necessary but acts as a check that sub-classes are
            // doing what they are supposed to.
            invalidTransition(Lifecycle.AFTER_START_EVENT);
        } else {
            setStateInternal(LifecycleState.STARTED, null, false);
        }
    } catch (Throwable t) {
        handleSubClassException(t, "lifecycleBase.startFail", toString());
    }
}

2.Server啓動

StadardServer#startInternal

protected void startInternal() throws LifecycleException {

    fireLifecycleEvent(CONFIGURE_START_EVENT, null);
    setState(LifecycleState.STARTING);

    globalNamingResources.start();

    // Start our defined Services //啓動services組件
    synchronized (servicesLock) {
        for (int i = 0; i < services.length; i++) {
            services[i].start();
        }
    }
	//執行生命週期事件
    if (periodicEventDelay > 0) {
        monitorFuture = getUtilityExecutor().scheduleWithFixedDelay(
            new Runnable() {
                @Override
                public void run() {
                    startPeriodicLifecycleEvent();
                }
            }, 0, 60, TimeUnit.SECONDS);
    }
}

這裏的Listener主要執行了NamingContextListener,做了一些JNDI資源的初始化。然後就是執行service容器的start方法。

3.Service啓動

StadardService#startInternal

protected void startInternal() throws LifecycleException {

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

     //engine啓動
    if (engine != null) {
        synchronized (engine) {
            engine.start();
        }
    }
    // 啓動Executor線程池
    synchronized (executors) {
        for (Executor executor: executors) {
            executor.start();
        }
    }
    // 啓動MapperListener
    mapperListener.start();
    // 啓動Connector
    synchronized (connectorsLock) {
        for (Connector connector: connectors) {
            // If it has already failed, don't try and start it
            if (connector.getState() != LifecycleState.FAILED) {
                connector.start();
            }
        }
    }
}
  1. 啓動engine
  2. 啓動線程池
  3. 啓動mapperListener
  4. 啓動Connector

3.1 Engine啓動

engine的啓動和初始化一樣,會調用父類ContainerBasestartInternal方法,主要分爲3個步驟

  1. 使用線程池啓動子容器。
  2. 啓動Pipeline,並將狀態設置爲STARTING,發出事件通知
  3. 如果backgroundProcessorDelay參數 >= 0,則開啓ContainerBackgroundProcessor線程,用於調用子容器的backgroundProcess

ContainerBase#startInternal

protected synchronized void startInternal() throws LifecycleException {

    // ...

    // 把子容器的啓動放在線程池中處理
    Container[] children = findChildren();
    List<Future<Void>> results = new ArrayList<>();
    for (int i = 0; i < children.length; i++) {
        results.add(startStopExecutor.submit(new StartChild(children[i])));
    }

    MultiThrowable multiThrowable = null;
    // 阻塞當前線程,直到子容器start完成
    for (Future<Void> result : results) {
        try {
            result.get();
        } catch (Throwable e) {
            log.error(sm.getString("containerBase.threadedStartFailed"), e);
            if (multiThrowable == null) {
                multiThrowable = new MultiThrowable();
            }
            multiThrowable.add(e);
        }

    }
    //...
    // 啓用Pipeline
    if (pipeline instanceof Lifecycle) {
        ((Lifecycle) pipeline).start();
    }

    setState(LifecycleState.STARTING);

     // 開啓ContainerBackgroundProcessor線程用於調用子容器的backgroundProcess方法,默認情況下backgroundProcessorDelay=-1。
    if (backgroundProcessorDelay > 0) {
        monitorFuture = Container.getService(ContainerBase.this).getServer()
            .getUtilityExecutor().scheduleWithFixedDelay(
            new ContainerBackgroundProcessorMonitor(), 0, 60, TimeUnit.SECONDS);
    }
}

啓動子容器

startStopExecutor是在init階段被創建的,默認情況下會創建線程池InlineExecutorService,這個線程池的coreSize=maxSize=1。而這個可以在server.xml裏通過startStopThreads來配置,這裏需要注意的是,startStopExecutor的創建是在ContainerBase的初始化方法調用時,意味着每個繼承了ContainerBase的容器在啓動子容器時都維護了一個自己的線程池,並可以分別修改線程池屬性。繼承了ContainerBase的如下:

在這裏插入圖片描述

ContainerBase會把StartChild任務丟給線程池處理,得到Future,並且會遍歷所有的Future進行阻塞result.get(),這個操作是將異步啓動轉同步,子容器啓動完成纔會繼續運行。然後再來看看submit到線程池的StartChild任務,它實現了Callable接口,在call裏面完成子容器的start動作,而start方法依然是調用LifecycleBase的start方法。

private static class StartChild implements Callable<Void> {

    private Container child;

    public StartChild(Container child) {
        this.child = child;
    }

    @Override
    public Void call() throws LifecycleException {
        child.start();
        return null;
    }
}

3.2 Host

由於Host在init階段沒有進行初始化,所以在調用start方法時,狀態爲NEW,需要先進行初始化。

3.2.1 Host的初始化

由於默認實現StandardHost內部沒有實現initInternal,所以這裏的初始化執行了ContainerBaseinitInternal方法,詳見ContainerBase的initInternal

3.2.2 Host的啓動

StandardHost#startInternal

 private String errorReportValveClass ="org.apache.catalina.valves.ErrorReportValve";

protected synchronized void startInternal() throws LifecycleException {

    // Set error report valve  // errorValve默認使用org.apache.catalina.valves.ErrorReportValve
    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添加到 Pipeline 中,注意是添加到 basic valve 的前面
                Valve valve =
                    (Valve) Class.forName(errorValve).getConstructor().newInstance();
                getPipeline().addValve(valve);
            }
        } catch (Throwable t) {
           //...
        }
    }
    // 調用父類 ContainerBase
    super.startInternal();
}

在Pipeline裏尋找是否存在ErrorReportValve,如果不存在則實例化後添加到Pipeline。那麼Pipline和Valve是什麼呢

Pipeline是管道組件,用於封裝了一組有序的Valve,便於Valve順序地傳遞或者處理請求

Valve可以理解爲請求攔截器。

Pipeline也是一個Lifecycle組件, 默認實現是StandardPipeline,這段被定義在ContainerBase中,也就意味着同上面的startStopExecutor一樣,每個容器都維護了一個自己的攔截器鏈。

protected final Pipeline pipeline = new StandardPipeline(this);

而Valve的接口如下:

public interface Valve {
    public Valve getNext();
    public void setNext(Valve valve);
    public void backgroundProcess();
    public void invoke(Request request, Response response) throws IOException, ServletException;
    public boolean isAsyncSupported();
}

而對於Host來說,Valve在server.xml`配置了一個日誌攔截器。

<Host name="localhost"  appBase="webapps"
      unpackWARs="true" autoDeploy="true" startStopThreads="4" >
    <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" />
</Host>

另一個默認的Valve來自於StandardHost的構造方法。這裏每個實現了ContainerBase的容器都會在構造方法裏創建一個默認的Valve。

public StandardHost() {

    super();
    pipeline.setBasic(new StandardHostValve());

}

所以StandardHost Pipeline 包含的 Valve 組件:

  1. basic:org.apache.catalina.core.StandardHostValve
  2. first:org.apache.catalina.valves.AccessLogValve

而在添加ErrorReportValve時,會將ErrorReportValve添加到basic之前,first之後。

StandardPiepline#addValve

public void addValve(Valve valve) {

    // Validate that we can add this Valve
    if (valve instanceof Contained)
        ((Contained) valve).setContainer(this.container);
   //...

    // Add this Valve to the set associated with this Pipeline
    if (first == null) {
        first = valve;
        valve.setNext(basic);
    } else {
        Valve current = first;
        while (current != null) {
            if (current.getNext() == basic) {
                //設置到basic之前
                current.setNext(valve);
                valve.setNext(basic);
                break;
            }
            current = current.getNext();
        }
    }

    container.fireContainerEvent(Container.ADD_VALVE_EVENT, valve);
}

3.2.3Pipeline啓動

ContainerBase#startInternal方法中,執行完子容器的啓動後會啓動pipeline。這裏也會先執行pipeline的初始化在執行啓動,不過初始化時除了狀態的變更和事件通知之外,什麼也沒做,而在啓動階段,會遍歷所有的Valve,如果當前Valve是 Lifecycle 的子類,則會調用其 start 方法啓動 Valve 組件。

StandardPipeline#startInternal

protected synchronized void startInternal() throws LifecycleException {

    // Start the Valves in our pipeline (including the basic), if any
    Valve current = first;
    if (current == null) {
        current = basic;
    }
    //遍歷 Valve 鏈表,如果 Valve 是 Lifecycle 的子類,則會調用其 start 方法啓動 Valve 組件
    while (current != null) {
        if (current instanceof Lifecycle)
            ((Lifecycle) current).start();
        current = current.getNext();
    }

    setState(LifecycleState.STARTING);
}

StandardHost的啓動到這裏就結束了,從代碼可以看出,並沒有做過多的事情。而對於Host的真正操作是在監聽器HostConfig裏,即每次LifeCycle狀態變更時發送的事件通知。HostConfig是在解析xml階段配置的,主要是找到webapp目錄,並解壓war包。

3.2.4 HostConfig

HostConfig主要負責處理start和stop事件

HostConfig#lifecycleEvent

public void lifecycleEvent(LifecycleEvent event) {

    //判斷事件是否由 Host 發出,並且爲 HostConfig 設置屬性
    //..

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

public void start() {

    //...
    if (host.getDeployOnStartup())
        deployApps();
}
protected void deployApps() {

    File appBase = host.getAppBaseFile();
    File configBase = host.getConfigBaseFile();
    // 過濾掉hostConfig.ignorePath
    String[] filteredAppPaths = filterAppPaths(appBase.list());
    // 部署 xml 描述文件
    deployDescriptors(configBase, configBase.list());
    // 解壓 war 包
    deployWARs(appBase, filteredAppPaths);
    // 處理擴展的文件
    deployDirectories(appBase, filteredAppPaths);

}

解壓war包

protected void deployWARs(File appBase, String[] files) {

    //...
    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]) ) {
            //...
            results.add(es.submit(new DeployWar(this, cn, war)));
        }
    }

}

private static class DeployWar implements Runnable {

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

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

//部署Web應用程序時,默認情況下是否應將XML文件複製到 $CATALINA_BASE / conf / <engine> / <host>
protected boolean copyXML = false;

protected String contextClass = "org.apache.catalina.core.StandardContext";

protected void deployWAR(ContextName cn, File war) {
    //...
    Context context = null;
    //實例化context
    if (deployThisXML && useXml && !copyXML) {
        //從擴展目錄的xml解析並實例化 這個是有限制條件的
        context = (Context) digester.parse(xml);
    }else if (deployThisXML && xmlInWar) {
       	//從META-INF/context.xml目錄下的jar包裏解析並實例化
         context = (Context) digester.parse(istream);
    }else if (!deployThisXML && xmlInWar) {
        // ...
    } else {
        //一般情況下的實例化
        context = (Context) Class.forName(contextClass).getConstructor().newInstance();
    }
    //...
    //實例化ContextConfig,並將其作爲監聽器add到StandardContext中
     Class<?> clazz = Class.forName(host.getConfigClass());
    LifecycleListener listener = (LifecycleListener) clazz.getConstructor().newInstance();
    context.addLifecycleListener(listener);

    context.setName(cn.getName());
    context.setPath(cn.getPath());
    context.setWebappVersion(cn.getVersion());
    context.setDocBase(cn.getBaseName() + ".war");
    //添加並啓動子容器
    host.addChild(context);
}

3.3 Context

Context的啓動和Host類似。init階段就不再贅述了。

3.3.1 Context的啓動

StandardContext#startInternal

protected synchronized void startInternal() throws LifecycleException {
    //由於這個方法比較長,這裏節選重點部分
    
    //調用ContextConfig 從web.xml 或者 Servlet3.0 的註解配置,讀取 Servlet 相關的配置信息,比如 Filter、Servlet、Listener 等
    fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);

    // Call ServletContainerInitializers 執行ServletContainerInitializer的SPI實現類
    for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :initializers.entrySet()) {
        entry.getKey().onStartup(entry.getValue(),getServletContext());
    }

    //  初始化filter
    if (ok) {
        if (!filterStart()) {
            log.error(sm.getString("standardContext.filterFail"));
            ok = false;
        }
    }

    // StandardWrapper 實例化並且啓動 Servlet,由於 Servlet 存在 loadOnStartup 屬性
    // 因此使用了 TreeMap,根據 loadOnStartup 值 對 Wrapper 容器進行排序,然後依次啓動 Servlet
    // Load and initialize all "load on startup" servlets
    if (ok) {
        if (!loadOnStartup(findChildren())){
            log.error(sm.getString("standardContext.servletFail"));
            ok = false;
        }
    }
}

由於這段代碼比較複雜,這裏說一下大概的內容。

  1. 實例化 WebResourceRoot,默認實現類是 StandardRoot,用於讀取 webapp 的文件資源
  2. 實例化loader對象,這裏默認是WebappLoader,並執行loader的start方法,將所有class緩存起來。
  3. 發佈CONFIGURE_START_EVENT事件,該事件主要由ContextConfig處理。
  4. 實例化 Sesssion 管理器,默認使用 StandardManager。
  5. 初始化filter
  6. 實例化並啓動servlet

針對幾個重點的部分作詳細研究。

3.3.2 ContextConfig

對於該事件的處理主要在ContextConfig#webConfig

  1. 首先解析web.xml
WebXml webXml = createWebXml();

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

  1. 解析web-fragment.xml,tomcat提供的jar包會忽略此xml文件
Map<String,WebXml> fragments = processJarsForWebFragments(webXml, webXmlParser);

Set<WebXml> orderedFragments = null;
orderedFragments =WebXml.orderWebFragments(webXml, fragments, sContext);
  1. 處理 javax.servlet.ServletContainerInitializer實現類
processServletContainerInitializers();

protected void processServletContainerInitializers() {

    List<ServletContainerInitializer> detectedScis;
    try {
        WebappServiceLoader<ServletContainerInitializer> loader = new WebappServiceLoader<>(context);
        detectedScis = loader.load(ServletContainerInitializer.class);
    } catch (IOException e) {
        //...
    }

    for (ServletContainerInitializer sci : detectedScis) {
        initializerClassMap.put(sci, new HashSet<Class<?>>());

        HandlesTypes ht;
        try {
            ht = sci.getClass().getAnnotation(HandlesTypes.class);
        } catch (Exception e) {
            //...
            continue;
        }
        if (ht == null) {
            continue;
        }
        Class<?>[] types = ht.value();
        if (types == null) {
            continue;
        }

        for (Class<?> type : types) {
            if (type.isAnnotation()) {
                handlesTypesAnnotations = true;
            } else {
                handlesTypesNonAnnotations = true;
            }
            Set<ServletContainerInitializer> scis =
                typeInitializerMap.get(type);
            if (scis == null) {
                scis = new HashSet<>();
                typeInitializerMap.put(type, scis);
            }
            scis.add(sci);
        }
    }
}

獲取ServletContainerInitializer的實現類,並將其保存在ContextConfig的map中,ServletContainerInitializer是servlet的spi機制,可以通過 HandlesTypes 篩選出相關的 servlet 類,並對 ServletContext 進行額外處理。自定義的ServletContainerInitializer的實現類,需要將類名的項目路徑配置在META-INF/services/javax.servlet.ServletContainerInitializer文件中。以下是一個例子。

@HandlesTypes( Test.class )
public class CustomServletContainerInitializer implements ServletContainerInitializer {
    @Override
    public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
        for ( Class<?> type : c ) {
            System.out.println( type.getName() );
        }
    }
}

javax.servlet.ServletContainerInitializer

com.test.CustomServletContainerInitializer
  1. 如果沒有配置web.xml,則會先掃描 WEB-INF/classes 目錄下面的 class 文件,然後掃描 WEB-INF/lib 目錄下面的 jar 包,解析字節碼讀取 servlet 相關的註解配置類,將解析完的信息設置到WebXml對象中。重點代碼如下:

ContextConfig#processAnnotationsStream

protected void processAnnotationsStream(InputStream is, WebXml fragment,
                                        boolean handlesTypesOnly, Map<String,JavaClassCacheEntry> javaClassCache)
    throws ClassFormatException, IOException {
    //對字節碼文件進行解析。
    ClassParser parser = new ClassParser(is);
    JavaClass clazz = parser.parse();
    //處理註解@HandlesTypes
    checkHandlesTypes(clazz, javaClassCache);

    if (handlesTypesOnly) {
        return;
    }
	//獲取其註解,並把 WebServlet、WebFilter、WebListener 註解的類添加到 WebXml 實例中
    processClass(fragment, clazz);
}

//解析servlet3.0註解
protected void processClass(WebXml fragment, JavaClass clazz) {
    AnnotationEntry[] annotationsEntries = clazz.getAnnotationEntries();
    if (annotationsEntries != null) {
        String className = clazz.getClassName();
        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
            }
        }
    }
}
  1. 將解析出的filter、servlet、session、cookie等屬性加載到context中
private void configureContext(WebXml webxml) {
    //省略部分代碼...
    // 設置 Filter
    for (FilterDef filter : webxml.getFilters().values()) {
        if (filter.getAsyncSupported() == null) {
            filter.setAsyncSupported("false");
        }
        context.addFilterDef(filter);
    }

    // 設置 FilterMapping,即 Filter 的 URL 映射
    for (FilterMap filterMap : webxml.getFilterMappings()) {
        context.addFilterMap(filterMap);
    }
    // 往 Context 中添加子容器 Wrapper,即 Servlet
    for (ServletDef servlet : webxml.getServlets().values()) {
        Wrapper wrapper = context.createWrapper();
        wrapper.setName(servlet.getServletName());
        Map<String,String> params = servlet.getParameterMap();
        for (Entry<String, String> entry : params.entrySet()) {
            wrapper.addInitParameter(entry.getKey(), entry.getValue());
        }
        wrapper.setServletClass(servlet.getServletClass());
        wrapper.setOverridable(servlet.isOverridable());
        context.addChild(wrapper);
    }
    //還有很多屬性被加載,這裏就不一一贅述了
}

context.addChild(wrapper)時,會調用StandardContext的addChild,然後會調用ContainerBase的addChild,最後進行wrapper的啓動。Wrapper的初始化和Context沒什麼區別。

3.3.2.1 Wrapper的啓動

StandardWrapper沒有子容器,所以啓動時主要完成了jmx事件通知。

StandardWrapper#startInternal

protected synchronized void startInternal() throws LifecycleException {

    // 發出 j2ee.state.starting 事件通知
    if (this.getObjectName() != null) {
        Notification notification = new Notification("j2ee.state.starting",
                                                     this.getObjectName(),
                                                     sequenceNumber++);
        broadcaster.sendNotification(notification);
    }

    // 調用ContainerBase的startInternal,見Engine啓動的ContainerBase#startInternal
    super.startInternal();

    setAvailable(0L);

    //running 事件通知
    if (this.getObjectName() != null) {
        Notification notification =
            new Notification("j2ee.state.running", this.getObjectName(),
                             sequenceNumber++);
        broadcaster.sendNotification(notification);
    }

}
3.3.2.2 加載靜態文件

ContextConfig#webConfig

加載所有jar包下 META-INF/resources/的靜態資源文件。

processResourceJARs(resourceJars);

protected void processResourceJARs(Set<WebXml> fragments) {
    for (WebXml fragment : fragments) {
        URL url = fragment.getURL();
        try {
            if ("jar".equals(url.getProtocol()) || url.toString().endsWith(".jar")) {
                try (Jar jar = JarFactory.newInstance(url)) {
                    jar.nextEntry();
                    String entryName = jar.getEntryName();
                    while (entryName != null) {
                        if (entryName.startsWith("META-INF/resources/")) {
                            context.getResources().createWebResourceSet(
                                WebResourceRoot.ResourceSetType.RESOURCE_JAR,
                                "/", url, "/META-INF/resources");
                            break;
                        }
                        jar.nextEntry();
                        entryName = jar.getEntryName();
                    }
                }
            } else if ("file".equals(url.getProtocol())) {
                File file = new File(url.toURI());
                File resources = new File(file, "META-INF/resources/");
                if (resources.isDirectory()) {
                    context.getResources().createWebResourceSet(
                        WebResourceRoot.ResourceSetType.RESOURCE_JAR,
                        "/", resources.getAbsolutePath(), null, "/");
                }
            }
        } catch (IOException ioe) {
            //...
        } 
    }
}
3.3.2.3 設置ServletContainerInitializer

最後將所有ServletContainerInitializer實現類設置到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());
        }
    }
}

3.3.3 調用ServletContainerInitializer

在ContextConfig處理完start事件後,會先調用ServletContainerInitializer#onStartup方法。

StandardContext#startInternal

//調用 ServletContainerInitializer#onStartup()
for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
     initializers.entrySet()) {
    try {
        entry.getKey().onStartup(entry.getValue(),
                                 getServletContext());
    } catch (ServletException e) {
        log.error(sm.getString("standardContext.sciFail"), e);
        ok = false;
        break;
    }
}

3.3.4 啓動listener

public boolean listenerStart() {

    // 實例化所有listener
    String[] listeners = findApplicationListeners();
    Object[] results = new Object[listeners.length];
    boolean ok = true;
    for (int i = 0; i < results.length; i++) {
        try {
            String listener = listeners[i];
            results[i] = getInstanceManager().newInstance(listener);
        } catch (Throwable t) {
           //...
            ok = false;
        }
    }

    // 將listener按照類型分爲2個list存儲
    List<Object> eventListeners = new ArrayList<>();
    List<Object> lifecycleListeners = new ArrayList<>();
    for (int i = 0; i < results.length; i++) {
        if ((results[i] instanceof ServletContextAttributeListener)
            || (results[i] instanceof ServletRequestAttributeListener)
            || (results[i] instanceof ServletRequestListener)
            || (results[i] instanceof HttpSessionIdListener)
            || (results[i] instanceof HttpSessionAttributeListener)) {
            eventListeners.add(results[i]);
        }
        if ((results[i] instanceof ServletContextListener)
            || (results[i] instanceof HttpSessionListener)) {
            lifecycleListeners.add(results[i]);
        }
    }
	//...
    //調用ServletContextListener的contextInitialized方法
    for (int i = 0; i < instances.length; i++) {
        if (!(instances[i] instanceof ServletContextListener))
            continue;
        ServletContextListener listener =
            (ServletContextListener) instances[i];
        try {
            fireContainerEvent("beforeContextInitialized", listener);
            if (noPluggabilityListeners.contains(listener)) {
                listener.contextInitialized(tldEvent);
            } else {
                listener.contextInitialized(event);
            }
            fireContainerEvent("afterContextInitialized", listener);
        } catch (Throwable t) {
            //...
            ok = false;
        }
    }
    return ok;

}
  1. 實例化所有的listener。
  2. 根據類型將listener分爲eventListenerslifecycleListeners
  3. 找出lifecycleListeners中的ServletContextListener,執行它的contextInitialized方法。

3.3.5 初始化filter

public boolean filterStart() {

    // Instantiate and record a FilterConfig for each defined filter
    boolean ok = true;
    synchronized (filterConfigs) {
        filterConfigs.clear();
        for (Entry<String,FilterDef> entry : filterDefs.entrySet()) {
            String name = entry.getKey();

            try {
                ApplicationFilterConfig filterConfig =
                    new ApplicationFilterConfig(this, entry.getValue());
                filterConfigs.put(name, filterConfig);
            } catch (Throwable t) {
                ok = false;
            }
        }
    }

    return ok;
}



ApplicationFilterConfig(Context context, FilterDef filterDef)
    throws ClassCastException, ReflectiveOperationException, ServletException,
NamingException, IllegalArgumentException, SecurityException {

    super();

    this.context = context;
    this.filterDef = filterDef;
    // Allocate a new filter instance if necessary
    if (filterDef.getFilter() == null) {
        getFilter();
    } else {
        this.filter = filterDef.getFilter();
        context.getInstanceManager().newInstance(filter);
        initFilter();
    }
}

將filterDefs轉換成ApplicationFilterConfig,filterDefs是ContextConfig在解析servlet註解時用來保存filter信息的對象,並在ApplicationFilterConfig的構造方法中完成調用filter.init()。舉個例子。這裏會調用HelloFilter的init方法。

@WebFilter(
    urlPatterns = {"/*"}
)
public class HelloFilter implements Filter {
    public HelloFilter() {
    }

    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("filter is init");
    }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("filter is running");
        chain.doFilter(request, response);
    }

    public void destroy() {
        System.out.println("filter is destroy");
    }
}

3.3.6 加載Wrapper

public boolean loadOnStartup(Container[] children) {

    TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();
    for (int i = 0; i < children.length; i++) {
        Wrapper wrapper = (Wrapper) children[i];
        int loadOnStartup = wrapper.getLoadOnStartup();
        if (loadOnStartup < 0)
            continue;
        Integer key = Integer.valueOf(loadOnStartup);
        ArrayList<Wrapper> list = map.get(key);
        if (list == null) {
            list = new ArrayList<>();
            map.put(key, list);
        }
        list.add(wrapper);
    }

    // Load the collected "load on startup" servlets
    for (ArrayList<Wrapper> list : map.values()) {
        for (Wrapper wrapper : list) {
            try {
                wrapper.load();
            } catch (ServletException e) {
                //...
            }
        }
    }
    return true;

}
  1. 首先根據loadOnStartup大小將wrapper進行排序,loadOnStartup值相同的放在同一個list。
  2. loadOnStartup>=0的會在啓動階段被加載,而如果loadOnStartup爲默認值-1的話,是在首次調用時加載,這裏的load方法調用的是StandardWrapper#load()
public synchronized void load() throws ServletException {
    // 實例化 Servlet,並且調用 init 方法完成初始化
    instance = loadServlet();
}

public synchronized Servlet loadServlet() throws ServletException {
    InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();

    servlet = (Servlet) instanceManager.newInstance(servletClass);

    //調用servlet的init
    initServlet(servlet);

    fireContainerEvent("load", this);
}

到這裏Engine的啓動就結束了。

3.4MapperListener啓動

public void startInternal() throws LifecycleException {

    setState(LifecycleState.STARTING);

    Engine engine = service.getContainer();
    if (engine == null) {
        return;
    }

    findDefaultHost();

    addListeners(engine);
    
    Container[] conHosts = engine.findChildren();
    for (Container conHost : conHosts) {
        Host host = (Host) conHost;
        if (!LifecycleState.NEW.equals(host.getState())) {
            // Registering the host will register the context and wrappers
            registerHost(host);
        }
    }
}
  1. 設置當前defaultName,此測試用例爲localhost
  2. MapperListener作爲ContainerListenerLifecycleListener遞歸設置到當前容器及其所有子容器
  3. 將Engine啓動時解析出來的Wrapper放到MapperListener中,以備請求時進行匹配。
private void registerHost(Host host) {

    String[] aliases = host.findAliases();
    //設置host
    mapper.addHost(host.getName(), aliases, host);

    for (Container container : host.findChildren()) {
        if (container.getState().isAvailable()) {
            registerContext((Context) container);
        }
    }

    // Default host may have changed
    findDefaultHost();

}

private void registerContext(Context context) {

    String contextPath = context.getPath();
    if ("/".equals(contextPath)) {
        contextPath = "";
    }
    Host host = (Host)context.getParent();

    WebResourceRoot resources = context.getResources();
    String[] welcomeFiles = context.findWelcomeFiles();
    List<WrapperMappingInfo> wrappers = new ArrayList<>();

    for (Container container : context.findChildren()) {
        //將wrapper封裝成WrapperMappingInfo
        prepareWrapperMappingInfo(context, (Wrapper) container, wrappers);
    }
    //將所有wrapper設置到Mapper中
    mapper.addContextVersion(host.getName(), host, contextPath,
                             context.getWebappVersion(), context, welcomeFiles, resources,
                             wrappers);


}

private final Map<Context, ContextVersion> contextObjectToContextVersionMap =
    new ConcurrentHashMap<>();

public void addContextVersion(String hostName, Host host, String path,
                              String version, Context context, String[] welcomeResources,
                              WebResourceRoot resources, Collection<WrapperMappingInfo> wrappers) {
    //省略了部分代碼
    contextObjectToContextVersionMap.put(context, newContextVersion);
}

首先將Host設置到Mapper,然後解析子容器Context,將解析出的Wrapper封裝成WrapperMappingInfo設置到MapperListener的Mapper中。這裏放一張截圖方便理解。其中/tomcat-test是測試用例servlet,其他的是Tomcat自帶。

在這裏插入圖片描述

3.5 Connector啓動

StandardConnector#startInternal

protected void startInternal() throws LifecycleException {

    //校驗端口
    if (getPortWithOffset() < 0) {
        throw new LifecycleException(sm.getString(
            "coyoteConnector.invalidPort", Integer.valueOf(getPortWithOffset())));
    }

    setState(LifecycleState.STARTING);

    try {
        //
        protocolHandler.start();
    } catch (Exception e) {
        //...
    }
}

根據Connector的init階段,這裏的protocolHandler的實現是Http11NioProtocol,而Http11NioProtocol的start方法會調用父類AbstractProtocol#start(),這個方法裏調用了endpoint的start。

AbstractProtocol#start()

public void start() throws Exception {
  
    endpoint.start();
    
}

3.5.1 Endpoint啓動

Connector啓動的重點是在Endpoint的啓動,這裏會啓動兩個線程組,Pollers

NioEndpoint#start()

public void startInternal() throws Exception {

    if (!running) {
        running = true;
        paused = false;
		//SocketProcessor對象的緩存
        if (socketProperties.getProcessorCache() != 0) {
            processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
                                                     socketProperties.getProcessorCache());
        }
        //緩存poller事件
        if (socketProperties.getEventCache() != 0) {
            eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
                                                 socketProperties.getEventCache());
        }
        //字節緩衝區高速緩存
        if (socketProperties.getBufferPool() != 0) {
            nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
                                                  socketProperties.getBufferPool());
        }

        // Create worker collection
        if (getExecutor() == null) {
            createExecutor();
        }
		//初始化連接數計數器,默認是1000 
        //private int maxConnections = 10000;
        initializeConnectionLatch();

        // Start poller threads
        pollers = new Poller[getPollerThreadCount()];
        for (int i = 0; i < pollers.length; i++) {
            pollers[i] = new Poller();
            Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-" + i);
            pollerThread.setPriority(threadPriority);
            pollerThread.setDaemon(true);
            pollerThread.start();
        }

        startAcceptorThreads();
    }
}

除了配置一些緩存之外,這裏創建了三種不同類型的Thread。Poller線程、Acceptor線程以及一個線程池。

  1. create線程池,

AbstractEndpoint#createExecutor

private int minSpareThreads = 10;

private int maxThreads = 200;

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

這裏默認的coresize=10,maxsize=200,keepalive=60s,這個線程池用來處理servlet請求。

  1. start Poller Thread,該線程用於監聽Socket事件,當Socket可讀或者可寫時,調用上面的線程池處理Socket請求。
 private int pollerThreadCount = 1;

pollers = new Poller[getPollerThreadCount()];
for (int i = 0; i < pollers.length; i++) {
    pollers[i] = new Poller();
    Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-" + i);
    pollerThread.setPriority(threadPriority);
    pollerThread.setDaemon(true);
    pollerThread.start();
}
  1. start Acceptor Thread,用於接收新連接,並將新連接添加到Poller事件隊列中。
 protected int acceptorThreadCount = 1;

protected void startAcceptorThreads() {
    int count = getAcceptorThreadCount();
    acceptors = new ArrayList<>(count);

    for (int i = 0; i < count; i++) {
        Acceptor<U> acceptor = new Acceptor<>(this);
        String threadName = getName() + "-Acceptor-" + i;
        acceptor.setThreadName(threadName);
        acceptors.add(acceptor);
        Thread t = new Thread(acceptor, threadName);
        t.setPriority(getAcceptorThreadPriority());
        t.setDaemon(getDaemon());
        t.start();
    }
}

放一張Endpoint啓動完之後主要的線程狀態圖。

在這裏插入圖片描述

從圖中可以看出Acceptor ThreadClientPoller Thread都處於running狀態,而線程池則是wait狀態,這裏當ClientPoller Thread接收到socket請求時,會啓用線程池處理servlet請求。

到這裏Tomcat就成功啓動完成了。

參考:

https://blog.csdn.net/Dwade_mia/article/details/79244157
https://blog.csdn.net/Dwade_mia/article/details/79328151

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