美文网首页
五 tomcat启动源码分析(二)--入口代码calatina启

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

作者: 爱编程的凯哥 | 来源:发表于2019-02-20 09:51 被阅读36次

上一节我们引出了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处理

相关文章

网友评论

      本文标题:五 tomcat启动源码分析(二)--入口代码calatina启

      本文链接:https://www.haomeiwen.com/subject/ekvbrqtx.html