Tomcat-Context,ContextConfig和Wrapper


​ StandardContext、ContextConfig以及StandardWrapper的核心逻辑源码解析及总结

Context

Context 是 Tomcat 中最关键的容器组件,默认实现为 StandardContext每一个 Context 实例代表一个独立的 Web 应用。在内部,Context 承担了大量与 Web 应用生命周期相关的工作,比如类加载器的初始化、web.xml 的解析、Servlet/Filter/Listener 的注册与实例化等

每个 Context 对应一个唯一的 ServletContext,用于代表当前 Web 应用的上下文环境。通过 StandardContextstartInternal 方法,我们可以深入了解 Tomcat 是如何逐步构建一个 Web 应用的,并是在哪一步触发Spring启动的。

start核心流程总结

  1. 初始化和设置类加载器
    • 设置StandardContext.loader为WebappLoader(和当前Context的使用的类加载器有关)
    • 启动WebappLoader(内部会创建ParallelWebappClassLoader)
    • 切换当前线程的类加载器为上述的ParallelWebappClassLoader,为后续类的隔离加载做准备(尤其是 SPI 加载)
  2. 触发配置解析流程
    • 发布 Lifecycle.CONFIGURE_START_EVENT 事件,触发内部的 ContextConfig 监听器
    • ContextConfig开始解析当前项目的web.xml和其他jar包里的web-fragment.xml,并将解析后的结果合并放到StandardContext中(比如将servlet配置构造成Warpper添加到Context中作为其子容器)
  3. tomcat 内部组件设置与启动
    • 启动Warpper(StandardWrapper启动阶段不会做什么,不是实例化关联的Servlet)
    • 触发当前contxet的Pipeline#start
    • 创建StandardManager作为session的默认管理器
  4. 实例和初始化关键Servlet相关组件
    • 调用ServletContainerInitializer#onStartup
    • 实例化并调用ServletContextListener#contextInitialized(Spring + Tomcat组合时用到的ContextLoaderListener就会在这时启动。所以,这里就是spring开始实例化的开始)
    • 调用Manager#start(内部会恢复上一次context中持久化的session)
    • 实例化所有 Filter,并调用其init()方法
    • 实例全部loadOnStartup >= 0的Servlet,并调用其init()方法

核心源码

public class StandardContext extends ContainerBase
        implements Context, NotificationEmitter {

    public StandardContext() {

        super();
        pipeline.setBasic(new StandardContextValve());
    }

    // web.xml里的listener标签里配置的各种监听器className
    private String applicationListeners[] = new String[0];

    // ServletContextListener集和
    private final Set<Object> noPluggabilityListeners = new HashSet<>();

    // ServletContext实现
    protected ApplicationContext context = null;

    // 在当前tomcat实例中,是否允许javax.servlet.ServletContext#getContext方法跨context获取当前context的ServletContext
    private boolean crossContext = false;

    // 当前web的path(即 URL 前缀)
    private String path = null;

    // Filter 实例与其配置类之间的映射(FilterConfig),用于实际调用
    private HashMap<String, ApplicationFilterConfig> filterConfigs = new HashMap<>();
    // Filter配置的抽象集和(在wel.xml里配置的Filter)
    private HashMap<String, FilterDef> filterDefs = new HashMap<>();
    // 默认为StandardManager(管理和持久化Session)
    protected Manager manager = null;
    // 是否开启可重新加载的检测
    // 为true时,当当前环境的Class文件或jar有改变时(增加或修改),会重新加载当前Context
    private boolean reloadable = false;
    // Servlet 映射关系,key 为 URL pattern,value 为 Servlet 名称
    private HashMap<String, String> servletMappings = new HashMap<>();
    // session超时事件(分钟单位)
    private int sessionTimeout = 30;
    // ============ Cookie相关配置 ================
    private String sessionCookieName;
    private boolean useHttpOnly = true;
    private String sessionCookieDomain;
    private String sessionCookiePath;
    // 是否在cookie1路径最后面添加/,默认否(比如路径为/foo,避免请求/foobar也带上这个cookie)
    private boolean sessionCookiePathUsesTrailingSlash = false;
    // 是否接受客户端提供的(但服务端不存在的)Session ID 并创建新的 Session。设为false和context path为 / 才会生效
    private boolean validateClientProvidedNewSessionId = true;

    /**
     * start方法,可以说是整个tomcat中最关键的部分
     */
    protected synchronized void startInternal() throws LifecycleException {

        boolean ok = true;

        // 初始化资源对象:WebResourceRoot(提供静态资源访问、JAR管理等),Loader会用到
        if (getResources() == null) {
            try {
                setResources(new StandardRoot(this));
            } catch (IllegalArgumentException e) {
                ok = false;
            }
        }
        if (ok) {
            // 启动资源对象,确保资源可用
            resourcesStart();
        }

        // 设置Loader。这时当前组件的state为STARTING_PREP,不会触发Loader的start
        if (getLoader() == null) {
            WebappLoader webappLoader = new WebappLoader();
            webappLoader.setDelegate(getDelegate());
            setLoader(webappLoader);
        }

        // 初始化 Cookie 解析器(符合 RFC6265)
        if (cookieProcessor == null) {
            cookieProcessor = new Rfc6265CookieProcessor();
        }

        // 绑定ClassLoader到当前线程,并返回旧的classLoader
        ClassLoader oldCCL = bindThread();

        try {
            if (ok) {
                // 启动 WebappLoader,内部会创建 ParallelWebappClassLoader
                Loader loader = getLoader();
                if (loader instanceof Lifecycle) {
                    ((Lifecycle) loader).start();
                }

                unbindThread(oldCCL);
                // 将ParallelWebappClassLoader绑定到当前线程上线文里,作为当前context的类加载器
                oldCCL = bindThread();

                // 通知 ContextConfig 进行 web.xml(当前项目)、web-fragment.xml(其他依赖jar包) 等配置解析和组合
                fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);

                // ================ 走到这,xml里的所有Servlet,Filter,Listener等配置都解析完毕,放置到当前Context内部中了

                // 启动所有Wrapper(内部基本不会做什么)
                for (Container child : findChildren()) {
                    if (!child.getState().isAvailable()) {
                        child.start();
                    }
                }

                // 启动当前context的pipeline
                if (pipeline instanceof Lifecycle) {
                    ((Lifecycle) pipeline).start();
                }

                // 创建并设置 Session 管理器(如 StandardManager)
                Manager contextManager = new StandardManager();
                if (contextManager != null) {
                    setManager(contextManager);
                }
            }

            // 检查配置是否成功(由 ContextConfig 设置)
            if (!getConfigured()) {
                log.error(sm.getString("standardContext.configurationFail"));
                ok = false;
            }

            // 调用所有的ServletContainerInitializer
            for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry : initializers.entrySet()) {
                try {
                    entry.getKey().onStartup(entry.getValue(),
                            getServletContext());
                } catch (ServletException e) {
                    ok = false;
                    break;
                }
            }

            if (ok) {
                // 调用ServletContextListener的contextInitialized方法
                if (!listenerStart()) {
                    log.error(sm.getString("standardContext.listenerFail"));
                    ok = false;
                }
            }

            try {
                // 启动 Session 管理器
                Manager manager = getManager();
                if (manager instanceof Lifecycle) {
                    ((Lifecycle) manager).start();
                }
            } catch (Exception e) {
                ok = false;
            }

            // 实例化并配置好当前context的全部Filter,并触发其init方法
            if (ok) {
                if (!filterStart()) {
                    ok = false;
                }
            }

            // 加载并实例化各种loadOnStartup > 0 的Servlet(并执行init方法)
            if (ok) {
                if (!loadOnStartup(findChildren())) {
                    ok = false;
                }
            }

            // 默认不会启动Context的backgroundProcessor(由Engine处理)
            super.threadStart();
        } finally {
            // 恢复线程上下文类加载器
            unbindThread(oldCCL);
        }

        // 清理 Web 资源缓存(避免 JAR 文件句柄未释放)
        getResources().gc();

        // 设置最终状态
        if (!ok) {
            setState(LifecycleState.FAILED);
            // Send j2ee.object.failed notification
            if (this.getObjectName() != null) {
                Notification notification = new Notification("j2ee.object.failed",
                        this.getObjectName(), sequenceNumber.getAndIncrement());
                broadcaster.sendNotification(notification);
            }
        } else {
            setState(LifecycleState.STARTING);
        }
    }
}

ContextConfig

​ 当 HostConfig 实例化并部署 Context 时,会自动注册 ContextConfig 作为 Context 的一个 LifecycleListener其主要职责是在 Context 启动(startInternal())期间解析各种配置(web.xml、注解、SCI 等),并将解析结果装配到 StandardContext 中,以保证 Web 应用能完整启动

​ 核心方法主要在ContextConfig#webConfig()中,被调用的链路为:

  1. StandardContext#startInternal()中触发Lifecycle.CONFIGURE_START_EVENT事件
  2. ContextConfig#lifecycleEvent
  3. ContextConfig#configureStart
  4. ContextConfig#webConfig

webConfig()核心逻辑

  1. web.xml解析

    • ${catalina.base}/conf/web.xml:server级别,即全局级别
    • ${catalina.base}/conf/Catalina/{hostname}/web.xml.default:host级别
    • WEB-INF/web.xml:当前context级,在context根目录下
    • WEB-INF/lib/*.jar!/META-INF/web-fragment.xml:fragment级别,当前web的依赖jar(WEB-INF/lib/目录)包里
  2. Servlet 3.0特性支持

    • 识别并加载 ServletContainerInitializer(WEB-INF/lib/*.jar!/META-INF/services/javax.servlet.ServletContainerInitializer)

    • 使用字节码技术解析项目class和jar包里的@WebServlet, @WebFilter, @WebListener,@HandlesType等注解,避免无用的class被加载进JVM

  3. web.xml合并注册

    • 将第1步里解析到的全部WebXml按优先级合并为一个WebXml对象,优先级: context > fragment > host > server
    • 合并后的WebXml注册到StandardContext中(Filter、Listener、Wrapper、Session config、欢迎页等)

webConfig()源码

protected void webConfig() {
    // web.xml的解析器
    WebXmlParser webXmlParser = new WebXmlParser(context.getXmlNamespaceAware(),
            context.getXmlValidation(), context.getXmlBlockExternal());

    Set<WebXml> defaults = new HashSet<>();

    // 1. 解析全局默认的web.xml,即conf/web.xml文件
    // 2. 解析host级别默认的web.xml,即conf/Catalina/{hostname}/web.xml.default 文件
    defaults.add(getDefaultWebXmlFragment(webXmlParser));

    // web.xml的解析结果
    WebXml webXml = createWebXml();

    // 将当前context级别的 WEB-INF/web.xml文件解析到webXml对象里
    InputSource contextWebXml = getContextWebXmlSource();
    if (!webXmlParser.parseWebXml(contextWebXml, webXml, false)) {
        ok = false;
    }

    ServletContext sContext = context.getServletContext();

    // 将Web应用中的所有jar包(即WEB-INF/lib/ 目录)里的META-INF/web-fragment.xml 解析为一个个的WebXml对象
    Map<String,WebXml> fragments = processJarsForWebFragments(webXml, webXmlParser);

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

    // 实例化META-INF/services/javax.servlet.ServletContainerInitializer文件里的SCI
    if (ok) {
        processServletContainerInitializers();
    }

    if  (!webXml.isMetadataComplete() || typeInitializerMap.size() > 0) {
        // 1. 使用字节码方式解析class文件并处理相关注解(@WebServlet, @WebFilter, @WebListener等)
        // 2. 找出符合 @HandlesType 的类
        processClasses(webXml, orderedFragments);
    }

    if (!webXml.isMetadataComplete()) {
        if (ok) {
            // 先合并jar包里web-fragment.xml配置到当前context的web.xml里
            ok = webXml.merge(orderedFragments);
        }

        // 再将全局默认和host的合并到当前context的web.xml里
        webXml.merge(defaults);

        if (ok) {
            convertJsps(webXml);
        }

        if (ok) {
            // 将合并完成的web.xml信息注册到当前StandardContext中
            configureContext(webXml);
        }
    } else {
        webXml.merge(defaults);
        convertJsps(webXml);
        configureContext(webXml);
    }

    if (ok) {
        // 暴露出jar包中META-INF/resources/的静态资源,让其可以直接通过url访问
        Set<WebXml> resourceJars = new LinkedHashSet<>(orderedFragments);
        for (WebXml fragment : fragments.values()) {
            if (!resourceJars.contains(fragment)) {
                resourceJars.add(fragment);
            }
        }
        processResourceJARs(resourceJars);
    }

    // 注册 ServletContainerInitializer 到 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());
            }
        }
    }
}

Wrapper

​ Wrapper即Servlet,默认实现为StandardWrapper,其主要任务为:

  • 管理 Servlet 的 生命周期(加载、初始化、分配和销毁)
  • 支持 线程安全策略(如 SingleThreadModel

Servlet 的实例化不依赖 StandardWrapper的 init()start() 方法,而是由 loadOnStartup字段决定。也就没必要分析着两个生命周期方法

  • loadOnStartup >= 0:在应用启动阶段就会实例化并初始化 Servlet(StandardContext内的start阶段)
  • loadOnStartup < 0(默认):延迟到首次请求时才实例化

​ 值得关注allocate()方法,这是在http请求到来时分配当前Wrapper的Servlet方法,其实现了SingleThreadModel(即STM,尽管已被废弃)逻辑的支持。内部利用栈进行缓存,让每个运行在当前STM Servlet里的线程能独享一个Servlet,默认最多20个缓存,到达最大数量时当前请求会被阻塞,只能等deallocate()方法(归还Servlet)来唤醒。

​ 所以,可以得出一个关键结论:对STM Servlet来说,其http请求的最大并发数为Min(StandardWrapper.maxInstances, tomcat的worker线程池最大线程数)

核心源码

public class StandardWrapper extends ContainerBase
        implements ServletConfig, Wrapper, NotificationEmitter {
    public StandardWrapper() {
        // 向pipeline添加StandardWrapperValve,这个valve主要实例化servlet并构造filter来处理请求
        swValve = new StandardWrapperValve();
        pipeline.setBasic(swValve);

    }

    // servlet实例(针对多线程共用时的)
    protected volatile Servlet instance = null;
    // 当前servlet是否已经初始化(就是调用了init接口)
    protected volatile boolean instanceInitialized = false;

    protected int loadOnStartup = -1;
    // 当前servlet的InitParam参数缓存
    protected HashMap<String, String> parameters = new HashMap<>();
    // 是否是单线程模式。默认false,表示为单例Servlet。如果为true,代表Servlet在每个运行的线程中是不同的,即多例
    protected volatile boolean singleThreadModel = false;
    // 单线程模式下默认最多20个单线程servlet
    protected int maxInstances = 20;
    // 单线程模式下servlet的实例树
    protected int nInstances = 0;
    // 单线程模式下基于栈实现的servlet实例池
    protected Stack<Servlet> instancePool = null;

     /**
     * 根据singleThreadModel采用不同的分配策略,进行Servlet实例的分配
     */
    public Servlet allocate() throws ServletException {


        boolean newInstance = false;

        // 非STM的,每次都返回相同的servlet(多线程共用这个对象)
        if (!singleThreadModel) {
            if (instance == null || !instanceInitialized) {
                synchronized (this) {
                    if (instance == null) {
                        try {
                            // 实例化
                            instance = loadServlet();
                            newInstance = true;
                            if (!singleThreadModel) {
                                countAllocated.incrementAndGet();
                            }
                        } catch (ServletException e) {
                            throw e;
                        } catch (Throwable e) {
                            ExceptionUtils.handleThrowable(e);
                            throw new ServletException(sm.getString("standardWrapper.allocate"), e);
                        }
                    }
                    // 如果未初始化,则调用Servlet#init方法
                    if (!instanceInitialized) {
                        initServlet(instance);
                    }
                }
            }
            // 再次检测STM(Servlet第一次实例化时才会设置这个值),如果是STM则先入栈(即缓存)
            if (singleThreadModel) {
                if (newInstance) {
                    synchronized (instancePool) {
                        instancePool.push(instance);
                        nInstances++;
                    }
                }
            } else {
                // 非STM则直接返回
                if (!newInstance) {
                    countAllocated.incrementAndGet();
                }
                return instance;
            }
        }
        // ================== 走到这,表示为STM Servlet ==================
        synchronized (instancePool) {
            while (countAllocated.get() >= nInstances) { // 缓存的STM Servlet不够分
                if (nInstances < maxInstances) {
                    // 没到上限,则进行实例化
                    try {
                        instancePool.push(loadServlet());
                        nInstances++;
                    } catch (ServletException e) {
                        throw e;
                    } catch (Throwable e) {
                        ExceptionUtils.handleThrowable(e);
                        throw new ServletException(sm.getString("standardWrapper.allocate"), e);
                    }
                } else {
                    try {
                        // 超过上限,则阻塞等待归还Servlet时的唤醒
                        instancePool.wait();
                    } catch (InterruptedException e) {
                        // Ignore
                    }
                }
            }
            // 增加servlet正在使用的数量
            countAllocated.incrementAndGet();
            return instancePool.pop();
        }
    }

     /**
     * 释放已使用完的Servlet实例
     */
    public void deallocate(Servlet servlet) throws ServletException {

        if (!singleThreadModel) {
            countAllocated.decrementAndGet();
            return;
        }

        // 归还到栈中,让其可以重复使用
        synchronized (instancePool) {
            countAllocated.decrementAndGet();
            instancePool.push(servlet);
            instancePool.notify();
        }

    }
}