您的位置:首页 > 运维架构 > Tomcat

4000 Tomcat源码分析 -- StandardContext的启动

2018-03-09 16:56 525 查看
本篇结构:

前言

StandContext的启动过程

ContextConfig

总结

一、前言

根据上篇的介绍,我们知道创建StandContext的三种方式:

直接解析server.xml中的Context标签创建、解析Context文件描述符(位于$CATALINA-BASE/conf/Catalina/localhost目录下或者META-INF目录下)、创建默认StandardContext(即没有Context文件描述符,又没在server.xml中配置)。

了解了Web应用的创建和部署后,下面再来看看Web应用的初始化和启动工作。StandardHost和HostConfig只是根据不同情况创建Context对象,具体初始化和启动工作由组件Context自身完成。

二、StandContext的启动过程

Tomcat的生命周期机制告诉我们,一个组件的启动过程应该关注它的start方法,这个start方法是典型的模板方法设计模式。LifecycleBase是所有组件都继承的抽象类,该类提供了生命周期相关的通用方法,start()方法也可以在LifecycleBase中找到。

观察start方法,在该方法中定义了组件启动的应进行的操作,又留出一个抽象方法startInternal()方法供子类实现组件自身的操作。

所以来看StandContext的startInternal()方法。

该方法所做的操作很多很复杂,下面简单列下,就不深究了。主要是:

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

// Send j2ee.state.starting notification
if (this.getObjectName() != null) {
Notification notification = new Notification("j2ee.state.starting",
this.getObjectName(), sequenceNumber.getAndIncrement());
broadcaster.sendNotification(notification);
}


2.启动当前维护的JNDI资源。(哼,不懂JNDI)

if (namingResources != null) {
namingResources.start();
}


3.初始化临时工作目录,即设置的workDir,默认为
$CATALINA-BASE/work/<Engine名称>/<Host名称>/<Context名称>


postWorkDirectory();

private void postWorkDirectory() {

// Acquire (or calculate) the work directory path
String workDir = getWorkDir();
if (workDir == null || workDir.length() == 0) {

// Retrieve our parent (normally a host) name
String hostName = null;
String engineName = null;
String hostWorkDir = null;
Container parentHost = getParent();
if (parentHost != null) {
hostName = parentHost.getName();
if (parentHost instanceof StandardHost) {
hostWorkDir = ((StandardHost)parentHost).getWorkDir();
}
Container parentEngine = parentHost.getParent();
if (parentEngine != null) {
engineName = parentEngine.getName();
}
}
if ((hostName == null) || (hostName.length() < 1))
hostName = "_";
if ((engineName == null) || (engineName.length() < 1))
engineName = "_";

String temp = getBaseName();
if (temp.startsWith("/"))
temp = temp.substring(1);
temp = temp.replace('/', '_');
temp = temp.replace('\\', '_');
if (temp.length() < 1)
temp = ContextName.ROOT_NAME;
if (hostWorkDir != null ) {
workDir = hostWorkDir + File.separator + temp;
} else {
workDir = "work" + File.separator + engineName +
File.separator + hostName + File.separator + temp;
}
setWorkDir(workDir);
}


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

// Add missing components as necessary
if (getResources() == null) {   // (1) Required by Loader
if (log.isDebugEnabled())
log.debug("Configuring default Resources");

try {
setResources(new StandardRoot(this));
} catch (IllegalArgumentException e) {
log.error(sm.getString("standardContext.resourcesInit"), e);
ok = false;
}
}
if (ok) {
resourcesStart();
}


5.创建Web应用类加载器webappLoader,webappLoader继承自LifecycleMBeanBase,在其启动后会去创建Web应用类加载器(ParallelWebappClassLoader)。

if (getLoader() == null) {
WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
webappLoader.setDelegate(getDelegate());
setLoader(webappLoader);
}


同时webappLoader提供了backgroundProcess方法,用于Context后台处理,当检测到Web应用的类文件、Jar包发生变化时,重新加载Context。

public void backgroundProcess() {
if (reloadable && modified()) {
try {
Thread.currentThread().setContextClassLoader
(WebappLoader.class.getClassLoader());
if (context != null) {
context.reload();
}
} finally {
if (context != null && context.getLoader() != null) {
Thread.currentThread().setContextClassLoader
(context.getLoader().getClassLoader());
}
}
}
}


6.如果没有设置Cookie处理器,默认为Rfc6265CookieProcessor。

if (cookieProcessor == null) {
cookieProcessor = new Rfc6265CookieProcessor();
}


7.设置字符集映射,用于根据Locale获取字符集编码。

getCharsetMapper()

public CharsetMapper getCharsetMapper() {

// Create a mapper the first time it is requested
if (this.charsetMapper == null) {
try {
Class<?> clazz = Class.forName(charsetMapperClass);
this.charsetMapper = (CharsetMapper) clazz.getConstructor().newInstance();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
this.charsetMapper = new CharsetMapper();
}
}

return this.charsetMapper;

}


8.web应用的依赖检测。

9.NamingContextListener注册。

10.启动Web应用类加载器,此时真正创建出ParallelWebappClassLoader实例。

Loader loader = getLoader();
if (loader instanceof Lifecycle) {
((Lifecycle) loader).start();
}


11.启动安全组件。

Realm realm = getRealmInternal();
if(null != realm) {
if (realm instanceof Lifecycle) {
((Lifecycle) realm).start();
}


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

fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);


13.启动Context子节点Wrapper。

for (Container child : findChildren()) {
if (!child.getState().isAvailable()) {
child.start();
}
}


14.启动Context的pipeline。

if (pipeline instanceof Lifecycle) {
((Lifecycle) pipeline).start();
}


15.创建会话管理器。

Manager contextManager = null;
Manager manager = getManager();
if (manager == null) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("standardContext.cluster.noManager",
Boolean.valueOf((getCluster() != null)),
Boolean.valueOf(distributable)));
}
if ( (getCluster() != null) &a
134d2
mp;& distributable) {
try {
contextManager = getCluster().createManager(getName());
} catch (Exception ex) {
log.error("standardContext.clusterFail", ex);
ok = false;
}
} else {
contextManager = new StandardManager();
}
}


16.将Context的Web资源集合添加到ServletContext。

if (ok)
getServletContext().setAttribute
(Globals.RESOURCES_ATTR, getResources());


17.创建实例管理器instanceManager,用于创建对象实例,如Servlet、Filter等。

if (ok ) {
if (getInstanceManager() == null) {
javax.naming.Context context = null;
if (isUseNaming() && getNamingContextListener() != null) {
context = getNamingContextListener().getEnvContext();
}
Map<String, Map<String, String>> injectionMap = buildInjectionMap(
getIgnoreAnnotations() ? new NamingResourcesImpl(): getNamingResources());
setInstanceManager(new DefaultInstanceManager(context,
injectionMap, this, this.getClass().getClassLoader()));
}
getServletContext().setAttribute(
InstanceManager.class.getName(), getInstanceManager());
InstanceManagerBindings.bind(getLoader().getClassLoader(), getInstanceManager());
}


18.将Jar包扫描器添加到ServletContext。

if (ok) {
getServletContext().setAttribute(
JarScanner.class.getName(), getJarScanner());
}


19.合并参数。

private void mergeParameters() {
Map<String,String> mergedParams = new HashMap<>();

String names[] = findParameters();
for (int i = 0; i < names.length; i++) {
mergedParams.put(names[i], findParameter(names[i]));
}

ApplicationParameter params[] = findApplicationParameters();
for (int i = 0; i < params.length; i++) {
if (params[i].getOverride()) {
if (mergedParams.get(params[i].getName()) == null) {
mergedParams.put(params[i].getName(),
params[i].getValue());
}
} else {
mergedParams.put(params[i].getName(), params[i].getValue());
}
}

ServletContext sc = getServletContext();
for (Map.Entry<String,String> entry : mergedParams.entrySet()) {
sc.setInitParameter(entry.getKey(), entry.getValue());
}

}


20.启动添加到Context的ServletContainerInitializer。

21.实例化应用类监听器ApplicationListener。

if (ok) {
if (!listenerStart()) {
log.error(sm.getString("standardContext.listenerFail"));
ok = false;
}
}


22.启动会话管理器。

Manager manager = getManager();
if (manager instanceof Lifecycle) {
((Lifecycle) manager).start();
}


23.实例化FilterConfig、Filter并调用Filter.init()。

if (ok) {
if (!filterStart()) {
log.error(sm.getString("standardContext.filterFail"));
ok = false;
}
}


24.对于loadOnStartup大于等于0的Wrapper,调用Wrapper.load(),该方法负责实例化Servlet,并调用Servlet.init()进行初始化。

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


25.启动后台定时处理程序,只有backgroundProcessorDelay>0才启动,用于监控守护文件的变更。

// Start ContainerBackgroundProcessor thread
super.threadStart();


26.发布正在运行的JMX通知。

// Send j2ee.state.running notification
if (ok && (this.getObjectName() != null)) {
Notification notification =
new Notification("j2ee.state.running", this.getObjectName(),
sequenceNumber.getAndIncrement());
broadcaster.sendNotification(notification);
}


27.释放资源,如关闭jar文件。

// The WebResources implementation caches references to JAR files. On
// some platforms these references may lock the JAR files. Since web
// application start is likely to have read from lots of JARs, trigger
// a clean-up now.
getResources().gc();


28.设置Context状态。

if (!ok) {
setState(LifecycleState.FAILED);
} else {
setState(LifecycleState.STARTING);
}


StandContext启动很复杂,涉及很多知识面,我是很模糊的,也不打算深究了。

三、ContextConfig

ContextConfig是创建Context时默认添的一个生命周期监听器。它监听6个事件,其中三个和Context启动关系密切:AFTER_INIT_EVENT、BEFORE_START_EVENT、CONFIGURE_START_EVENT。

ContextConfig的lifecycleEvent()方法:

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

}


3.1、AFTER_INIT_EVENT事件

严格说,该事件属于Context事件初始化阶段,主要用于Context属性的配置工作。

根据前面讲的,再来回顾一下Context的创建,有以下来源:

1. 解析server.xml中的Context元素。

2. 通过HostConfig部署Web应用时,解析Web应用(或者WAR包)根目录下的META-INF/context.xml文件。如果不存在,则自动创建一个默认的Context对象,只设置name,path,docBase等几个属性。

3. 通诺HostConfig部署Web应用时,解析$CATALINA-BASE/conf/Catalina/localhost目录下的Context部署文件描述符创建。

除了Context创建时的属性配置,Tomcat提供的默认配置也要一并添加到Context实例中,AFTER_INIT_EVENT事件就是要完成这部分工作的。

来看该事件触发时执行的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);
}


init首先会创建createContextDigester创建解析规则,点进去看可以发现会回到之前讲Server解析时提到的ContextRuleSet,只不过这时传进去的create参数值为false。

不多说,重点来看contextConfig()方法:

protected void contextConfig(Digester digester) {

String defaultContextXml = null;

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

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);
}
}
}
if (context.getConfigFile() != null) {
processContextConfig(digester, context.getConfigFile());
}

}


(1)如果Context的override属性为false(默认配置):

①如果存在defaultContextXml即conf/context.xml(Catalina容器级默认配置文件),那么解析该文件,更新Context实例属性。

②如果存在hostContextXml即$CATALINA-BASE/conf/Catalina/localhost/context.xml.default文件(Host级的默认配置),则解析该文件,更新Context实例属性。

(2)如果context的configFile不为空(即$CATALINA-BASE/conf/Catalina/localhost下的Context部署描述文件或者Web应用根目录下的META-INF/context.xml文件),那么解析该文件,更新Context实例属性。

看到这会发现configFile其实被解析了两遍,在创建Context时会先解析一遍,这里再被解析一遍,为什么?

因为这里会解析conf/context.xml和context.xml.default文件,配置默认属性,如果之前创建Context时已经配置了某个属性,而这个属性又在conf/context.xml和context.xml.default中存在,显然这时会被覆盖,想要配置Context级别的属性不被覆盖,所以这时再解析一遍。

根据上述,可以得出结论:

Tomcat中Context属性的优先级为:configFile > $CATALINA-BASE/conf/Catalina/localhost/context.xml.default > conf/context.xml,即Web应用配置优先级最高,Host级别配置次之,Catalina容器级别最低。

3.2、BEFORE_START_EVENT

该事件在Context启动之前触发,主要用于更新docBase属性,解决Web目录锁的问题。

protected synchronized void beforeStart() {

try {
fixDocBase();
} catch (IOException e) {
log.error(sm.getString(
"contextConfig.fixDocBase", context.getName()), e);
}

antiLocking();
}


更新Context的docBase属性是为了满足WAR部署的情况。当Web应用为一个WAR压缩且需要解压部署(Hot的unpackWAR=true,且Context的unpackWAR=true)时,docBase属性指向的是解压后的文件夹目录,而非WAR包的路径。

具体过程是在fixDocBase()方法中:

protected void fixDocBase() throws IOException {

Host host = (Host) context.getParent();
File appBase = host.getAppBaseFile();

String docBase = context.getDocBase();
if (docBase == null) {
// Trying to guess the docBase according to the path
String path = context.getPath();
if (path == null) {
return;
}
ContextName cn = new ContextName(path, context.getWebappVersion());
docBase = cn.getBaseName();
}

File file = new File(docBase);
if (!file.isAbsolute()) {
docBase = (new File(appBase, docBase)).getPath();
} else {
docBase = file.getCanonicalPath();
}
file = new File(docBase);
String origDocBase = docBase;

ContextName cn = new ContextName(context.getPath(), context.getWebappVersion());
String pathName = cn.getBaseName();

boolean unpackWARs = true;
if (host instanceof StandardHost) {
unpackWARs = ((StandardHost) host).isUnpackWARs();
if (unpackWARs && context instanceof StandardContext) {
unpackWARs =  ((StandardContext) context).getUnpackWAR();
}
}

boolean docBaseInAppBase = docBase.startsWith(appBase.getPath() + File.separatorChar);

if (docBase.toLowerCase(Locale.ENGLISH).endsWith(".war") && !file.isDirectory()) {
URL war = UriUtil.buildJarUrl(new File(docBase));
if (unpackWARs) {
docBase = ExpandWar.expand(host, war, pathName);
file = new File(docBase);
docBase = file.getCanonicalPath();
if (context instanceof StandardContext) {
((StandardContext) context).setOriginalDocBase(origDocBase);
}
} else {
ExpandWar.validate(host, war, pathName);
}
} else {
File docDir = new File(docBase);
File warFile = new File(docBase + ".war");
URL war = null;
if (warFile.exists() && docBaseInAppBase) {
war = UriUtil.buildJarUrl(warFile);
}
if (docDir.exists()) {
if (war != null && unpackWARs) {
// Check if WAR needs to be re-expanded (e.g. if it has
// changed). Note: HostConfig.deployWar() takes care of
// ensuring that the correct XML file is used.
// This will be a NO-OP if the WAR is unchanged.
ExpandWar.expand(host, war, pathName);
}
} else {
if (war != null) {
if (unpackWARs) {
docBase = ExpandWar.expand(host, war, pathName);
file = new File(docBase);
docBase = file.getCanonicalPath();
} else {
docBase = warFile.getCanonicalPath();
ExpandWar.validate(host, war, pathName);
}
}
if (context instanceof StandardContext) {
((StandardContext) context).setOriginalDocBase(origDocBase);
}
}
}

// Re-calculate now docBase is a canonical path
docBaseInAppBase = docBase.startsWith(appBase.getPath() + File.separatorChar);

if (docBaseInAppBase) {
docBase = docBase.substring(appBase.getPath().length());
docBase = docBase.replace(File.separatorChar, '/');
if (docBase.startsWith("/")) {
docBase = docBase.substring(1);
}
} else {
docBase = docBase.replace(File.separatorChar, '/');
}

context.setDocBase(docBase);
}


(1)根据Host的appBase以及Context的docBase计算docBase的绝对路径。

(2)如果docBase指向WAR包:

需要解压部署:

①解压WAR文件;

②将Context的docBase设置为解压后的路径。

不需要解压部署:

只检测WAR包,不更新docBase。

(3)如果docBase指向目录:

①如果docBase指向的是有效目录,且存在与该目录同名的WAR包,同时需要解压部署,则重新解压WAR包;

②如果docBase指向的是无效目录,即不存在,但是存在与该目录同名的WAR包,如果需要解压,则解压WAR包,更新Context的docBase为解压路径。

③如果不需要解压部署,则只检测WAR包。

当Context的antiResourceLocking属性为true时,Tomcat会将当前Web应用目录复制到临时文件夹下,以避免对原目录的资源加锁。

3.3、CONFIGURE_START_EVENT

Context在启动之前,会触发CONFIGURE_START_EVENT事件,ContextConfig通过该事件解析web.xml,创建Wrapper(Servlet)、Filter、ServletContextListener等,完成web容器的初始化。

CONFIGURE_START_EVENT触发该方法:

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

}


该事件主要工作内容:

根据配置创建Wrapper(Servlet)、Filter、ServletContextListener等,完成Web容器的初始化。除了解析Web应用目录下的web.xml外,还包括Tomcat的默认配置、web-fragment.xml、ServletContainerInitializer,以及相关XML文件的排序和合并。

根据Servlet规范,Web应用部署描述可来源于WEB-IN/web.xml、Web应用JAR包中的META-INF/web-fragment.xml和META-INF/services/javax.servlet.ServletContainerInitializer。

除了Servlet规范中提到的部署描述方式,Tomcat还支持默认配置,以简化Web应用的配置工作。这些默认配置有容器级别的(conf/web.xml)和Host级别(conf/Engine名称/Host名称/web.xml.default)。解析中Web应用中的配置优先级最高,Host其次,最后是容器级。

来看webConfig方法:

protected void webConfig() {
/*
* Anything and everything can override the global and host defaults.
* This is implemented in two parts
* - Handle as a web fragment that gets added after everything else so
*   everything else takes priority
* - Mark Servlets as overridable so SCI configuration can replace
*   configuration from the defaults
*/

/*
* The rules for annotation scanning are not as clear-cut as one might
* think. Tomcat implements the following process:
* - As per SRV.1.6.2, Tomcat will scan for annotations regardless of
*   which Servlet spec version is declared in web.xml. The EG has
*   confirmed this is the expected behaviour.
* - As per http://java.net/jira/browse/SERVLET_SPEC-36, if the main
*   web.xml is marked as metadata-complete, JARs are still processed
*   for SCIs.
* - If metadata-complete=true and an absolute ordering is specified,
*   JARs excluded from the ordering are also excluded from the SCI
*   processing.
* - If an SCI has a @HandlesType annotation then all classes (except
*   those in JARs excluded from an absolute ordering) need to be
*   scanned to check if they match.
*/
WebXmlParser webXmlParser = new WebXmlParser(context.getXmlNamespaceAware(),
context.getXmlValidation(), context.getXmlBlockExternal());

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

Set<WebXml> tomcatWebXml = new HashSet<>();
tomcatWebXml.add(getTomcatWebXmlFragment(webXmlParser));

WebXml webXml = createWebXml();

// Parse context level web.xml
InputSource contextWebXml = getContextWebXmlSource();
if (!webXmlParser.parseWebXml(contextWebXml, webXml, false)) {
ok = false;
}

ServletContext sContext = context.getServletContext();

// Ordering is important here

// Step 1. Identify all the JARs packaged with the application and those
// provided by the container. If any of the application JARs have a
// web-fragment.xml it will be parsed at this point. web-fragment.xml
// files are ignored for container provided JARs.
Map<String,WebXml> fragments = processJarsForWebFragments(webXml, webXmlParser);

// Step 2. Order the fragments.
Set<WebXml> orderedFragments = null;
orderedFragments =
WebXml.orderWebFragments(webXml, fragments, sContext);

// Step 3. Look for ServletContainerInitializer implementations
if (ok) {
processServletContainerInitializers();
}

if  (!webXml.isMetadataComplete() || typeInitializerMap.size() > 0) {
// Step 4. Process /WEB-INF/classes for annotations and
// @HandlesTypes matches
Map<String,JavaClassCacheEntry> javaClassCache = new HashMap<>();

if (ok) {
WebResource[] webResources =
context.getResources().listResources("/WEB-INF/classes");

for (WebResource webResource : webResources) {
// Skip the META-INF directory from any JARs that have been
// expanded in to WEB-INF/classes (sometimes IDEs do this).
if ("META-INF".equals(webResource.getName())) {
continue;
}
processAnnotationsWebResource(webResource, webXml,
webXml.isMetadataComplete(), javaClassCache);
}
}

// Step 5. Process JARs for annotations and
// @HandlesTypes matches - only need to process those fragments we
// are going to use (remember orderedFragments includes any
// container fragments)
if (ok) {
processAnnotations(
orderedFragments, webXml.isMetadataComplete(), javaClassCache);
}

// Cache, if used, is no longer required so clear it
javaClassCache.clear();
}

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

// Step 7a
// merge tomcat-web.xml
webXml.merge(tomcatWebXml);

// Step 7b. 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(tomcatWebXml);
webXml.merge(defaults);
convertJsps(webXml);
configureContext(webXml);
}

if (context.getLogEffectiveWebXml()) {
log.info("web.xml:\n" + webXml.toXml());
}

// 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
}

// Step 11. Apply the ServletContainerInitializer config to the
// 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());
}
}
}
}


这个方法是Web容器的初始化,过程涉及内容较多,就简单描述下:

主要是解析默认配置,先解析容器级的配置(conf/web.xml),然后解析Host级别的配置(web.xml.default);

解析Web应用的web.xml,其他的解析结果均会合并到该解析结果中。

扫描素有JAR包,如果有META-INF/web-fragment.xml,解析该分件。



最关注的应该是使用web.xml配置Context实例,包括Servlet、Filter、Listener等Servelt规范中支持的组件,这些可以在configureContext()中找到。



四、总结

这篇很模糊不清,很潦草,没有太大参考价值,更多的还是自己去看源码吧。

当然看到这,至少希望明白web.xml是在这里解析,Servlet是在这里被包装成StandardWrapper,Filter是在这里创建的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: