五 tomcat啟動源碼分析(二)--入口代碼calatina啟動介紹

上一節(jié)我們引出了calatina類進(jìn)行應(yīng)用加載,再回顧下調(diào)用代碼

                daemon.setAwait(true);
                daemon.load(args);
                daemon.start();

對應(yīng)在calatina中都能找到相應(yīng)方法。setAwait(true)表示阻塞等待的含義我們先跳過。此類中有兩個重點要分析的方法load()和start(),一個加載配置,一個啟動應(yīng)用,看下我分析完的整個思維導(dǎo)圖,圖片太大,可以查看原圖


catalina

我將轉(zhuǎn)化為我們重點分析類的時序圖:

時序圖

放大時序圖,我們看到了如下流程

  1. catalina 先調(diào)用load加載了server.xml,配置文件生成了對應(yīng)tocmat架構(gòu)的主要類圖,此時通過Digester技術(shù)實現(xiàn),主要類圖如下


    tomcat主要類圖.jpg

其中stndardService為每一個service服務(wù),connector為此service中的一個接收器,它和standardEngine代表的應(yīng)用容器通過MapperListener進(jìn)行映射,standardEngine內(nèi)部又分成standardHost、standardConetxt還有StandardWrapper(未化出,后生成),HostConfig和ConetxtConfig為對應(yīng)host和context的事件監(jiān)聽者用于初始化web應(yīng)用類。

  1. catalina方法的start方法,最終調(diào)用到standardService的start()方法,此方法采用模版方法先調(diào)用父級的公用模版方法,最后調(diào)用自己的startInernal方法,最終將會初始化整個service服務(wù)。

  2. standardService的啟動方法中核心代碼如下:

   protected void startInternal() throws LifecycleException {

      .....
        //更新tomcat狀態(tài)
        setState(LifecycleState.STARTING);

        // 啟動容器服務(wù)
        if (container != null) {
            synchronized (container) {
                container.start();
            }
        }

      //開啟定義線程
        synchronized (executors) {
            for (Executor executor: executors) {
                executor.start();
            }
        }

        // 開啟接收connector
        synchronized (connectors) {
            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);
                }
            }
        }
    }

核心部分是三個服務(wù)啟動,我們重點看container.start()connector.start()方法。

  1. container.start()方法啟動業(yè)務(wù)容器,其都遵守tomcat的生命周期最高接口Lifecycle來管理,通過事件驅(qū)動模式,配置觀察者模式,通過觀察tomcat的狀態(tài)變化,進(jìn)行啟動驅(qū)動。
    此時有個重點類為ContainBase,此類是StandardEngine、StandardHost、StandardPipeline、StandardWrapper抽象父類,看下ContainBase中的重點方法
    第一,startInternal方法,每層容器,查找子容器,開啟子容器,添加pipeline通道,更新此容器狀態(tài),驅(qū)動監(jiān)聽者進(jìn)行狀態(tài)更新,相關(guān)操作。
  protected synchronized void startInternal() throws LifecycleException {

        // Start our subordinate components, if any
        ...........
        //添加子容器,啟動子容器
        Container children[] = findChildren();
        for (int i = 0; i < children.length; i++) {
            children[i].start();
        }

        // 啟動當(dāng)前容器的pipeline,配置調(diào)用責(zé)任鏈
        if (pipeline instanceof Lifecycle)
            ((Lifecycle) pipeline).start();

      //事件驅(qū)動,通知當(dāng)前容器的監(jiān)聽者,進(jìn)行相應(yīng)操作
        setState(LifecycleState.STARTING);

        // Start our thread
        threadStart();

    }

第二,setState方法,事件驅(qū)動方法

private synchronized void setStateInternal(LifecycleState state,
            Object data, boolean check) throws LifecycleException {
    。。。。。。。。  
        this.state = state;
        String lifecycleEvent = state.getLifecycleEvent();
        if (lifecycleEvent != null) {
    //事件驅(qū)動
            fireLifecycleEvent(lifecycleEvent, data);
        }
    }

此方法繼續(xù)進(jìn)入,會看到

 public void /**/fireLifecycleEvent(String type, Object data) {

        LifecycleEvent event = new LifecycleEvent(lifecycle, type, data);
        LifecycleListener interested[] = listeners;
        for (int i = 0; i < interested.length; i++)
              //典型的觀察者模式寫法
            interested[i].lifecycleEvent(event);

    }

第三,類圖已經(jīng)說了兩個重點的監(jiān)聽者HostConfig和ContextConfig,HostConfig用于找出目錄下的war包

    protected void deployApps(String name) {
  //根據(jù)你配置的appBase路徑,查找文件
        File appBase = appBase();
//判斷你是否在conf/Catalina/你應(yīng)用名.xml中定制自己的應(yīng)用文件,沒有定制,后面將會使用默認(rèn)的context.xml
        File configBase = configBase();
        ContextName cn = new ContextName(name);
        String baseName = cn.getBaseName();
        
        // Deploy XML descriptors from configBase
        File xml = new File(configBase, baseName + ".xml");
        if (xml.exists())
            deployDescriptor(cn, xml, baseName + ".xml");
        // Deploy WARs, and loop if additional descriptors are found
        File war = new File(appBase, baseName + ".war");
        if (war.exists())
            deployWAR(cn, war, baseName + ".war");
        // Deploy expanded folders
        File dir = new File(appBase, baseName);
        if (dir.exists())
            deployDirectory(cn, dir, baseName);
        
    }

再看下 deployWAR()方法發(fā)布我們常見的war包,其核心邏輯

  protected void deployWAR(ContextName cn, File war, String file) {
           ............
        //生成StandardContext類
           context = (Context) Class.forName(contextClass).newInstance();
           .........
      //配置ContextConfig監(jiān)聽器
            Class<?> clazz = Class.forName(host.getConfigClass());
            LifecycleListener listener =
                (LifecycleListener) clazz.newInstance();
            context.addLifecycleListener(listener);

            context.setName(cn.getName());
            context.setPath(cn.getPath());
            context.setWebappVersion(cn.getVersion());
            context.setDocBase(file);
          //host中添加context,并將在此方法中啟動conetxt
            host.addChild(context);
            ..........
        deployed.put(cn.getName(), deployedApp);
    }

第四,addChildInternal()增加子容器方法,上一步host.addChild(context)添加context,隨后addChildInternal中啟動了context,然后還是驅(qū)動模式通過fireContainerEvent方法通知觀察者。

    private void addChildInternal(Container child) {

             ...............
              //啟動子容器
                child.start();
                success = true;
           ............
          //驅(qū)動事件發(fā)生
        fireContainerEvent(ADD_CHILD_EVENT, child);
    }

  1. ContextConfig類,查看其接收事件方法,最后進(jìn)行驅(qū)動configureStart方法發(fā)布服務(wù)
  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;
        }

        // 啟動服務(wù)
        if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
            configureStart();
        .....
    }

查看configureStart方法

     */
    protected synchronized void configureStart() {
      ...............
      //創(chuàng)建WebXm解析其
        createWebXmlDigester(context.getXmlNamespaceAware(), 
         context.getXmlValidation());
        //解析web.xml
        webConfig();
      .............
    }

最后終于看到熟悉的web.xml,看下 webConfig()方法,此時解析web.xml,解析所有servlet,并通過webXml.configureContext(context)生成對應(yīng)的每個StandardWrapper,

  protected void webConfig() {
        WebXml webXml = createWebXml();

        // Parse global web.xml if present
        InputSource globalWebXml = getGlobalWebXmlSource();
        if (globalWebXml == null) {
            // This is unusual enough to log
            log.info(sm.getString("contextConfig.defaultMissing"));
        } else {
            parseWebXml(globalWebXml, webXml, false);
        }

        // Parse host level web.xml if present
        // Additive apart from welcome pages
        webXml.setReplaceWelcomeFiles(true);
        InputSource hostWebXml = getHostWebXmlSource();
        parseWebXml(hostWebXml, webXml, false);
        
        // Parse context level web.xml
        webXml.setReplaceWelcomeFiles(true);
        InputSource contextWebXml = getContextWebXmlSource();
        parseWebXml(contextWebXml, webXml, false);
        
        // Assuming 0 is safe for what is required in this case
        double webXmlVersion = 0;
        if (webXml.getVersion() != null) {
            webXmlVersion = Double.parseDouble(webXml.getVersion());
        }
        
        if (webXmlVersion >= 3) {
            // 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();

            // Only need to process fragments and annotations if metadata is
            // not complete
            Set<WebXml> orderedFragments = null;
            if  (!webXml.isMetadataComplete()) {
                // Step 2. Order the fragments.
                orderedFragments = WebXml.orderWebFragments(webXml, fragments);
    
                // Step 3. Look for ServletContainerInitializer implementations
                if (ok) {
                    processServletContainerInitializers(orderedFragments);
                }
    
                // Step 4. Process /WEB-INF/classes for annotations
                // This will add any matching classes to the typeInitializerMap
                if (ok) {
                    URL webinfClasses;
                    try {
                        webinfClasses = context.getServletContext().getResource(
                                "/WEB-INF/classes");
                        processAnnotationsUrl(webinfClasses, webXml);
                    } catch (MalformedURLException e) {
                        log.error(sm.getString(
                                "contextConfig.webinfClassesUrl"), e);
                    }
                }
    
                // Step 5. Process JARs for annotations - only need to process
                // those fragments we are going to use
                // This will add any matching classes to the typeInitializerMap
                if (ok) {
                    processAnnotations(orderedFragments);
                }
    
                // Step 6. Merge web-fragment.xml files into the main web.xml
                // file.
                if (ok) {
                    ok = webXml.merge(orderedFragments);
                }
    
                // Step 6.5 Convert explicitly mentioned jsps to servlets
                if (!false) {
                    convertJsps(webXml);
                }
    
                // Step 7. Apply merged web.xml to Context
                if (ok) {
                    webXml.configureContext(context);
    
                    // Step 7a. 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();
                    context.getServletContext().setAttribute(
                           org.apache.tomcat.util.scan.Constants.MERGED_WEB_XML,
                            mergedWebXml);
                    if (context.getLogEffectiveWebXml()) {
                        log.info("web.xml:\n" + mergedWebXml);
                    }
                }
            } else {
                webXml.configureContext(context);
            }
            
            // Always need to look for static resources
            // Step 8. 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>();
                if (orderedFragments != null) {
                    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
            }
            
            // Only look for ServletContainerInitializer if metadata is not
            // complete
            if (!webXml.isMetadataComplete()) {
                // Step 9. 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());
                        }
                    }
                }
            }
        } else {
            // Apply unmerged web.xml to Context
            convertJsps(webXml);
            webXml.configureContext(context);
        }
    }

最終的StandardWrapper類中存了每個servlet的相關(guān)信息。
到這里,業(yè)務(wù)容器從Engine-->Host--->Conetext--->Wrapper層層驅(qū)動,初始化了整個web服務(wù)處理核心。

  1. 業(yè)務(wù)容器啟動完后,啟動Connector,根據(jù)LifecycleBase模版模式,最終落到startInternal方法上
    protected void startInternal() throws LifecycleException {

        setState(LifecycleState.STARTING);

        try {
    //啟動通信處理handler,開啟endpoint,監(jiān)聽socket端口
            protocolHandler.start();
        } catch (Exception e) {
            String errPrefix = "";
            if(this.service != null) {
                errPrefix += "service.getName(): \"" + this.service.getName() + "\"; ";
            }

            throw new LifecycleException
                (errPrefix + " " + sm.getString
                 ("coyoteConnector.protocolHandlerStartFailed"), e);
        }
        //綁定connect和container關(guān)系,最后存入其屬性`Mapper mapper`中
        mapperListener.start();
    }

再看下endpoint開啟監(jiān)聽的代碼,其中設(shè)置了接收線程等配置,并異步開啟了Acceptor進(jìn)行端口監(jiān)聽。

 public void startInternal() throws Exception {

        if (!running) {
            running = true;
            paused = false;
             
            // Create worker collection
            if ( getExecutor() == null ) {
                createExecutor();
            }

            initializeConnectionLatch();
            
            // 控制輪詢線程
            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();
            }

            // 控制接收線程
            for (int i = 0; i < acceptorThreadCount; i++) {
                Thread acceptorThread = new Thread(new Acceptor(), getName() + "-Acceptor-" + i);
                acceptorThread.setPriority(threadPriority);
                acceptorThread.setDaemon(getDaemon());
                acceptorThread.start();
            }
        }
    }

看下Acceptor類,socket監(jiān)聽實現(xiàn):

  protected class Acceptor implements Runnable {
        /**
         * The background thread that listens for incoming TCP/IP connections and
         * hands them off to an appropriate processor.
         */
        @Override
        public void run() {

            int errorDelay = 0;

            // Loop until we receive a shutdown command
            while (running) {
                
                // Loop if endpoint is paused
                while (paused && running) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        // Ignore
                    }
                }

                if (!running) {
                    break;
                }
                try {
                    //if we have reached max connections, wait
                    awaitConnection();
                    
                    SocketChannel socket = null;
                    try {
                        // Accept the next incoming connection from the server
                        // socket
                        socket = serverSock.accept();
                    } catch (IOException ioe) {
                        // Introduce delay if necessary
                        errorDelay = handleExceptionWithDelay(errorDelay);
                        // re-throw
                        throw ioe;
                    }
                    // Successful accept, reset the error delay
                    errorDelay = 0;

                    // Hand this socket off to an appropriate processor
                    //TODO FIXME - this is currently a blocking call, meaning we will be blocking
                    //further accepts until there is a thread available.
                    if ( running && (!paused) && socket != null ) {
                        // setSocketOptions() will add channel to the poller
                        // if successful
                        if (!setSocketOptions(socket)) {
                            try {
                                socket.socket().close();
                                socket.close();
                            } catch (IOException ix) {
                                if (log.isDebugEnabled())
                                    log.debug("", ix);
                            }
                        } else {
                            countUpConnection();
                        }
                    }
                } catch (SocketTimeoutException sx) {
                    //normal condition
                } catch (IOException x) {
                    if (running) {
                        log.error(sm.getString("endpoint.accept.fail"), x);
                    }
                } catch (OutOfMemoryError oom) {
                    try {
                        oomParachuteData = null;
                        releaseCaches();
                        log.error("", oom);
                    }catch ( Throwable oomt ) {
                        try {
                            try {
                                System.err.println(oomParachuteMsg);
                                oomt.printStackTrace();
                            }catch (Throwable letsHopeWeDontGetHere){
                                ExceptionUtils.handleThrowable(letsHopeWeDontGetHere);
                            }
                        }catch (Throwable letsHopeWeDontGetHere){
                            ExceptionUtils.handleThrowable(letsHopeWeDontGetHere);
                        }
                    }
                } catch (Throwable t) {
                    ExceptionUtils.handleThrowable(t);
                    log.error(sm.getString("endpoint.accept.fail"), t);
                }
            }//while
        }//run
    }

到此,web服務(wù)全部啟動成功

總結(jié)下知識點:

  1. Digester 解析xml技術(shù)
  2. LifecycleBase生命周期管理
  3. fireContainerEvent事件驅(qū)動模式
  4. ExceptionUtils.handleThrowable(Throwable t)異常統(tǒng)一處理方式

目錄:?tomcat 源碼學(xué)習(xí)系列
上一篇: ? tomcat啟動源碼分析(一)--入口代碼Bootstrap初始化
下一篇:? tomcat啟動源碼分析(三)--http請求nio處理

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容