Tomcat8.5.43源码分析-(4)Tomcat启动过程探究 第二部分 Web应用加载

我们回到Tomcat启动过程探究 第一部分的结尾,回到StandardService的初始化方法initInternal()。

StandardService的重点方法有四个:

  • engine.init():初始化Servlet引擎,引擎只负责请求的处理不需要考虑请求连接,协议等等。一个Service存在一个对应的Engine。
  • executor.init():初始化线程池,该线程池可以在组件中共享。默认实现为StandardThreadExecutor。
  • mapperListener.init():初始化监听路由。MapperListener未重写生命周期中的init()方法,将在start()方法中详细讨论。
  • connector.init():初始化连接器,connector负责监听、读取请求,对请求进行制定协议的解析,匹配正确的处理容器,反馈响应。Server.xml中的默认的两个Connector如下,关于协议这块此处不再展开,以后若有机会给予补充:
    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" /><!HTTP 1.1协议>
    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" /><!AJP 1.3协议>

在StandardServer.init()方法中,可以看到Server和Service是一对多的关系。为此,干脆先整理各层级容器直接的关系图:

接下来,我们关注Engine(StandardEngine)的初始化时会做些什么。

首先在StandardEngine构造方法里,我发现了有意思的东西:

    /**
     * Create a new StandardEngine component with the default basic Valve.
     */
    public StandardEngine() {

        super();
        pipeline.setBasic(new StandardEngineValve());
        /* Set the jmvRoute using the system property jvmRoute */
        try {
            setJvmRoute(System.getProperty("jvmRoute"));
        } catch(Exception ex) {
            log.warn(sm.getString("standardEngine.jvmRouteFail"));
        }
        // By default, the engine will hold the reloading thread
        backgroundProcessorDelay = 10;

    }

注意这一句:

pipeline.setBasic(new StandardEngineValve());

查询资料后,知道:

Pipeline(管道)采用了责任链的方式处理客户端的请求,每一个Valve(阀)表示责任链上的每一个处理器。

StandardPipeline重写了startInternal()方法,Pieline和Valve也将在start()方法中详细讨论。

至此,从Bootstrap.load()开始的长途跋涉就告一段落了。不过后面是流程更复杂的Bootstrap.start()。

经过了load的查看源码的经历,很容易就找到了start的关键路径为:Bootstrap.start()->Catalina.start()->StandardServer.startInternal()

    /**
     * Start nested components ({@link Service}s) and implement the requirements
     * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
     *
     * @exception LifecycleException if this component detects a fatal error
     *  that prevents this component from being used
     */
    @Override
    protected void startInternal() throws LifecycleException {

        fireLifecycleEvent(CONFIGURE_START_EVENT, null);//使注册的监听器响应事件
        setState(LifecycleState.STARTING);//设置生命周期状态,使注册的监听器响应事件

        globalNamingResources.start();

        // Start our defined Services
        synchronized (servicesLock) {
            for (int i = 0; i < services.length; i++) {
                services[i].start();
            }
        }
    }

StandardServer此刻注册的监听器列表如下:

遍历启动StandardService.startInternal():

    /**
     * Start nested components ({@link Executor}s, {@link Connector}s and
     * {@link Container}s) and implement the requirements of
     * {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
     *
     * @exception LifecycleException if this component detects a fatal error
     *  that prevents this component from being used
     */
    @Override
    protected void startInternal() throws LifecycleException {

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

        // Start our defined Container first
        if (engine != null) {
            synchronized (engine) {
                engine.start();
            }
        }

        synchronized (executors) {
            for (Executor executor: executors) {
                executor.start();
            }
        }

        mapperListener.start();

        // Start our defined Connectors second
        synchronized (connectorsLock) {
            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);
                }
            }
        }
    }

此刻StandardService的监听器列表为空。

在Catalina.createStartDigester()中,给StandardEngine通过addChild添加了一个child,即StandardHost。

在容器抽象类ContainerBase中startInternal()方法有这样一段:

        // Start our child containers, if any
        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])));
        }

它代表了当一个父容器start的时候,子容器会开启新线程。并执行子容器的start()方法。

接下来我们关注StandardHost的startInternal()方法:

    /**
     * Start this component and implement the requirements
     * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
     *
     * @exception LifecycleException if this component detects a fatal error
     *  that prevents this component from being used
     */
    @Override
    protected synchronized void startInternal() throws LifecycleException {

        // Set error report valve
        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 valve =
                        (Valve) Class.forName(errorValve).getConstructor().newInstance();
                    getPipeline().addValve(valve);
                }
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                log.error(sm.getString(
                        "standardHost.invalidErrorReportValveClass",
                        errorValve), t);
            }
        }
        super.startInternal();
    }

这个方法只设置了一个关闭阀门,并没有做太多的事情。

但此刻StandardHost的监听器列表:

我们知道接下来HostConfig会接收到Lifecycle.START_EVENT的消息。

我们看下HostConfig在接收到消息后会做什么:

    /**
     * Process the START event for an associated Host.
     *
     * @param event The lifecycle event that has occurred
     */
    @Override
    public void lifecycleEvent(LifecycleEvent event) {

        // Identify the host we are associated with
        try {
            host = (Host) event.getLifecycle();
            if (host instanceof StandardHost) {
                setCopyXML(((StandardHost) host).isCopyXML());
                setDeployXML(((StandardHost) host).isDeployXML());
                setUnpackWARs(((StandardHost) host).isUnpackWARs());
                setContextClass(((StandardHost) host).getContextClass());
            }
        } catch (ClassCastException e) {
            log.error(sm.getString("hostConfig.cce", event.getLifecycle()), e);
            return;
        }

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

首先是标识符的设置,这边只说比较重要的标识符:

  • CopyXML:若为true,复制context.xml到$CATALINA_BASE/conf/<Engine名称>/<Host名称>目录下
  • DeployXML:若为true,Digester解析context.xml并创建Context对象。

之后就是HostConfig.start()方法了:

    /**
     * Process a "start" event for this Host.
     */
    public void start() {

        if (log.isDebugEnabled())
            log.debug(sm.getString("hostConfig.start"));

        try {
            ObjectName hostON = host.getObjectName();
            oname = new ObjectName
                (hostON.getDomain() + ":type=Deployer,host=" + host.getName());
            Registry.getRegistry(null, null).registerComponent
                (this, oname, this.getClass().getName());
        } catch (Exception e) {
            log.warn(sm.getString("hostConfig.jmx.register", oname), e);
        }

        if (!host.getAppBaseFile().isDirectory()) {
            log.error(sm.getString("hostConfig.appBase", host.getName(),
                    host.getAppBaseFile().getPath()));
            host.setDeployOnStartup(false);
            host.setAutoDeploy(false);
        }

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

    }

这里比较有意思的一段是:

            Registry.getRegistry(null, null).registerComponent
                (this, oname, this.getClass().getName());

将当前监听器注册到MBean,由其管理。关于MBean,后面会另做一章介绍,这里只要知道是注册上去就可以了。

HostConfig.delpoyApps()方法:

    /**
     * Deploy applications for any directories or WAR files that are found
     * in our "application root" directory.
     */
    protected void deployApps() {

        File appBase = host.getAppBaseFile();
        File configBase = host.getConfigBaseFile();
        String[] filteredAppPaths = filterAppPaths(appBase.list());
        // Deploy XML descriptors from configBase
        deployDescriptors(configBase, configBase.list());
        // Deploy WARs
        deployWARs(appBase, filteredAppPaths);
        // Deploy expanded folders
        deployDirectories(appBase, filteredAppPaths);

    }
  • deployDescriptors:从Context描述文件部署context。例如:$CATALINA_BASE/conf/Catalina/localhost下创建app.xml文件。
  • deployWARs:从War包部署context。例如:把Web工程的打包的War包复制到Host指定的appBase下。
  • deployDirectories:从Web路径部署context。例如:把Web工程的所有资源文件复制到Host指定的appBase下。

这三个方法,其实是在不同的地方做同样一件事情,以deployDirectories为例:

  1. 创建新的线程,通过方法对应的方式,解析生成Context实例StandardContext。
  2. 给StandardContext设置监听器ContextConfig。deployDirectory(ContextName cn, File dir):
                Class<?> clazz = Class.forName(host.getConfigClass());
                LifecycleListener listener = (LifecycleListener) clazz.getConstructor().newInstance();

    这里的关系实际上已在Catalina.createStartDigester()中配置。

  3. 将StandardContext设置为StandardHost的child,若Host已经启动,则会直接启动context:
host.addChild(context);

 接下来执行的是StandardContext.startInternal(),这个方法非常的冗长,这里只选取重要的部分:

        if (getLoader() == null) {
            WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
            webappLoader.setDelegate(getDelegate());
            setLoader(webappLoader);
        }
                Loader loader = getLoader();
                if (loader instanceof Lifecycle) {
                    ((Lifecycle) loader).start();
                }

上两段为创建和启动Web应用类加载器WebappClassLoader。

                // Notify our interested LifecycleListeners
                fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);

 CONFIGURE_START_EVENT事件的,通知。顺便整理发送该通知时,StandardContext的监听列表:

 ContextConfig接收到CONFIGURE_START_EVENT后,会执行configureStart()->webConfig()。

webConfig()是真正解析web.xml的方法,是和Web开发关系最紧密,也是我们最常见的配置xml。我们重点看一下这个方法,已经在重要的步骤上添加了注释:

    /**
     * Scan the web.xml files that apply to the web application and merge them
     * using the rules defined in the spec. For the global web.xml files,
     * where there is duplicate configuration, the most specific level wins. ie
     * an application's web.xml takes precedence over the host level or global
     * web.xml file.
     */
    protected void webConfig() {
        /*
         * Anything and everything can override the global and host defaults.
         * This is implemented in two parts
         * - Handle as a web fragment that gets added after everything else so
         *   everything else takes priority
         * - Mark Servlets as overridable so SCI configuration can replace
         *   configuration from the defaults
         */

        /*
         * The rules for annotation scanning are not as clear-cut as one might
         * think. Tomcat implements the following process:
         * - As per SRV.1.6.2, Tomcat will scan for annotations regardless of
         *   which Servlet spec version is declared in web.xml. The EG has
         *   confirmed this is the expected behaviour.
         * - As per http://java.net/jira/browse/SERVLET_SPEC-36, if the main
         *   web.xml is marked as metadata-complete, JARs are still processed
         *   for SCIs.
         * - If metadata-complete=true and an absolute ordering is specified,
         *   JARs excluded from the ordering are also excluded from the SCI
         *   processing.
         * - If an SCI has a @HandlesType annotation then all classes (except
         *   those in JARs excluded from an absolute ordering) need to be
         *   scanned to check if they match.
         */
        WebXmlParser webXmlParser = new WebXmlParser(context.getXmlNamespaceAware(),
                context.getXmlValidation(), context.getXmlBlockExternal());

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

        WebXml webXml = createWebXml();

        // Parse context level web.xml
        InputSource contextWebXml = getContextWebXmlSource();//解析容器基本的web.xml
        if (!webXmlParser.parseWebXml(contextWebXml, webXml, false)) {
            ok = false;
        }

        ServletContext sContext = context.getServletContext();

        // Ordering is important here

        // Step 1. Identify all the JARs packaged with the application and those
        // provided by the container. If any of the application JARs have a
        // web-fragment.xml it will be parsed at this point. web-fragment.xml
        // files are ignored for container provided JARs.
        //扫描/WEB-INF/lib下的所有JAR包,如果包含/META-INF/web-fragment.xml这类片段webXml
        //则创建片段webXml
        Map<String,WebXml> fragments = processJarsForWebFragments(webXml, webXmlParser);

        // Step 2. Order the fragments.
        //对片段webXml进行排序,排序结果影响了Filter的执行顺序
        Set<WebXml> orderedFragments = null;
        orderedFragments =
                WebXml.orderWebFragments(webXml, fragments, sContext);

        // Step 3. Look for ServletContainerInitializer implementations
        //查找并创建ServletContainerInitializer的实现类
        if (ok) {
            processServletContainerInitializers();
        }

        if  (!webXml.isMetadataComplete() || typeInitializerMap.size() > 0) {
            // Steps 4 & 5.
            //注解配置解析,将解析结果合并到web.xml
            processClasses(webXml, orderedFragments);
        }

        if (!webXml.isMetadataComplete()) {
            // Step 6. Merge web-fragment.xml files into the main web.xml
            // file.
            //合并片段webXml按照顺序到web.xml
            if (ok) {
                ok = webXml.merge(orderedFragments);
            }

            // Step 7. Apply global defaults
            // Have to merge defaults before JSP conversion since defaults
            // provide JSP servlet definition.
            //配置JSP Servlet
            webXml.merge(defaults);

            // Step 8. Convert explicitly mentioned jsps to servlets
            if (ok) {
                convertJsps(webXml);
            }

            // Step 9. Apply merged web.xml to Context
            //使用合并后的web.xml配置当前StandardContext,包括Servlet、Filter、Listener
            //并针对Servlet创建StandardWrapper,添加到StandardContext
            if (ok) {
                configureContext(webXml);
            }
        } else {
            webXml.merge(defaults);
            convertJsps(webXml);
            configureContext(webXml);
        }

        if (context.getLogEffectiveWebXml()) {
            log.info("web.xml:\n" + webXml.toXml());
        }

        // Always need to look for static resources
        // Step 10. Look for static resources packaged in JARs
        //添加META-INF/resources/下的静态资源到standardContext
        if (ok) {
            // Spec does not define an order.
            // Use ordered JARs followed by remaining JARs
            Set<WebXml> resourceJars = new LinkedHashSet<>();
            for (WebXml fragment : orderedFragments) {
                resourceJars.add(fragment);
            }
            for (WebXml fragment : fragments.values()) {
                if (!resourceJars.contains(fragment)) {
                    resourceJars.add(fragment);
                }
            }
            processResourceJARs(resourceJars);
            // See also StandardContext.resourcesStart() for
            // WEB-INF/classes/META-INF/resources configuration
        }

        // Step 11. Apply the ServletContainerInitializer config to the
        // context
        if (ok) {
            for (Map.Entry<ServletContainerInitializer,
                    Set<Class<?>>> entry :
                        initializerClassMap.entrySet()) {
                if (entry.getValue().isEmpty()) {
                    context.addServletContainerInitializer(
                            entry.getKey(), null);
                } else {
                    context.addServletContainerInitializer(
                            entry.getKey(), entry.getValue());
                }
            }
        }
    }

至此,终于真正的完成了web.xml的合并、解析,并应用于StandardContext。

我们回到StandardContext.startInternal(),看看接下来还做了什么:

                // Start our child containers, if not already started
                for (Container child : findChildren()) {
                    if (!child.getState().isAvailable()) {
                        child.start();
                    }
                }

启动StandardWrapper后,StandarWrapper.startInternal()只有一个作用,即由MBean发送Notification广播。

随后沿着addChild这条线,会继续调用StandarWrapper.load()->loadServlet()方法,开始初始化和创建Servlet实例。

至此, Web应用的加载过程就完毕了。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值