Context组件表示一个Web应用,运行于一个特定的虚拟主机Host中。每个Web应用可以基于WAR文件,也可以基于相应的未打包目录。Catalina根据HTTP请求URI基于最长匹配选择处理该请求的Web应用,一旦选择,该Context就会根据定义好的servlet映射选择一个适当的servlet去处理。
Context组件
Context接口继承Container接口,StandardContext类是Container组件的默认实现,它继承ContainerBase基类并实现了Context接口,其构造函数和部分成员变量如下所示:
public class StandardContext extends ContainerBase implements Context, NotificationEmitter {
private static final Log log = LogFactory.getLog(StandardContext.class);
// ----------------------------------------------------------- Constructors
/**
* Create a new StandardContext component with the default basic Valve.
*/
public StandardContext() {
super();
pipeline.setBasic(new StandardContextValve());
broadcaster = new NotificationBroadcasterSupport();
// Set defaults
if (!Globals.STRICT_SERVLET_COMPLIANCE) {
// Strict servlet compliance requires all extension mapped servlets
// to be checked against welcome files
resourceOnlyServlets.add("jsp");
}
}
// ----------------------------------------------------- Instance Variables
/**
* Allow multipart/form-data requests to be parsed even when the
* target servlet doesn't specify @MultipartConfig or have a
* <multipart-config> element.
*/
protected boolean allowCasualMultipartParsing = false;
private boolean swallowAbortedUploads = true;
private Map<ServletContainerInitializer,Set<Class<?>>> initializers =
new LinkedHashMap<>();
private URL configFile = null;
private boolean configured = false;
protected ApplicationContext context = null;
private String encodedPath = null;
private String path = null;
private String docBase = null;
private boolean reloadable = false;
private boolean unpackWAR = true;
private boolean copyXML = false;
private boolean override = false;
private boolean swallowOutput = false;
// 省略一些代码
private boolean validateClientProvidedNewSessionId = true;
private boolean mapperContextRootRedirectEnabled = true;
private boolean mapperDirectoryRedirectEnabled = false;
private boolean useRelativeRedirects = !Globals.STRICT_SERVLET_COMPLIANCE;
private boolean dispatchersUseEncodedPaths = true;
private String requestEncoding = null;
private String responseEncoding = null;
private boolean allowMultipleLeadingForwardSlashInPath = false;
}
- 成员变量的含义可以参考Context的配置文档;
- Context组件的构造函数为自己的Pipeline添加了基本阀StandardContextValve,addChild方法只能添加Wrapper组件。
组件初始化
StandardContext类的initInternal方法如下:
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
// Register the naming resources
if (namingResources != null) {
namingResources.init();
}
// Send j2ee.object.created notification
if (this.getObjectName() != null) {
Notification notification = new Notification("j2ee.object.created",
this.getObjectName(), sequenceNumber.getAndIncrement());
broadcaster.sendNotification(notification);
}
}
- 调用基类ContainerBase的对应方法为自己创建线程池;
- 命名服务初始化;
- 发送JMX的j2ee.object.created通知。
组件启动
由于StandardContext类的startInternal方法代码较多,故此处省略。简而言之,Context组件启动时按顺序做了如下工作:
- 为该Context创建工作目录,如${catalina.base}/work/Engine名称/Host名称/Context的基本名称;
- 创建WebResourceRoot用于读取Web应用的资源;
- 创建Loader,Loader是ClassLoader的一种实现,可以按需重新加载;
- 初始化字符集映射;
- 启动Loader;
- 发布事件Lifecycle.CONFIGURE_START_EVENT;
- 启动子容器组件(即Wrapper组件);
- 执行各ServletContainerInitializer回调方法;
- 配置并初始化过滤器;
- 加载被标识为“启动加载”的servlet;
- 发布事件Lifecycle.START_EVENT。
ContextConfig监听器
Context组件里比较重要的生命周期事件监听器之一是ContextConfig监听器,Tomcat在解析server.xml时为Digester添加了ContextRuleSet规则,进而为StandardContext添加ContextConfig生命周期监听器(请参见本系列的Tomcat启动分析(二))。ContextConfig的主要作用是在Context组件启动时响应Context发布的事件。
成员变量
ContextConfig监听器重要的部分成员变量如下:
protected Context context = null;
protected String defaultWebXml = null;
protected boolean ok = false;
protected String originalDocBase = null;
protected final Map<ServletContainerInitializer, Set<Class<?>>> initializerClassMap =
new LinkedHashMap<>();
protected final Map<Class<?>, Set<ServletContainerInitializer>> typeInitializerMap =
new HashMap<>();
- context变量引用与之关联的Context组件;
- defaultWebXml变量表示默认的web.xml路径,若没有指定则是conf/web.xml;
- ok变量表示配置和启动阶段是否正常;
- originalDocBase变量表示Context的docBase;
- initializerClassMap和typeInitializerMap变量都与ServletContainerInitializer有关。
响应生命周期事件
ContextConfig类实现的lifecycleEvent方法如下:
@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();
} else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
beforeStart();
} else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
// Restore docBase for management tools
if (originalDocBase != null) {
context.setDocBase(originalDocBase);
}
} else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) {
configureStop();
} else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) {
init();
} else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) {
destroy();
}
}
- 首先将发布事件的Context组件保存到context变量;
- 然后针对不同事件执行不同的事件处理方法,比较重要的是Lifecycle.AFTER_INIT_EVENT和Lifecycle.CONFIGURE_START_EVENT两个事件。
初始化后事件
回忆LifecycleBase类的init方法,在调用initInternal方法后会调用setStateInternal更改组件的状态并发布LifecycleState.INITIALIZED枚举对应的事件,即初始化后事件Lifecycle.AFTER_INIT_EVENT。由于容器组件均继承自LifecycleBase类,因此StandardContext在执行initInternal方法后也会发布该事件,ContextConfig监听器对此事件的响应是执行init方法,该方法代码如下:
protected void init() {
// Called from StandardContext.init()
Digester contextDigester = createContextDigester();
contextDigester.getParser();
if (log.isDebugEnabled()) {
log.debug(sm.getString("contextConfig.init"));
}
context.setConfigured(false);
ok = true;
contextConfig(contextDigester);
}
- 首先创建了Digester对象;
- contextConfig方法利用上述Digester对象解析<Context>配置,参见Context配置文档的Defining a context一节。
配置开始事件
StandardContext在启动时会发布配置开始事件Lifecycle.CONFIGURE_START_EVENT,ContextConfig监听器对此事件的响应是执行configureStart方法,该方法代码如下:
protected synchronized void configureStart() {
// Called from StandardContext.start()
if (log.isDebugEnabled()) {
log.debug(sm.getString("contextConfig.start"));
}
if (log.isDebugEnabled()) {
log.debug(sm.getString("contextConfig.xmlSettings",
context.getName(),
Boolean.valueOf(context.getXmlValidation()),
Boolean.valueOf(context.getXmlNamespaceAware())));
}
webConfig();
if (!context.getIgnoreAnnotations()) {
applicationAnnotationsConfig();
}
if (ok) {
validateSecurityRoles();
}
// Configure an authenticator if we need one
if (ok) {
authenticatorConfig();
}
// Dump the contents of this pipeline if requested
if (log.isDebugEnabled()) {
log.debug("Pipeline Configuration:");
Pipeline pipeline = context.getPipeline();
Valve valves[] = null;
if (pipeline != null) {
valves = pipeline.getValves();
}
if (valves != null) {
for (int i = 0; i < valves.length; i++) {
log.debug(" " + valves[i].getClass().getName());
}
}
log.debug("======================");
}
// Make our application available if no problems were encountered
if (ok) {
context.setConfigured(true);
} else {
log.error(sm.getString("contextConfig.unavailable"));
context.setConfigured(false);
}
}
webConfig方法根据Servlet规范解析web.xml:
- 先找出Web应用jar内的web-fragment.xml并把它们排序;
- 查找ServletContainerInitializer接口的实现类;
- 处理/WEB-INF/classes下Web资源内的注解,如@HandlesTypes、@WebServlet、@WebFilter和@WebListener等;
- 处理jar内的注解,如@HandlesTypes、@WebServlet、@WebFilter和@WebListener等;
- 将web-fragment.xml和默认web-fragment.xml合并到web.xml;
- configureContext方法利用合并后的web.xml配置Context组件。
configureContext方法部分代码如下:
private void configureContext(WebXml webxml) {
// As far as possible, process in alphabetical order so it is easy to
// check everything is present
// Some validation depends on correct public ID
context.setPublicId(webxml.getPublicId());
// Everything else in order
context.setEffectiveMajorVersion(webxml.getMajorVersion());
context.setEffectiveMinorVersion(webxml.getMinorVersion());
for (Entry<String, String> entry : webxml.getContextParams().entrySet()) {
context.addParameter(entry.getKey(), entry.getValue());
}
// 省略一些代码
for (FilterDef filter : webxml.getFilters().values()) {
if (filter.getAsyncSupported() == null) {
filter.setAsyncSupported("false");
}
context.addFilterDef(filter);
}
for (FilterMap filterMap : webxml.getFilterMappings()) {
context.addFilterMap(filterMap);
}
// 省略一些代码
for (ServletDef servlet : webxml.getServlets().values()) {
Wrapper wrapper = context.createWrapper();
// Description is ignored
// Display name is ignored
// Icons are ignored
// jsp-file gets passed to the JSP Servlet as an init-param
if (servlet.getLoadOnStartup() != null) {
wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
}
if (servlet.getEnabled() != null) {
wrapper.setEnabled(servlet.getEnabled().booleanValue());
}
wrapper.setName(servlet.getServletName());
Map<String,String> params = servlet.getParameterMap();
for (Entry<String, String> entry : params.entrySet()) {
wrapper.addInitParameter(entry.getKey(), entry.getValue());
}
wrapper.setRunAs(servlet.getRunAs());
Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs();
for (SecurityRoleRef roleRef : roleRefs) {
wrapper.addSecurityReference(
roleRef.getName(), roleRef.getLink());
}
wrapper.setServletClass(servlet.getServletClass());
MultipartDef multipartdef = servlet.getMultipartDef();
if (multipartdef != null) {
if (multipartdef.getMaxFileSize() != null &&
multipartdef.getMaxRequestSize()!= null &&
multipartdef.getFileSizeThreshold() != null) {
wrapper.setMultipartConfigElement(new MultipartConfigElement(
multipartdef.getLocation(),
Long.parseLong(multipartdef.getMaxFileSize()),
Long.parseLong(multipartdef.getMaxRequestSize()),
Integer.parseInt(
multipartdef.getFileSizeThreshold())));
} else {
wrapper.setMultipartConfigElement(new MultipartConfigElement(
multipartdef.getLocation()));
}
}
if (servlet.getAsyncSupported() != null) {
wrapper.setAsyncSupported(
servlet.getAsyncSupported().booleanValue());
}
wrapper.setOverridable(servlet.isOverridable());
context.addChild(wrapper);
}
for (Entry<String, String> entry :
webxml.getServletMappings().entrySet()) {
context.addServletMappingDecoded(entry.getKey(), entry.getValue());
}
// 省略一些代码
}
该方法做了很多工作,例如:
- 将过滤器定义和映射添加到Context组件;
- 对每个servlet定义,调用Context组件的createWrapper方法将servlet定义和参数包装成Wrapper,然后将其添加到Context组件中;
- 将servlet映射添加到Context组件;
- ...