Tomcat-Context,ContextConfig和Wrapper
StandardContext、ContextConfig以及StandardWrapper的核心逻辑源码解析及总结
Context
Context
是 Tomcat 中最关键的容器组件,默认实现为 StandardContext
。每一个 Context
实例代表一个独立的 Web 应用。在内部,Context
承担了大量与 Web 应用生命周期相关的工作,比如类加载器的初始化、web.xml
的解析、Servlet
/Filter
/Listener
的注册与实例化等。
每个 Context
对应一个唯一的 ServletContext
,用于代表当前 Web 应用的上下文环境。通过 StandardContext
的 startInternal
方法,我们可以深入了解 Tomcat 是如何逐步构建一个 Web 应用的,并是在哪一步触发Spring启动的。
start核心流程总结
- 初始化和设置类加载器
- 设置StandardContext.loader为WebappLoader(和当前Context的使用的类加载器有关)
- 启动WebappLoader(内部会创建ParallelWebappClassLoader)
- 切换当前线程的类加载器为上述的ParallelWebappClassLoader,为后续类的隔离加载做准备(尤其是 SPI 加载)
- 触发配置解析流程
- 发布
Lifecycle.CONFIGURE_START_EVENT
事件,触发内部的 ContextConfig 监听器 - ContextConfig开始解析当前项目的web.xml和其他jar包里的web-fragment.xml,并将解析后的结果合并放到StandardContext中(比如将servlet配置构造成Warpper添加到Context中作为其子容器)
- 发布
- tomcat 内部组件设置与启动
- 启动Warpper(StandardWrapper启动阶段不会做什么,不是实例化关联的Servlet)
- 触发当前contxet的Pipeline#start
- 创建StandardManager作为session的默认管理器
- 实例和初始化关键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()中,被调用的链路为:
- StandardContext#startInternal()中触发Lifecycle.CONFIGURE_START_EVENT事件
- ContextConfig#lifecycleEvent
- ContextConfig#configureStart
- ContextConfig#webConfig
webConfig()核心逻辑
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/目录)包里
Servlet 3.0特性支持
识别并加载 ServletContainerInitializer(WEB-INF/lib/*.jar!/META-INF/services/javax.servlet.ServletContainerInitializer)
使用字节码技术解析项目class和jar包里的@WebServlet, @WebFilter, @WebListener,@HandlesType等注解,避免无用的class被加载进JVM
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();
}
}
}