五 tomcat启动源码分析(二)--入口代码calatina启动介绍

上一节我们引出了calatina类进行应用加载,再回顾下调用代码

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

对应在calatina中都能找到相应方法。setAwait(true)表示阻塞等待的含义我们先跳过。此类中有两个重点要分析的方法load()和start(),一个加载配置,一个启动应用,看下我分析完的整个思维导图,图片太大,可以查看原图


catalina

我将转化为我们重点分析类的时序图:

时序图

放大时序图,我们看到了如下流程

  1. catalina 先调用load加载了server.xml,配置文件生成了对应tocmat架构的主要类图,此时通过Digester技术实现,主要类图如下


    tomcat主要类图.jpg

其中stndardService为每一个service服务,connector为此service中的一个接收器,它和standardEngine代表的应用容器通过MapperListener进行映射,standardEngine内部又分成standardHost、standardConetxt还有StandardWrapper(未化出,后生成),HostConfig和ConetxtConfig为对应host和context的事件监听者用于初始化web应用类。

  1. catalina方法的start方法,最终调用到standardService的start()方法,此方法采用模版方法先调用父级的公用模版方法,最后调用自己的startInernal方法,最终将会初始化整个service服务。

  2. standardService的启动方法中核心代码如下:

   protected void startInternal() throws LifecycleException {

      .....
        //更新tomcat状态
        setState(LifecycleState.STARTING);

        // 启动容器服务
        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);
                }
            }
        }
    }

核心部分是三个服务启动,我们重点看container.start()connector.start()方法。

  1. container.start()方法启动业务容器,其都遵守tomcat的生命周期最高接口Lifecycle来管理,通过事件驱动模式,配置观察者模式,通过观察tomcat的状态变化,进行启动驱动。
    此时有个重点类为ContainBase,此类是StandardEngine、StandardHost、StandardPipeline、StandardWrapper抽象父类,看下ContainBase中的重点方法
    第一,startInternal方法,每层容器,查找子容器,开启子容器,添加pipeline通道,更新此容器状态,驱动监听者进行状态更新,相关操作。
  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();
        }

        // 启动当前容器的pipeline,配置调用责任链
        if (pipeline instanceof Lifecycle)
            ((Lifecycle) pipeline).start();

      //事件驱动,通知当前容器的监听者,进行相应操作
        setState(LifecycleState.STARTING);

        // Start our thread
        threadStart();

    }

第二,setState方法,事件驱动方法

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

此方法继续进入,会看到

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

    }

第三,类图已经说了两个重点的监听者HostConfig和ContextConfig,HostConfig用于找出目录下的war包

    protected void deployApps(String name) {
  //根据你配置的appBase路径,查找文件
        File appBase = appBase();
//判断你是否在conf/Catalina/你应用名.xml中定制自己的应用文件,没有定制,后面将会使用默认的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()方法发布我们常见的war包,其核心逻辑

  protected void deployWAR(ContextName cn, File war, String file) {
           ............
        //生成StandardContext类
           context = (Context) Class.forName(contextClass).newInstance();
           .........
      //配置ContextConfig监听器
            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,然后还是驱动模式通过fireContainerEvent方法通知观察者。

    private void addChildInternal(Container child) {

             ...............
              //启动子容器
                child.start();
                success = true;
           ............
          //驱动事件发生
        fireContainerEvent(ADD_CHILD_EVENT, child);
    }

  1. ContextConfig类,查看其接收事件方法,最后进行驱动configureStart方法发布服务
  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;
        }

        // 启动服务
        if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
            configureStart();
        .....
    }

查看configureStart方法

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

最后终于看到熟悉的web.xml,看下 webConfig()方法,此时解析web.xml,解析所有servlet,并通过webXml.configureContext(context)生成对应的每个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的相关信息。
到这里,业务容器从Engine-->Host--->Conetext--->Wrapper层层驱动,初始化了整个web服务处理核心。

  1. 业务容器启动完后,启动Connector,根据LifecycleBase模版模式,最终落到startInternal方法上
    protected void startInternal() throws LifecycleException {

        setState(LifecycleState.STARTING);

        try {
    //启动通信处理handler,开启endpoint,监听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关系,最后存入其属性`Mapper mapper`中
        mapperListener.start();
    }

再看下endpoint开启监听的代码,其中设置了接收线程等配置,并异步开启了Acceptor进行端口监听。

 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监听实现:

  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服务全部启动成功

总结下知识点:

  1. Digester 解析xml技术
  2. LifecycleBase生命周期管理
  3. fireContainerEvent事件驱动模式
  4. ExceptionUtils.handleThrowable(Throwable t)异常统一处理方式

目录: tomcat 源码学习系列
上一篇:   tomcat启动源码分析(一)--入口代码Bootstrap初始化
下一篇:  tomcat启动源码分析(三)--http请求nio处理

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355

推荐阅读更多精彩内容