Tomcat源码笔记(八)Context

58 篇文章 0 订阅
8 篇文章 1 订阅

在上节中,大致过了一遍Engine到Host的启动过程,重点我认为有以下几点

 

1、Engine容器启动了一个线程间隔10秒执行各子容器的backgroundProcess()方法,并发布periodic周期事件。默认配置下只有Engine启动后台任务线程,子容器Host/Context不启动这个线程,可以修改server.xml配置文件为容器增加backgroundProcessorDelay=20 的参数配置,则该容器会创建一个新的周期任务线程,之后该容器及子容器的后台backgroundProcess()任务则由该线程执行,20是周期(秒)

2、Host启动,将server.xml中配置的Context信息,还有webapp/文件夹下的war包,应用文件夹都解析为一个StandardContext,进行部署和Context的启动。同时也监听context的配置文件,监听到文件被修改,则在backgroundProcess()周期任务中重新部署

一个Context代表一个应用,下边逐行代码分析启动一个web应用时tomcat都做了什么工作

目录

ContextConfig

init

beforeStart

StandardContext

startInternal 

 ContextConfig

 configureStart

webConfig


ContextConfig

StandardContext是Context容器的默认实现类,启动时会先发布init_before /init_after/start_before/start_after事件,而ContextConfig就是StandardContext的生命周期监听器,先来看监听器在StandardContext.startInternal()之前进行的处理。

  @Override
    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;
        }

        // Process the event that has occurred
        if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
            configureStart();// 3333 startinternal 执行期间发布config_start事件触发
        } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
            beforeStart();// 2222 初始化后,startinternal 之前触发
        } else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
            // Restore docBase for management tools
            if (originalDocBase != null) {
                context.setDocBase(originalDocBase);
            }// 4444
        } else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) {
            configureStop();
        } else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) {
            init();// 1111 初始化结束事件
        } else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) {
            destroy();
        }
    }

init

从各个地方加载Context的配置,设置Context对象的属性

1、\tomcat8\conf\context.xml  默认情况下如下配置,设置了该Context对象监听的资源路径,如果下边两个路径的xml被修改,则reload该应用Context对象

 2、\tomcat8\conf\Catalina\localhost\context.xml.default  这默认没有,如果需要,则配置的context.xml是当前Host容器(localhost)下应用的通用属性

3、应用路径\META-INF\context.xml   各应用如有需要自行配置

    protected synchronized void init() {
        // xml文件解析器
        Digester contextDigester = createContextDigester();
        contextDigester.getParser();

        context.setConfigured(false);
        ok = true;

        contextConfig(contextDigester);
    }

    protected void contextConfig(Digester digester) {

        String defaultContextXml = null;

        // ========================1========================
        // Open the default context.xml file, if it exists
        if (context instanceof StandardContext) {
            defaultContextXml = ((StandardContext)context).getDefaultContextXml();
        }
        // set the default if we don't have any overrides
        if (defaultContextXml == null) {
            defaultContextXml = Constants.DefaultContextXml;
        }

        if (!context.getOverride()) {
            File defaultContextFile = new File(defaultContextXml);
            if (!defaultContextFile.isAbsolute()) {
                defaultContextFile =
                        new File(context.getCatalinaBase(), defaultContextXml);
            }
            if (defaultContextFile.exists()) {
                try {
                    URL defaultContextUrl = defaultContextFile.toURI().toURL();
                    processContextConfig(digester, defaultContextUrl);
                } catch (MalformedURLException e) {
                    log.error(sm.getString(
                            "contextConfig.badUrl", defaultContextFile), e);
                }
            }

            // ===========================2========================
            File hostContextFile = new File(getHostConfigBase(), Constants.HostContextXml);
            if (hostContextFile.exists()) {
                try {
                    URL hostContextUrl = hostContextFile.toURI().toURL();
                    processContextConfig(digester, hostContextUrl);
                } catch (MalformedURLException e) {
                    log.error(sm.getString(
                            "contextConfig.badUrl", hostContextFile), e);
                }
            }
        }

        // =============================3=======================
        if (context.getConfigFile() != null) {
            processContextConfig(digester, context.getConfigFile());
        }

    }

beforeStart

执行StandardContext.startInternal之前进行处理

1、调整docBase,根据配置重新设置docBse属性,在webapp下的应用设置为相对路径,不在的设置为绝对路径

2、Context参数antiResourceLocking,默认false,如果配置为true,则会建立临时文件夹(路径由系统参数java.io.tmpdir设置),复制该应用的所有文件到临时文件夹,启动时加载临时文件夹内容。

因为tomcat支持热部署,例如war包重新部署时需删除旧的文件夹,但可能某旧文件被锁定无法删除,所以此参数通过建立临时文件防止资源锁导致部署失败

    protected synchronized void beforeStart() {
        // 重新设置docBase
        try {
            fixDocBase();
        } catch (IOException e) {
            log.error(sm.getString(
                    "contextConfig.fixDocBase", context.getName()), e);
        }

        // 防止资源锁
        antiLocking();
    }

   protected void antiLocking() {
      if ((context instanceof StandardContext)
            && ((StandardContext) context).getAntiResourceLocking()) {
          // 如果设置参数为true ,获取临时文件目录
           if (originalDocBase.toLowerCase(Locale.ENGLISH).endsWith(".war")) {
                antiLockingDocBase = new File(
                        System.getProperty("java.io.tmpdir"),
                        deploymentCount++ + "-" + docBase + ".war");
            } else {
                antiLockingDocBase = new File(
                        System.getProperty("java.io.tmpdir"),
                        deploymentCount++ + "-" + docBase);
            }
            antiLockingDocBase = antiLockingDocBase.getAbsoluteFile();

            // 删除原临时文件
            ExpandWar.delete(antiLockingDocBase);

            // 复制所有文件到临时目录
            if (ExpandWar.copy(docBaseFile, antiLockingDocBase)) {
                context.setDocBase(antiLockingDocBase.getPath());
            }
      }
   }

StandardContext

startInternal 

启动大致的操作如下

1、设置/创建jsp临时work目录

2、封装和加载当前应用的各种资源路径,包括docBase、WEB-INF/lib下jar包路径等

3、创建当前应用的WebAppClassLoader并绑定到当前线程,用于加载/WEB-INF/classes和/WEB-INF/lib目录下的class

4、发布configure_start事件(这里由ContextConfig接入进行加载web.xml等配置文件获取Servlet/Filter等定义)

5、第四步加载完所有类型的配置,这一步是启动所有配置,调用SCI初始化、实例化Listener、Filter、Servlet等。普通web.xml配置Spring容器的启动就在Listener的启动这步,没什么太需要说的,底下没有详细列代码

6、容器通用操作,启动后台任务线程、发布已启动事件start

   @Override
    protected synchronized void startInternal() throws LifecycleException {
		// 。。。。
		
        setConfigured(false);
        boolean ok = true;

        // 创建work目录,示例:apache-tomcat-6.0.53\work\Catalina\localhost
		// jsp转为class的临时目录
        postWorkDirectory();

        // Add missing components as necessary
        if (getResources() == null) {   // (1) Required by Loader
            try {
                setResources(new StandardRoot(this));
            } catch (IllegalArgumentException e) {
                ok = false;
            }
        }
        if (ok) {
			// 启动StandardRoot对象
			// StandardRoot对象中封装当前web应用资源
			// 包括当前应用的项目class类路径、WEB-INF/lib下的jar包路径等
            resourcesStart();
        }

        // 创建Context的加载器,其中包含web应用的WebappClassLoader
        if (getLoader() == null) {
            WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
            webappLoader.setDelegate(getDelegate());
            setLoader(webappLoader);
        }

		// 。。。。。

        // 将当前的webappClassLoader设置到当前线程,返回原线程中的classLoader
        // 我debug执行,这里会返回null,因为此时webappClassLoader还未实例化
        ClassLoader oldCCL = bindThread();

        try {
            if (ok) {
                // 启动当前webAppLoader,主要是实例化当前应用的类加载器WebappClassLoader/ParallelWebappClassLoader
                Loader loader = getLoader();
                if (loader instanceof Lifecycle) {
                    ((Lifecycle) loader).start();
                }

                // 将原线程绑定的classLoader再恢复回来
                // 如果oldCCL为null,空执行
                unbindThread(oldCCL);

                // 启动了webAppLoader之后,将实例化后的WebappClassLoader绑定到当前线程,返回原线程的classLoader
                oldCCL = bindThread();
				
				// 。。。。
                }

                // 发布configure_start事件,触发ContextConfig中的方法执行加载web.xml等配置
                fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);

                // configure_start事件执行完后,此时已经加载了子容器Warpper,启动之
                for (Container child : findChildren()) {
                    if (!child.getState().isAvailable()) {
                        child.start();
                    }
                }

                // 。。。。
            }
            // 。。。

            // 调用ServletContainerInitializers,例如SpringBoot war包定义的启动类
            for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
                initializers.entrySet()) {
                try {
                    entry.getKey().onStartup(entry.getValue(), getServletContext());
                } catch (ServletException e) {
                    log.error(sm.getString("standardContext.sciFail"), e);
                    ok = false;
                    break;
                }
            }

            // 开始实例化Listener/Filter/Servlet
            // Configure and call application event listeners
            if (ok) {
                if (!listenerStart()) {
                    log.error(sm.getString("standardContext.listenerFail"));
                    ok = false;
                }
            }

            // Configure and call application filters
            if (ok) {
                if (!filterStart()) {
                    log.error(sm.getString("standardContext.filterFail"));
                    ok = false;
                }
            }

            // Load and initialize all "load on startup" servlets
            if (ok) {
                if (!loadOnStartup(findChildren())){
                    log.error(sm.getString("standardContext.servletFail"));
                    ok = false;
                }
            }

            // Start ContainerBackgroundProcessor thread
            super.threadStart();
        } finally {
            // Unbinding thread
            unbindThread(oldCCL);
        }

        // Reinitializing if something went wrong
        if (!ok) {
            setState(LifecycleState.FAILED);
        } else {
            setState(LifecycleState.STARTING);
        }
    }

 ContextConfig

 configureStart

在StandardContext启动中途会发布configure_start事件触发ContextConfig监听器的该方法执行,来加载web应用具体配置,加载web.xml、初始化容器等

  /**
     * Process a "contextConfig" event for this Context.
     */
    protected synchronized void configureStart() {
        // 解析/WEB-INF/web.xml
        webConfig();

        if (!context.getIgnoreAnnotations()) {
            applicationAnnotationsConfig();
        }
        if (ok) {
            validateSecurityRoles();
        }

        // Configure an authenticator if we need one
        // Tomcat对Servlet的访问提供的安全机制,和Realm配合使用
        // 简单的看就是web.xml中给Servlet配置能访问的角色role,在Realm组件中配置角色的用户名密码等信息(支持xml配置、jdbc等)
        // 在请求访问Servlet时会被tomcat拦截要求输入用户名密码,调用Realm组件核验无误后才允许访问
        // 没用过,好像不重要??
        if (ok) {
            authenticatorConfig();
        }

        // Make our application available if no problems were encountered
        if (ok) {
            context.setConfigured(true);
        } else {
            context.setConfigured(false);
        }
    }

webConfig

web应用配置的解析过程如下:

1、获取并加载路径 Tomcat目录/conf/web.xml 中通用的web.xml配置。其中配置了两个默认的Servlet,DefaultServlet和JspServlet,前者处理静态资源,后者处理后缀为 .jsp的请求(获取jsp文件,将其编译为Servlet,并执行其service方法返回静态页面)

2、获取应用目录/WEB-INF/web.xml 中的配置,解析为一个WebXml对象

3、解析web-fragment.xml,扫描ClassPath下(WebAppClassLoader及其父类能加载到的)所有的jar包的META-INF/web-fragment.xml文件。是Servlet3.0模块化新特性,xml配置与web.xml相同,即可以将通用配置配置在web-fragment.xml封入jar包,避免重复配置,具体可以百度用法

4、多个web-fragment.xml间的执行顺序在xml中可配置,这里按照顺序将多个xml对象排序。具体如何配置可以参考解析规则配置类WebRuleSet#addRuleInstances,博客参考Servlet3.0新特性之web-fragment.xml模块化配置文件 - 图图小淘气_real - 博客园

5、扫描ServletContainerInitializer实现类,这一步很重要,是不通过web.xml配置方式的入口,SpringBoot启动的时机就在这一步。ServletContainerInitializer 用于取代web.xml配置,使用编程风格注册Filter/Servlet等组件,使得编程框架(eg.spring)高度内聚,和容器Tomcat解耦。

原理:ServletContainerInitializer和HandlesTypes组合使用,ServletContainerInitializer是接口,定义如下,HandlesTypes是注解,表示ServletContainerInitializer实现类的处理对象类型,在Tomcat启动时由tomcat扫描所有class,将符合HandlesTypes配置的对象class作为参数c,然后调用该实现类进行初始化动作

public interface ServletContainerInitializer {
    void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;
}

接口实现类通过java SPI机制将实现类声明,例如spring-web.jar声明如下

 SpringServletContainerInitializer 声明如下,即表示SpringServletContainerInitializer类中只处理实现了WebApplicationInitializer接口的类。在Tomcat启动该Context时会查找应用下所有实现了WebApplicationInitializer的类,将类列表作为参数传入调用SpringServletContainerInitializer.onStartup方法配置容器。

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
	public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
			throws ServletException {
      // 实例化所有WebApplicationInitializer的实现类,挨个调用onStartup方法
}
}

public interface WebApplicationInitializer {
	void onStartup(ServletContext servletContext) throws ServletException;
}

我们熟悉的SpringBoot项目war包部署初始化类如下,父类SpringBootServletInitializer 就是实现了WebApplicationInitializer

public class ServletInitializer extends SpringBootServletInitializer {
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(DemoApplication.class);
    }
}

6、扫描当前应用和WEB-INF/lib下所有类,查找符合ServletContainerInitializer实现类配置的HandlesTypes的所有实现类。此外还有查找所有被WebServlet/WebFilter/WebListener注解的类,顾名思义也是个组件配置的路子,将这些类也作为配置添加到WebXml对象配置中

7、合并当前应用的WebXml对象和WEB-INF/lib下jar包的WebXml对象,把合并后的总配置信息挨个设置到StandardContext对象上

8、扫描WEB-INF/lib下jar包的META-INF/resources/路径,将静态资源也添加到当前Context

9、将扫描到的ServletContainerInitializer和HandlesTypes实现类配置到Context中

    protected void webConfig() {
        // web.xml的解析器,里边配置xml中各种元素的父子关系、解析规则等,和解析server.xml模式一致
        WebXmlParser webXmlParser = new WebXmlParser(context.getXmlNamespaceAware(),
                context.getXmlValidation(), context.getXmlBlockExternal());

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

        // ===========================2=================================
        WebXml webXml = createWebXml();

        // 开始解析,把WEB-INF/web.xml中的信息解析到WebXml对象中
        InputSource contextWebXml = getContextWebXmlSource();
        if (!webXmlParser.parseWebXml(contextWebXml, webXml, false)) {
            ok = false;
        }

        ServletContext sContext = context.getServletContext();

        // =============================3===================================
        Map<String,WebXml> fragments = processJarsForWebFragments(webXml, webXmlParser);

        // =============================4====================================
        Set<WebXml> orderedFragments = null;
        orderedFragments =
                WebXml.orderWebFragments(webXml, fragments, sContext);

        // =============================5====================================
        if (ok) {
            // 查当前应用和WEB-INF/lib下  SPI配置的SCI接口实现类
            processServletContainerInitializers();
        }

        // =============================6================================
        if  (!webXml.isMetadataComplete() || typeInitializerMap.size() > 0) {
            // 1、查当前应用和WEB-INF/lib下 SCI实现类配置的处理类型实现类
            // 2、查当前应用和WEB-INF/lib下 被WebServlet/WebFilter/WebListener注解的类,将其也当作配置添加到当前Context
            processClasses(webXml, orderedFragments);
        }

        // ================================7==============================
        if (!webXml.isMetadataComplete()) {
            // Step 6. Merge web-fragment.xml files into the main web.xml
            // file.
            if (ok) {
                ok = webXml.merge(orderedFragments);
            }

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

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

            // Step 9. Apply merged web.xml to Context
            if (ok) {
                configureContext(webXml);
            }
        } else {
            webXml.merge(defaults);
            convertJsps(webXml);
            configureContext(webXml);
        }

        // ===============================8==========================
        // Always need to look for static resources
        // Step 10. 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<>();
            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
        }

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值