Tomcat源码:StandardEngine、StandardHost、StandardContext、StandardWrapper

news2024/11/25 0:54:21

前文:

《Tomcat源码:启动类Bootstrap与Catalina的加载》

《Tomcat源码:容器的生命周期管理与事件监听》

《Tomcat源码:StandardServer与StandardService》

《Tomcat源码:Container接口》

        写在开头:本文为个人学习笔记,内容比较随意,夹杂个人理解,如有错误,欢迎指正。

前言

        在前文中,我们介绍了容器组件的公共接口Container接口,这个接口的抽象实现类ContainerBase实现了initInternal、startInternal这两个生命周期,规定了子容器中的大部分行为,本文我们就来继续深入到各个子容器中进行源码的分析。

        

目录

前言

一、StandardEngine

        1、initInternal

         2、startInternal

二、StandardHost

三、HostConfig

        生命周期监听器的添加

        生命周期监听器的执行

        start与deployApps

        deployWARs

        HostConfig#deployWAR

四、StandardContext

        1、initInternal

        2、startInternal

        触发事件监听器

        解析web.xml配置

        wrapper的创建

         启动监听器

        loadOnStartup

五、StandardWrapper

        1、startInternal

        2、load


一、StandardEngine

        Engine的实现类为StandardEngine,在前文中我们了解到StandardService在init时调用了engine.init()来初始化engine,StandardEngine没有重写Init方法,但由于StandardEngine继承了ContainerBase从而间接继承了LifecycleBase抽象类,得以复用LifecycleBase中的init方法,start方法也是如此,因此以engine为代表的这些子容器实际上只需要重写initInternal、startInternal即可。

    // LifecycleBase.java
    public final synchronized void init() throws LifecycleException {
        if (!state.equals(LifecycleState.NEW)) {
            invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
        }

        try {
            setStateInternal(LifecycleState.INITIALIZING, null, false);
            // 子类实现initInternal方法完成不同的初始化逻辑
            initInternal();
            setStateInternal(LifecycleState.INITIALIZED, null, false);
        } catch (Throwable t) {
            handleSubClassException(t, "lifecycleBase.initFail", toString());
        }
    }

        1、initInternal

        StandardEngine的initInternal方法中,第一步调用了getRealm() 方法,确保 StandardEngine 的父类 StandardEngine中的 Realm 类型的属性不为空,Realm 是 Tomcat 的特性功能,这里先略过。
        第二步调用了super.initInternal(),也就是ContainerBase的initInternal方法,该方法我们前文中做了介绍,简单来说就是创建一个用于启动子容器的线程池。

    protected void initInternal() throws LifecycleException {
        getRealm();
        super.initInternal();
    }

         ContainerBase的initInternal方法如下

    protected ThreadPoolExecutor startStopExecutor;
    
    protected void initInternal() throws LifecycleException {
        // 创建线程安全的队列
        BlockingQueue<Runnable> startStopQueue = new LinkedBlockingQueue<>();
        // 创建线程池
        startStopExecutor = new ThreadPoolExecutor(getStartStopThreadsInternal(), getStartStopThreadsInternal(), 10,
                TimeUnit.SECONDS, startStopQueue, new StartStopThreadFactory(getName() + "-startStop-"));
        startStopExecutor.allowCoreThreadTimeOut(true);
        // 调用父类LifecycleMBeanBase的初始化方法
        super.initInternal();
    }

         2、startInternal

        startInternal也和initInternal一样,直接调用了父类ContainerBase中的该方法,其作用便是利用initInternal方法中创建的线程池启动以当前容器的子容器(比如Engine中启动Host)。

    protected synchronized void startInternal() throws LifecycleException {
        if (log.isInfoEnabled()) {
            log.info(sm.getString("standardEngine.start", ServerInfo.getServerInfo()));
        }
        super.startInternal();
    }

         startInternal通过线程池异步启动当前容器的子容器。

    // 将要启动的子容器加入到线程池中异步启动
    Container children[] = findChildren();
    List<Future<Void>> results = new ArrayList<>();
    for (Container child : children) {
        results.add(startStopExecutor.submit(new StartChild(child)));
    }
    MultiThrowable multiThrowable = null;
    // 获取子容器启动结果
    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);
            }
    }
    // ...
    threadStart();

        startInternal启动过程中会调用threadStart方法启动当前容器的backgroundProcess,因为StandardEngine没有重写该方法,因此还是调用的ContainerBase中的实现,前文中已经做了解析,这里就不赘述了。(《Tomcat源码:Container接口》)

二、StandardHost

        Host时Engine的子容器,其结构也和StandardEngine类似,但StandardHost没有重写initInternal因此这里只分析startInternal。

        Host#startInternal的逻辑就是看自己的Pipeline对象里是否包含了 org.apache.catalina.valves.ErrorReportValve这个Valve对象,如果没有,就添加一个 org.apache.catalina.valves.ErrorReportValve 到自己的Pipeline 对象里。

        最后调用 ContainerBase 的 startInternal 方法,ContainerBase#startInternal 方法在上篇文章也讲到了,这里就不多讲了。

    protected synchronized void startInternal() throws LifecycleException {

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

三、HostConfig

        生命周期监听器的添加

        在Host之前的容器都属于server.xml中的配置,但context并不是其中的配置,因此context的读取方式就和之前的容器不同了,下面我们来介绍下context是如何成为host的child的。

        首先回到Catalina初始化中的createStartDigester方法,其中有这样一行:

digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));

        这是要向Digester实例中添加多个规则(RuleSet),HostRuleSet继承了RuleSetBase类,并实现了它的抽像方法:addRuleInstances。下面来看这个方法的具体实现:

    public void addRuleInstances(Digester digester) {

        digester.addObjectCreate(prefix + "Host",
                                 "org.apache.catalina.core.StandardHost",
                                 "className");
        digester.addSetProperties(prefix + "Host");
        digester.addRule(prefix + "Host",
                         new CopyParentClassLoaderRule());
        digester.addRule(prefix + "Host",
                         new LifecycleListenerRule
                         ("org.apache.catalina.startup.HostConfig",
                          "hostConfigClass"));
        // 其余代码
        }

        首先创建了LifecycleListenerRule规则,然后在模式" Server/Service/Engine/Hos"下,将这个规则加到Host之上。这个规则正是tomcat的生命周期规则,因此在Host生命周期过程中会触发这个规则。

        以上归纳起来就一句话:HostConfig是一个生命周期监听器类,在tomcat加载配置文件的时候被添加到Host之上。结合我们之前介绍生命周期方法的内容可知,StandardHost启动的过程中,在其父类中改变了生命周期状态,导致这个监听器的触发。

        生命周期监听器的执行

        添加完了监听器,StandardHost在启动时就会触发监听器,下面我们看下监听器执行了哪些操作。

        start与deployApps

        首先是根据状态判断触发start方法。

    public void lifecycleEvent(LifecycleEvent event) {
        // 其余代码
        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状态时触发
            start();
        } else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
            stop();
        }
    }

         继续调用deployApps方法,这个方法里面将会过滤出WEB应用的地址(即webapps),然后对描述符部署、war包部署、文件夹部署这三种不同的部署方式做专门处理。

        注意这里获取WEB应用的webapps使用的时数组,因为一个webapps下面可能有多个web应用。

    // StandardHost.java
   private boolean deployOnStartup = true;    
 
   // HostConfig.java
   public void start() {
        // 其源代码
        if (host.getDeployOnStartup()) {
            deployApps();
        }
    }

    protected void deployApps() {
        // 这里的appBase会读取到StardHost中的 private String appBase = "webapps";
        File appBase = host.getAppBaseFile();
        File configBase = host.getConfigBaseFile();
        // 过滤出应用路径
        String[] filteredAppPaths = filterAppPaths(appBase.list());
        // 针对描述符部署、war包部署、文件夹部署这三种方式
        // Deploy XML descriptors from configBase
        deployDescriptors(configBase, configBase.list());
        // Deploy WARs
        deployWARs(appBase, filteredAppPaths);
        // Deploy expanded folders
        deployDirectories(appBase, filteredAppPaths);
    }

        deployWARs

        我们以最经典的部署包方式为例来深入看下

    protected void deployWARs(File appBase, String[] files) { 
        // initInternal时创建的用于异步启动子容器的线程池
        ExecutorService es = host.getStartStopExecutor();
        List<Future<?>> results = new ArrayList<>();

        for (String file : files) {
            if (file.equalsIgnoreCase("META-INF")) {
                continue;
            }
            if (file.equalsIgnoreCase("WEB-INF")) {
                continue;
            }
            File war = new File(appBase, file);
            if (file.toLowerCase(Locale.ENGLISH).endsWith(".war") && war.isFile() && !invalidWars.contains(file)) {
                ContextName cn = new ContextName(file, true);
                if (tryAddServiced(cn.getName())) {
                    try {
                        // 其余代码
                        // 发布war的任务提交到线程池中
                        results.add(es.submit(new DeployWar(this, cn, war)));
                    } catch (Throwable t) {
                        ExceptionUtils.handleThrowable(t);
                        removeServiced(cn.getName());
                        throw t;
                    }
                }
            }
        }
        for (Future<?> result : results) {
            try {
                result.get();
            } catch (Exception e) {
                log.error(sm.getString("hostConfig.deployWar.threaded.error"), e);
            }
        }
    }

         异步发布war包调用了静态内部类DeployWar中的run方法,可以看到其实际上是调用了外部类的deployWAR。

    private static class DeployWar implements Runnable {
        private HostConfig config;
        private ContextName cn;
        private File war;
        DeployWar(HostConfig config, ContextName cn, File war) {
            this.config = config;
            this.cn = cn;
            this.war = war;
        }
        @Override
        public void run() {
            try {
                // 发布单个war
                config.deployWAR(cn, war);
            } finally {
                config.removeServiced(cn.getName());
            }
        }
    }

        HostConfig#deployWAR

        可以看到这里将当前WEB应用构建为了context子容器作为child加入到了host中,并且给context添加了一个监听器ContextConfig,这个监听器将会在下文有非常重要的作用,具体内容我们会在下文介绍。

   protected void deployWAR(ContextName cn, File war) {
		// 其余代码
        Context context = null;
        boolean deployThisXML = isDeployThisXML(war, cn);
        try {
            // 其余代码
            // 这里读取的StartHost中的configClass变量
            // configClass = "org.apache.catalina.startup.ContextConfig";
            Class<?> clazz = Class.forName(host.getConfigClass());
            LifecycleListener listener = (LifecycleListener) clazz.getConstructor().newInstance();
            context.addLifecycleListener(listener);
            context.addLifecycleListener(listener);
            context.setName(cn.getName());
            context.setPath(cn.getPath());
            context.setWebappVersion(cn.getVersion());
            context.setDocBase(cn.getBaseName() + ".war");
          	// 添加StandardContext到当前Host中
            host.addChild(context);
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            log.error(sm.getString("hostConfig.deployWar.error", war.getAbsolutePath()), t);
        } 
        // 其余代码
    }

         

四、StandardContext

        1、initInternal

        首先调用了父类的 initInternal 方法,也就是 ContainerBase#initInternal。
然后调用了 namingResources(NamingResourcesImpl类型的属性) 的 init 方法,这个 namingResources 跟 StandardServer 里的 globalNamingResources 类似,只不过 globalNamingResources 是全局的,而这里的 namingResources 只是这个 StandardContext 的。
        最后构造一个 Notification 对象并调用 broadcaster.sendNotification 方法来广播这个通知。broadcaster 是在 StandardContext 对象构造函数里初始化的。

    protected void initInternal() throws LifecycleException {
        super.initInternal();
        if (namingResources != null) {
            namingResources.init();
        }
        if (this.getObjectName() != null) {
            // 发布正在启动的JMX通知,可以通过添加NotificationListener监听Web应用的启动
            Notification notification = new Notification("j2ee.object.created", this.getObjectName(),
                    sequenceNumber.getAndIncrement());
            broadcaster.sendNotification(notification);
        }
    }

    public StandardContext() {
        super();
        pipeline.setBasic(new StandardContextValve());
        broadcaster = new NotificationBroadcasterSupport();
        if (!Globals.STRICT_SERVLET_COMPLIANCE) {
            resourceOnlyServlets.add("jsp");
        }
    }

        2、startInternal

        这段代码超过了三百行,因此这里直接以文字描述的方式简介一下其中的过程

1、发布正在启动的JMX通知,这样可以通过添加NotificationListener来监听Web应用的启动。

2、启动当前Context维护的NDI资源。

3、初始化当前Context使用的WebResourceRoot并启动。WebResourceRoot维护了Web应用所有的资源集合(Class文件、Jar包以及其他资源文件),主要用于类加载和按照路径查找资源文件。

4、创建Web应用类加载器(WebappLoader)。WebappLoader继承自LifecycleMBeanBase,在其启动时创建Web应用类加载器(WebappClassLoader)。此外,该类还提供了background-Process,用于Context后台处理。当检测到Web应用的类文件、Jar包发生变更时,重新加载Context。

5、如果没有设置Cookie处理器,则创建默认的Rfc6265CookieProcessor。

6、设置字符集映射(CharsetMapper),该映射主要用于根据Locale获取字符集编码。

7、初始化临时目录,默认为$CATALINA_BASE/work/<Engine名称><Host名称>/<Context名称>

8、Wb应用的依赖检测,主要检测依赖扩展,点完整性。

9、如果当前Context使用JNDI,则为其添加NamingContextListener。

10、启动Web应用类加载器(WebappLoader.start),此时才真正创建WebappClassLoader实例。

11、启动安全组件(Realm)。

12、发布CONFIGURE_START_EVENT事件,ContextConfig监听该事件以完成Servlet的创建.

13、启动Context子节点(Wrapper),在server.xml中配置的Wrapper。

14、启动Context维护的Pipeline。

15、创建会话管理器。如果配置了集群组件,则由集群组件创建,否则使用标准的会话管理器(StandardManager)。在集群环境下,需要将会话管理器注册到集群组件。

16、将Context的Web资源集合(org.apache.catalina.WebResourceRoot)添加到Servlet(ontext属性,属性名为org.apache,catalina.resources

17、创建实例管理器(InstanceManager),用于创建对象实例,如Servlet、Filter等。

18、将Jar包扫描器(JarScanner)添加到ServletContext)属性,属性名为org.apache.tomcat.JarScanner

19、合并ServletContext初始化参数和Context组件中的ApplicationParameter。合并原则:ApplicationParameter配置为可以覆盖,那么只有当ServletContext没有相关参数或者相关参数为空时添加;如果配置为不可覆盖,则强制添加,此时即使ServletContext配置了同名参数也不会生效。

20、启动添加到当前Context的ServletContainerInitializer。该类的实例具体由ContextConfig查找并添加。该类主要用于以可编程的方式添加Wb应用的配置,如Servlet、Filter等。

21、实例化应用监听器(ApplicationListener),分为事件监听器(ServletContextAttributeListener,ServletRequestAttributeListener,ServletRequestListener HttpSessionldListener,HttpSessionAttributeListener)以及生命周期监听(HttpSessionListener、ServletContextListener)。这些监听器可以通过Context部署描述文件、可编程的方式(ServletContainerInitializer)或者Web.xml添加,并且触发ServletContextListener.contextInitialized。

22、检测未覆盖的HTTP方法的安全约束。

23、启动会话管理器。

24、实例化FilterConfig(ApplicationFilterConfig)、Filter,并调用Filter.init初始化e

25、对于loadOnStartup≥O的Wrapper,调用Wrapper.load(),该方法负责实例化Servlet,并调用Servlet.init进行初始化。

26、启动后台定时处理线程。只有当backgroundProcessorDelay>O时启动,用于监控守护文件的变更等。当backgroundProcessorDelay≤O时,表示Context的后台任务由上级容器(Host)调度。

27、发布正在运行的MX通知。

28、调用WebResourceRoot.gc()释放资源(WebResourceRoot加载资源时,为了提高性能会缓存某些信息,该方法用于清理这些资源,如关闭JAR文件)。

29、设置Context的状态,如果启动成功,设置为STARTING(其父类LifecycleBase会自动将状态转换为STARTED),否则设置为FAILED。

        触发事件监听器

        在启动子容器前会先触发CONFIGURE_START_EVENT事件。

    // 发布CONFIGURE_START_EVENT事件,ContextConfig监听该事件完成Servlet创建
    fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);

        在上文StandardHost的最后我们讲到Host将Context添加为子容器时为其添加了一个监听器ContextConfig,下面我们来看下ContextConfig内部的操作。

        可以看到在CONFIGURE_START_EVENT事件时,会调用configureStart方法,该方法最重要的步骤为webConfig(),正是这个步骤解析了web.xml。

    public void lifecycleEvent(LifecycleEvent event) {
        context = (Context) event.getLifecycle();
        // 其余代码
        if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
            configureStart();
        }
    }

    protected synchronized void configureStart() {
        // 其余代码
        // 开始解析web.xml的配置
        webConfig();
        // 如果未配置忽略应用注解配置,那么对filter,servlet,listener进行Resource注解的搜索
        // Resource配置在类,字段,方法上,会根据资源的类型进行划分资源类型,添加到不同资源集合中,比如环境变量,JNDI等等资源
        if (!context.getIgnoreAnnotations()) {
            applicationAnnotationsConfig();
        }
        if (ok) {
            // 将context约束的角色和wrapper中注解RunAs或配置中设置的角色添加到context容器中,重复的不会被再次添加
            validateSecurityRoles();
        }
        // 配置验证器,如果没有Ralm或者实现了 Authenticator的管道阀,那么就会添加一个默认的NonLoginAuthenticator验证器
        if (ok) {
            authenticatorConfig();
        }
        // 其余代码
        // 如果配置context时没有遇到任何问题,那么就表示配置成功
        if (ok) {
            context.setConfigured(true);
        } else {
            log.error(sm.getString("contextConfig.unavailable"));
            context.setConfigured(false);
        }
    }

        解析web.xml配置

        Tomcat初始化Web容器的过程如下(ContextConfig.webConfig):

  1. 解析默认配置,生成WebXml对象(Tomcat使用该对象表示web.xml的解析结果)。先解析容器级配置,然后再解析Host级配置。这样对于同名配置,Host级将覆盖容器级。为了便于后续过程描述,我们暂且称之为“默认WebXml”。为了提升性能,ContextConfig对默认WebXml进行了缓存,以避免重复解析。
  2. 解析Web应用的web.xml文件。如果StandardContext的altDDName不为空,则将该属性指向的文件作为web,xml,否则使用默认路径,即WEB-NF/web.xml。解析结果同样为WebXml对象(此时创建的对象为主WebXml,其他解析结果均需要合并到该对象上)。暂时将其称为“主WebXml"。
  3. 扫描Web应用所有JAR包,如果包含META-INF/web-fragment.xml,则解析文件并创建WebXml对象。暂时将其称为“片段WebXml”。
  4. 将web-fragment.xml创建的WebXml对象按照Servlet规范进行排序,同时将排序结果对应的JAR文件名列表设置到ServletContext属性中,属性名为javax.servlet.context.orderedLibs。该排序非常重要,因为这决定了Filter等的执行顺序。

        篇幅原因,这里只看下读取web应用读取web.xml的步骤,可以看到首先是调用getContextWebXmlSource方法将web.xml转换为输入流后进行的解析。

    protected void webConfig() {
        WebXmlParser webXmlParser = new WebXmlParser(context.getXmlNamespaceAware(),
                context.getXmlValidation(), context.getXmlBlockExternal());

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

        WebXml webXml = createWebXml();
        // 读取应用下的web.xml并解析
        InputSource contextWebXml = getContextWebXmlSource();
        if (!webXmlParser.parseWebXml(contextWebXml, webXml, false)) {
            ok = false;
        }
        // 其余代码
        // 添加wrapper为context的子容器
        configureContext(webXml);
    }

        这里将读取当前web应用下的web.xml。

    protected InputSource getContextWebXmlSource() {
        InputStream stream = null;
        InputSource source = null;
        URL url = null;

        String altDDName = null;
        // 获取ServletContext,如没有则创建一个,类别为ApplicationContext
        ServletContext servletContext = context.getServletContext();
        try {
            if (servletContext != null) {
                altDDName = (String)servletContext.getAttribute(Globals.ALT_DD_ATTR);
                // 其余代码
                // ApplicationWebXml = "/WEB-INF/web.xml";
                stream = servletContext.getResourceAsStream
                        (Constants.ApplicationWebXml);
                    try {
                        url = servletContext.getResource(
                                Constants.ApplicationWebXml);
                    } catch (MalformedURLException e) {
                        log.error(sm.getString("contextConfig.applicationUrl"));
                    }
            }
            // 其余代码
            source = new InputSource(url.toExternalForm());
            source.setByteStream(stream);
        } 
        // 其余代码
        return source;
    }

        wrapper的创建

        可以看到这里是将上文中解析完的web.xml中的配置里的servlet读取出来后创建wrapper并设置属性,最后添加到context中作为子容器。

        另外还有一个要注意的就是addApplicationListener,Spring启动中的关键监听器ContextLoaderListener便是这个时候被添加的。

    private void configureContext(WebXml webxml) {
        // 添加web.xml中的监听器
        for (String listener : webxml.getListeners()) {
            context.addApplicationListener(listener);
        }
        // 其余代码
        for (ServletDef servlet : webxml.getServlets().values()) {
            // 获取web.xml中配置的servlet
            Wrapper wrapper = context.createWrapper();
            // 启动项设置
            if (servlet.getLoadOnStartup() != null) {
                wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
            }
            wrapper.setName(servlet.getServletName());
            // 配置servlet初始化参数
            Map<String,String> params = servlet.getParameterMap();
            for (Entry<String, String> entry : params.entrySet()) {
                wrapper.addInitParameter(entry.getKey(), entry.getValue());
            }
            wrapper.setServletClass(servlet.getServletClass());
            // 添加为context的子容器
            context.addChild(wrapper);
        }
        for (Entry<String, String> entry :
                webxml.getServletMappings().entrySet()) {
            context.addServletMappingDecoded(entry.getKey(), entry.getValue());
        }
        // 其余代码
    }

        创建完后回到StandardContext的startInternal中就可以启动子容器了

    // StandardContext#startInternal
    // 启动Context子节点Wrapper(在server.xml中配置的)
    for (Container child : findChildren()) {
        if (!child.getState().isAvailable()) {
            child.start();
        }
    }

         启动监听器

        startInternal下面会调用listenerStart方法实例化并启动应用监听器

    // 实例化并启动应用监听器(ApplicationListener)
    if (ok) {
        if (!listenerStart()) {
            log.error(sm.getString("standardContext.listenerFail"));
            ok = false;
        }
    }

         listenerStart方法内部找出ServletContextListener类型的监听器,调用contextInitialized方法,这个方法就是我们Spring框架的启动点。

    public boolean listenerStart() {
        // 其余代码
        // 遍历所有监听器找出ServletContextListener类别的
        for (Object instance : instances) {
            if (!(instance instanceof ServletContextListener)) {
                continue;
            }
            ServletContextListener listener = (ServletContextListener) instance;
            try {
                fireContainerEvent("beforeContextInitialized", listener);
                // 调用其contextInitialized方法
                if (noPluggabilityListeners.contains(listener)) {
                    listener.contextInitialized(tldEvent);
                } else {
                    listener.contextInitialized(event);
                }
                fireContainerEvent("afterContextInitialized", listener);
            } 
            // 其余代码
        }
    }

         ContextLoaderListener继承了ServletContextListener,其contextInitialized将会启动Spring框架。

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
	public ContextLoaderListener() {}

	public ContextLoaderListener(WebApplicationContext context) {super(context);}

	@Override
	public void contextInitialized(ServletContextEvent event) {
		initWebApplicationContext(event.getServletContext());
	}

	@Override
	public void contextDestroyed(ServletContextEvent event) {
		closeWebApplicationContext(event.getServletContext());
		ContextCleanupListener.cleanupAttributes(event.getServletContext());
	}
}

        loadOnStartup

        在web.xml中我们对于需要启动时加载的servlet会配置<load-on-startup>1</load-on-startup>,这个功能的实现也是在StandardContext#startInternal中完成的。

    // StandardContext#startInternal
    if (ok) {
       if (!loadOnStartup(findChildren())) {
            log.error(sm.getString("standardContext.servletFail"));
            ok = false;
         }
    }

        可以看到内部实现是交给了对应wrapper的load方法完成的,其具体内容我们下文介绍StandardWrapper时介绍。

    public boolean loadOnStartup(Container children[]) {
        // 对于设置了<load-on-startup>1</load-on-startup>的servlet调用wrapper的load方法
        TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();
        for (Container child : children) {
            Wrapper wrapper = (Wrapper) child;
            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);
        }

        for (ArrayList<Wrapper> list : map.values()) {
            for (Wrapper wrapper : list) {
                try {
                    wrapper.load();
                } 
            }
        }
        return true;

    }

五、StandardWrapper

        StandardWrapper 没有重载initInternal 方法,我们从startInternal看起

        1、startInternal

        和其他容器差不多,唯一不同的是多了一个setAvailable。

    protected synchronized void startInternal() throws LifecycleException {
        if (this.getObjectName() != null) {
            Notification notification = new Notification("j2ee.state.starting", this.getObjectName(), sequenceNumber++);
            broadcaster.sendNotification(notification);
        }
        super.startInternal();
        // 方法设置 available 属性的值。
        setAvailable(0L);
        if (this.getObjectName() != null) {
            Notification notification = new Notification("j2ee.state.running", this.getObjectName(), sequenceNumber++);
            broadcaster.sendNotification(notification);
        }

    }

        从 available 的注释可以看出,它的作用是表示 Servlet 的可用时间的。 

    /**
     * The date and time at which this servlet will become available (in milliseconds since the epoch), or zero if the
     * servlet is available. If this value equals Long.MAX_VALUE, the unavailability of this servlet is considered
     * permanent.
     */
    protected long available = 0L;

        2、load

        在上文中,讲到了Context的startInternal 方法中做了一件事情就是调用 Wrapper 的 load 方法(在 StandardContext#loadOnStartup 中调用的)。在 StandardContext#startInternal 中先调用 Wrapper的 start 方法,然后调用 Wrapper 的 load 方法。

        load 方法逻辑很简单,先调用 loadServlet() 获取一个 Servlet 对象,就是通过 servletClass 属性指定的类名,调用 InstanceManager#newInstance(servletClass) 方法来创建一个 Servlet 对象。然后调用 initServlet(instance) 来初始化这个 Servlet 对象,也就是调用这个 Servlet 对象的 init 方法。

        可以看出 Wrapper 里有一个 Servlet 属性,Wrapper 正是对 Servlet 的包装。

    public synchronized void load() throws ServletException {
        // 创建当前Servlet 对象
        instance = loadServlet();
        if (!instanceInitialized) {
            // 调用Servlet对象的init方法来初始化
            initServlet(instance);
        }
        // 其余代码
    }

        至此,整个容器的启动过程就介绍完了,可以看到整个流程是由Server起步直到Wrapper结束。

        其中Server代表的是整个tomcat应用,Service代表的是server.xml中的service节点。而后续的Engine与Host都是service中的子节点。

        再到Context代表了webapps下的每个应用,子容器Wrapper表示web应用中的每个servlet。Context中的start方法中会创建当前web应用的ServletContext,并启动ServletContextListener监听器,启动web应用(典型的如Spring容器的启动点就是ContextLoaderListener这个ServletContextListener的实现类)。并对于需要启动时加载的servlet(loadOnStartup=1)调用其对应wrapper的load方法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/417793.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++STL精讲】vector的模拟实现

文章目录&#x1f490;专栏导读&#x1f490;文章导读&#x1f337;定义vector类&#x1f337;各成员函数的实现&#x1f33a;构造函数&#x1f33a;迭代器&#x1f33a;size与capacity——求大小与容量&#x1f33a;reserve——扩容关于reserve中的深浅拷贝问题&#x1f33a;r…

RK3399平台开发系列讲解(基础篇)ADC 使用方法

🚀返回专栏总目录 文章目录 一、ADC 的 DTS 配置二、ADC 驱动说明2.1、获取 AD 通道2.2、读取 AD 采集到的原始数据2.3、计算采集到的电压三、接口说明沉淀、分享、成长,让自己和他人都能有所收获!😄 📢内核采用工业 I/O 子系统来控制 ADC,该子系统主要为 AD 转换或者…

Day920.结构化日志业务审计日志 -SpringBoot与K8s云原生微服务实践

结构化日志&业务审计日志 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于结构化日志&业务审计日志的内容。 1、什么是结构化日志 结构化日志&#xff08;Structured Logging&#xff09;是一种将日志信息组织为结构化数据的技术。 传统的日志通常是一些文…

浏览器前进与后退的秘密——栈 (栈的理解与实现)

文章目录前言&#xff1a;浏览器与栈的纠缠如何理解“栈”&#xff1f;如何实现一个“栈”&#xff1f;基于数组的顺序栈基于链表的链式栈解答开篇&#x1f431;‍&#x1f409;作者简介&#xff1a;大家好&#xff0c;我是黑洞晓威&#xff0c;一名大二学生&#xff0c;希望和…

0010_C++语言_简单的C++程序

系列文章目录 0001_C语言_C入门 文章目录 系列文章目录 前言 一、写一个简单的C程序 二、来看看这个程序 1.返回类型 2.函数名 3.形参列表 4.函数体 总结 前言 期中考试完毕&#xff01;&#xff08;在此祝宫野艾莲娜小姐考得很好&#xff01;&#xff09; 今天来看一个简单…

AXI write data在Write data channel的排布

前几天帮一位同事分析了下write data在AXI write data channel上排布&#xff0c;想想还是记录一下&#xff0c;方便日后复习。我们先来看一张wdata排布图&#xff0c;灰色单元表示该Byte没有被传输。 第一次看这张图的时候&#xff0c;是否有感觉疑惑&#xff1a; address为0…

Mysql-锁机制

Mysql的锁机制 一、简介 锁是为了保证数据库中数据的一致性&#xff0c;使各种【共享资源】在被访问时变得【有序】而设计的一种规则。 tip MysQL中不同的存储引擎支持不同的锁机制&#xff1a; InoDB支持【行锁】&#xff0c;有时也会升级为表锁。MyIsam只支持表锁。 【表锁…

实验6 TensorFlow基础

1. 实验目的 掌握TensorFlow低阶API&#xff0c;能够运用TensorFlow处理数据以及对数据进行运算。 2.实验内容 ①实现张量维度变换&#xff0c;部分采样等&#xff1b; ②实现张量加减乘除、幂指对数运算&#xff1b; ③利用TensorFlow对数据集进行处理。 3.实验过程 题目…

UE4 几种常见的项目优化方式

1.灯光范围优化 当屏幕某一块像素被多盏灯光所影响&#xff0c;那么也会拖慢帧率&#xff0c;可以打开灯光复杂度视图进行查看&#xff0c;屏幕上越红的地方灯光复杂度越高&#xff0c;尝试降低灯光半径可以解决&#xff1a; 2.材质纹素优化 有时候我们并不知道目标模型的…

Vue:自定义组件事件

一、直接在组件标签上绑定事件 1、关于组件的自定义事件&#xff0c;实现步骤&#xff1a; ①提供事件源&#xff08;这个事件源是一个组件&#xff09;②给组件绑定事件&#xff1a;v-on:事件名 或者 事件名 ③编写回调函数&#xff0c;将回调函数和事件进行绑定。 ④等…

NeRF:今年最火的AI技术

转载&#xff1a; https://mp.weixin.qq.com/s?__bizMzIxOTczOTM4NA&mid2247549727&idx2&snc59e18cb2bd8dfe34eafdc6e7cf9f4fd&chksm97d4e688a0a36f9ea0ed4c5a988a3802b4e9265655c5c0a6f208a396860f3842abae9be3a9a3&scene27 什么是NeRF&#xff1f; Ne…

springboot解决ajax跨域问题

方式1:使用CrossOrigin注解方式 CrossOrigin(origins "*") //解决跨域的关键注解,该注解可以标注在类上也可以标注在方法上域A&#xff1a;http://localhost/across_test/test.html 域A下的test.html <!DOCTYPE html> <html lang"en"> <h…

Three——一、详解基础场景搭建(结尾含完整代码)

速成Three.js——一、详解基础场景搭建(结尾含源码) 给模型添加标签时需要准标签部分 从本章开始会从最初的搭建场景模型开始到插入精灵图部分结尾&#xff0c;便于刚入门three而不知如何去学起的前端工程师去学习&#xff0c;这里可以学到场景搭建的基础知识&#xff0c;引入…

【微服务笔记12】微服务组件之Hystrix和OpenFeign结合使用

这篇文章&#xff0c;主要介绍微服务组件之Hystrix和OpenFeign结合使用。 目录 一、Hystrix和OpenFeign结合使用 1.1、引入依赖 1.2、openfeign启用hystrix 1.3、编写FeignClient接口 1.4、编写FeignClient实现类 1.5、编写Controller控制器 1.6、启动类启动OpenFeign …

Apache网页的优化,安全与防盗链

在企业中&#xff0c;部署Apache后只采用默认的配置参数&#xff0c;会引发网站很多问题&#xff0c;换言之默认配置是针对以前较低的服务器配置的&#xff0c;以前的配置已经不适用当今互联网时代。 为了适应企业需求&#xff0c;就需要考虑如何提升Apache的性能与稳定性&…

智慧水务系统-全域孪生驾驶舱-三维实景模型

平台概述 柳林智慧水务系统平台是以物联感知技术、大数据、智能控制、云计算、人工智能、数字孪生、AI算法、虚拟现实技术为核心&#xff0c;以监测仪表、通讯网络、数据库系统、数据中台、模型软件、前台展示、智慧运维等产品体系为支撑&#xff0c;以城市水资源、水生态、水…

LeetCode-152. 乘积最大子数组

目录思路动态规划题目来源 152. 乘积最大子数组 思路 这题跟LeetCode-53. 最大子数组和很像 最后把整个 dp 数组看一遍求最大值即可。因此状态转移方程可能是&#xff1a; dp[i] Math.max(dp[i-1]nums[i],nums[i]);说明&#xff1a;牢记状态的定义&#xff0c;一定以下标 i…

Android13 PMS是如何启动的?

作者&#xff1a;Arthas0v0 平常使用安卓实际就是在使用各种app&#xff0c;而下载的app实际是一个apk文件。这个apk文件的安装就交给了PackageManagerService来实现。PackageManagerService的启动也是在SystemServer中。这个过程比较长需要长一点的时间来理。 SystemServer.s…

2023-04-09 有向图及相关算法

有向图及相关算法 1 有向图的实现 有向图的的应用场景 社交网络中的关注互联网连接程序模块的引用任务调度学习计划食物链论文引用无向图是特殊的有向图&#xff0c;即每条边都是双向的 改进Graph和WeightedGraph类使之支持有向图 Graph类的改动WeightedGraph类的改动 2 …

肖 sir_就业课__004项目流程(H模型)

项目流程&#xff1a; 一、面试提问&#xff08;h模型&#xff09; 1、你说下你们公司测试流程&#xff1f; 2、给你一个需求你会怎么做? 3、你讲下你的工作&#xff1f; 4、谈谈你是如何去测试&#xff1f; 答案&#xff1a;h模型 要求第一人称来写 讲解简化文字流程&#x…