总结:主要是创建Context对象,并且将默认context配置,host级别配置,context配置的值设置进去,设置docBase,如果是war包就解压到webapp的目录中,重新设置docBase为war包解压后的目录。如果配置文件中没有指定docBase,那么就以webapps为基路径+context的baseName作为docBase

HostConfig.deployApps()
//在监听到start事件类型,也就是StandardHost调用startInternal
protected void deployApps() { File appBase = host.getAppBaseFile();
//这个值是在触发before_start时间时生成的,默认是tomcat安装目录+engine名+host名
File configBase = host.getConfigBaseFile();
//获取host上配置的webapp下的所有文件,默认是webapps目录下的所有文件
String[] filteredAppPaths = filterAppPaths(appBase.list());
// Deploy XML descriptors from configBase 发布xml描述文件
deployDescriptors(configBase, configBase.list());
// Deploy WARs
deployWARs(appBase, filteredAppPaths);
// Deploy expanded folders
deployDirectories(appBase, filteredAppPaths); } deployDescriptors protected void deployDescriptors(File configBase, String[] files) { if (files == null)
return; ExecutorService es = host.getStartStopExecutor();//获取线程池
List<Future<?>> results = new ArrayList<>(); for (int i = 0; i < files.length; i++) {
File contextXml = new File(configBase, files[i]); if (files[i].toLowerCase(Locale.ENGLISH).endsWith(".xml")) {
//context命名,在构造函数里面进行设置,设置版本,path,命名。
ContextName cn = new ContextName(files[i], true);
//是否已经部署过,如果已经部署过了,就不再进行部署
if (isServiced(cn.getName()) || deploymentExists(cn.getName()))
continue;
//异步发布context描述xml
results.add(
es.submit(new DeployDescriptor(this, cn, contextXml)));
}
} for (Future<?> result : results) {
try {
//等待异步部署context描述文件结束
result.get();
} catch (Exception e) {
log.error(sm.getString(
"hostConfig.deployDescriptor.threaded.error"), e);
}
}
} ContextName.ContextName() //name 文件名
public ContextName(String name, boolean stripFileExtension) {
//假设文件名为myContext.xml和my/context.xml
String tmp1 = name; // Convert Context names and display names to base names // Strip off any leading "/" 剥离开头的/
if (tmp1.startsWith("/")) {
tmp1 = tmp1.substring(1);
} // Replace any remaining / 将/替换成#,如果是myContext.xml,那么依然是myContext.xml,如果是my/context.xml,那么就是my#context.xml
tmp1 = tmp1.replaceAll("/", FWD_SLASH_REPLACEMENT); // Insert the ROOT name if required 如果需要,插入ROOT名称
//如果是以##开头或者没有名字的
if (tmp1.startsWith(VERSION_MARKER) || "".equals(tmp1)) {
tmp1 = ROOT_NAME + tmp1;//比如##a,就会变成ROOT##a,为空就是ROOT
} // Remove any file extensions 去除扩展名
if (stripFileExtension &&
(tmp1.toLowerCase(Locale.ENGLISH).endsWith(".war") ||
tmp1.toLowerCase(Locale.ENGLISH).endsWith(".xml"))) {
tmp1 = tmp1.substring(0, tmp1.length() -4);
} baseName = tmp1;//myContext,my#context String tmp2;
// Extract version number 提取版本号
int versionIndex = baseName.indexOf(VERSION_MARKER);
if (versionIndex > -1) {
//如果存在##这种的,提取##后面作为版本号
version = baseName.substring(versionIndex + 2);
//比如myContext##1.2,这里version就是1.2,tmp2为myContext
tmp2 = baseName.substring(0, versionIndex);
} else {
version = "";
tmp2 = baseName;
}
//如果为ROOT,那么path路径为域名根目录
if (ROOT_NAME.equals(tmp2)) {
path = "";
} else {
// /myContext /my/context
path = "/" + tmp2.replaceAll(FWD_SLASH_REPLACEMENT, "/");
}
//如果存在版本号的应用
if (versionIndex > -1) {
// /myContext##1.2 /my/context##1.2
this.name = path + VERSION_MARKER + version;
} else {
this.name = path;
}
} DeployDescriptor.run() public void run() {
config.deployDescriptor(cn, descriptor);
} HostConfig.deployDescriptor(ContextName cn, File contextXml) protected void deployDescriptor(ContextName cn, File contextXml) {
//发布应用,用于记录发布的context名,和是否有描述文件,一些可能被修改的文件的修改时间,用于日后检测是否需要重新加载
DeployedApplication deployedApp =
new DeployedApplication(cn.getName(), true); long startTime = 0;
// Assume this is a configuration descriptor and deploy it
if(log.isInfoEnabled()) {
startTime = System.currentTimeMillis();
log.info(sm.getString("hostConfig.deployDescriptor",
contextXml.getAbsolutePath()));
} Context context = null;
//是否为扩展war包
boolean isExternalWar = false;
//是否是扩展web 应用
boolean isExternal = false;
//记录扩展web应用的地址
File expandedDocBase = null; try (FileInputStream fis = new FileInputStream(contextXml)) {
synchronized (digesterLock) {
try {
//解析contextxml文件
context = (Context) digester.parse(fis);
} catch (Exception e) {
log.error(sm.getString(
"hostConfig.deployDescriptor.error",
contextXml.getAbsolutePath()), e);
} finally {
//释放内存
digester.reset();
//如果创建失败,就new出一个失败的context
if (context == null) {
context = new FailedContext();
}
}
}
//host.getConfigClass() == org.apache.catalina.startup.ContextConfig
Class<?> clazz = Class.forName(host.getConfigClass());
LifecycleListener listener = (LifecycleListener) clazz.getConstructor().newInstance();
//给context设置ContextConfig生命周期监听器
context.addLifecycleListener(listener); context.setConfigFile(contextXml.toURI().toURL());
context.setName(cn.getName());
context.setPath(cn.getPath());//一般我们在config/engine名+host名下的配置文件都是
context.setWebappVersion(cn.getVersion());
// Add the associated docBase to the redeployed list if it's a WAR
if (context.getDocBase() != null) {
File docBase = new File(context.getDocBase());
if (!docBase.isAbsolute()) {
docBase = new File(host.getAppBaseFile(), context.getDocBase());
}
// If external docBase, register .xml as redeploy first,如果是扩展的web应用
//那么首先进行重发布处理
if (!docBase.getCanonicalPath().startsWith(
host.getAppBaseFile().getAbsolutePath() + File.separator)) {
isExternal = true;
//设置应用配置xml为重发布资源,记录最后修改的时间
deployedApp.redeployResources.put(
contextXml.getAbsolutePath(),
Long.valueOf(contextXml.lastModified()));
//设置应用目录为重发布资源,记录最后修改的时间
deployedApp.redeployResources.put(docBase.getAbsolutePath(),
Long.valueOf(docBase.lastModified()));
//如果是war包,设置isExternalWar为true
if (docBase.getAbsolutePath().toLowerCase(Locale.ENGLISH).endsWith(".war")) {
isExternalWar = true;
}
} else {
log.warn(sm.getString("hostConfig.deployDescriptor.localDocBaseSpecified",
docBase));
// Ignore specified docBase
context.setDocBase(null);
}
} host.addChild(context);//将context添加到对应host容器中,这里会进行context的初始化,启动生命周期
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("hostConfig.deployDescriptor.error",
contextXml.getAbsolutePath()), t);
} finally {
// Get paths for WAR and expanded WAR in appBase // default to appBase dir + name 默认是AppBase路径加上容器的继承名称
expandedDocBase = new File(host.getAppBaseFile(), cn.getBaseName());
//如果应用的docBase不为空,也就是设置了应用的位置,并且不是war包
if (context.getDocBase() != null
&& !context.getDocBase().toLowerCase(Locale.ENGLISH).endsWith(".war")) {
// first assume docBase is absolute 重新设置expandedDocBase
expandedDocBase = new File(context.getDocBase());
if (!expandedDocBase.isAbsolute()) {
// if docBase specified and relative, it must be relative to appBase
//如果是相对路径,都会认为相对appbase
expandedDocBase = new File(host.getAppBaseFile(), context.getDocBase());
}
} boolean unpackWAR = unpackWARs;
if (unpackWAR && context instanceof StandardContext) {
unpackWAR = ((StandardContext) context).getUnpackWAR();
} // Add the eventual unpacked WAR and all the resources which will be
// watched inside it
//如果是war包,并且允许解压,那么把war加入重新部署检测列表
if (isExternalWar) {
if (unpackWAR) {
deployedApp.redeployResources.put(expandedDocBase.getAbsolutePath(),
Long.valueOf(expandedDocBase.lastModified()));
//加入以expandedDocBase为基路径的资源重新加载监控
//在配置文件中有WatchedResource这样的xml元素,可以配置需要重加载检测文件
addWatchedResources(deployedApp, expandedDocBase.getAbsolutePath(), context);
} else {
//如果不允许解压,那么就直接用他们的相对路径进行资源修改监控
addWatchedResources(deployedApp, null, context);
}
} else {
//如果不是war包,而又不是扩展目录应用,那么自动加上war后缀
// Find an existing matching war and expanded folder
if (!isExternal) {
File warDocBase = new File(expandedDocBase.getAbsolutePath() + ".war");
if (warDocBase.exists()) {
deployedApp.redeployResources.put(warDocBase.getAbsolutePath(),
Long.valueOf(warDocBase.lastModified()));
} else {
// Trigger a redeploy if a WAR is added 如果这个war后面被添加进来了,那么就触发重新加载
deployedApp.redeployResources.put(
warDocBase.getAbsolutePath(),
Long.valueOf(0));
}
}
//这段代码和上面的war时的代码是一样的
if (unpackWAR) {
deployedApp.redeployResources.put(expandedDocBase.getAbsolutePath(),
Long.valueOf(expandedDocBase.lastModified()));
addWatchedResources(deployedApp,
expandedDocBase.getAbsolutePath(), context);
} else {
addWatchedResources(deployedApp, null, context);
}
if (!isExternal) {
// For external docBases, the context.xml will have been
// added above.
deployedApp.redeployResources.put(
contextXml.getAbsolutePath(),
Long.valueOf(contextXml.lastModified()));
}
}
// Add the global redeploy resources (which are never deleted) at
// the end so they don't interfere with the deletion process
//添加全局重新部署资源,如conf/engine名称+host名/context.xml.default和conf/context.xml
addGlobalRedeployResources(deployedApp);
}
//如果这个web应用已经成功添加到host中,那么记录这个应用已经被发布
if (host.findChild(context.getName()) != null) {
deployed.put(context.getName(), deployedApp);
} if (log.isInfoEnabled()) {
log.info(sm.getString("hostConfig.deployDescriptor.finished",
contextXml.getAbsolutePath(), Long.valueOf(System.currentTimeMillis() - startTime)));
}
} host.addChild最终调用了StandardHost.addChildInternal方法 private void addChildInternal(Container child) { if( log.isDebugEnabled() )
log.debug("Add child " + child + " " + this);
synchronized(children) {
if (children.get(child.getName()) != null)
throw new IllegalArgumentException("addChild: Child name '" +
child.getName() +
"' is not unique");
child.setParent(this); // May throw IAE 给子容器设置父容器,并且触发属性更改事件
children.put(child.getName(), child);将生成的context以context的名字做key,进行保存到map中
} // Start child
// Don't do this inside sync block - start can be a slow process and
// locking the children object can cause problems elsewhere
try {
if ((getState().isAvailable() ||
LifecycleState.STARTING_PREP.equals(getState())) &&
startChildren) {
//启动context容器
child.start();
}
} catch (LifecycleException e) {
log.error("ContainerBase.addChild: start: ", e);
throw new IllegalStateException("ContainerBase.addChild: start: " + e);
} finally {
fireContainerEvent(ADD_CHILD_EVENT, child);
}
} child.setParent(this); public void setParent(Container container) { Container oldParent = this.parent;
this.parent = container;
//调用实现了PropertyChangeListener接口的观察者
support.firePropertyChange("parent", oldParent, this.parent); } child.start();
StandardContext.start(); public final synchronized void start() throws LifecycleException {
//如果已经正在启动了,那么无需重复启动,直接返回,由于多线程的原因,这个state是volatile修饰的,保证内存可见性
if (LifecycleState.STARTING_PREP.equals(state) || LifecycleState.STARTING.equals(state) ||
LifecycleState.STARTED.equals(state)) { if (log.isDebugEnabled()) {
Exception e = new LifecycleException();
log.debug(sm.getString("lifecycleBase.alreadyStarted", toString()), e);
} else if (log.isInfoEnabled()) {
log.info(sm.getString("lifecycleBase.alreadyStarted", toString()));
} return;
}
//如果当前状态还是NEW,也就是连init都么有,那么就需要进行初始化
if (state.equals(LifecycleState.NEW)) {
init();
//如果是失败,那么就停止容器
} else if (state.equals(LifecycleState.FAILED)) {
stop();
//如果没有进行初始化完,就开始启动,那么直接报错
} else if (!state.equals(LifecycleState.INITIALIZED) &&
!state.equals(LifecycleState.STOPPED)) {
invalidTransition(Lifecycle.BEFORE_START_EVENT);
} try {
//设置声明周期类型,并且触发对应的事件
setStateInternal(LifecycleState.STARTING_PREP, null, false);
startInternal();
if (state.equals(LifecycleState.FAILED)) {
// This is a 'controlled' failure. The component put itself into the
// FAILED state so call stop() to complete the clean-up.
stop();
} else if (!state.equals(LifecycleState.STARTING)) {
// Shouldn't be necessary but acts as a check that sub-classes are
// doing what they are supposed to.
invalidTransition(Lifecycle.AFTER_START_EVENT);
} else {
setStateInternal(LifecycleState.STARTED, null, false);
}
} catch (Throwable t) {
// This is an 'uncontrolled' failure so put the component into the
// FAILED state and throw an exception.
ExceptionUtils.handleThrowable(t);
setStateInternal(LifecycleState.FAILED, null, false);
throw new LifecycleException(sm.getString("lifecycleBase.startFail", toString()), t);
}
} init(); //StandardContext触发after_init事件
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();-----》2
} 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(); -----》1
} else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) {
destroy();
} } ContextConfig.init(); protected void init() {
// Called from StandardContext.init()
//创建一个Digester用于解析context.xml
Digester contextDigester = createContextDigester();
contextDigester.getParser(); if (log.isDebugEnabled()) {
log.debug(sm.getString("contextConfig.init"));
}
context.setConfigured(false);//设置配置状态,默认设置为失败,以免被误任务成功
ok = true; contextConfig(contextDigester);
} ContextConfig.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 如果没有使用tomcat默认的全局配置
if (defaultContextXml == null) {
defaultContextXml = Constants.DefaultContextXml;//conf/context.xml
}
//如果还没有进行解析,那么就会重新解析,默认的全局配置-》configBase下的context.xml.default-》configBase下的配置
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();
//处理应用配置,这个配置是默认配置,如果用户指定了默认配置,那么就解析用户指定的配置,如果没有指定,那么就直接使用tomcat提供的全局配置
processContextConfig(digester, defaultContextUrl);
} catch (MalformedURLException e) {
log.error(sm.getString(
"contextConfig.badUrl", defaultContextFile), e);
}
} //Constants.HostContextXml == context.xml.default,这个默认配置文件是host范围的context配置
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);
}
}
}
//context.getConfigFile() 获取应用的配置的文件,这个配置文件就是config/engine名+host名下扫描出来的配置文件
if (context.getConfigFile() != null) {
processContextConfig(digester, context.getConfigFile());
} } ContextConfig. processContextConfig() //解析应用配置文件,从目前tomcat调用这个方法的先后顺序可以看出,tomcat给一个web应用设置属性是从全局默认的配置开始-》host级别的配置-》再到context级别的配置,所以context级别的配置最高。
protected void processContextConfig(Digester digester, URL contextXml) { if (log.isDebugEnabled()) {
log.debug("Processing context [" + context.getName()
+ "] configuration file [" + contextXml + "]");
} InputSource source = null;
InputStream stream = null; try {
source = new InputSource(contextXml.toString());
URLConnection xmlConn = contextXml.openConnection();
xmlConn.setUseCaches(false);
stream = xmlConn.getInputStream();
} catch (Exception e) {
log.error(sm.getString("contextConfig.contextMissing",
contextXml) , e);
} if (source == null) {
return;
} try {
source.setByteStream(stream);
digester.setClassLoader(this.getClass().getClassLoader());
//这里这个digester不会再创建StandardContext对象了,因为在前面已经创建了一个
digester.setUseContextClassLoader(false);
digester.push(context.getParent());
digester.push(context);
XmlErrorHandler errorHandler = new XmlErrorHandler();
digester.setErrorHandler(errorHandler);
digester.parse(source);
if (errorHandler.getWarnings().size() > 0 ||
errorHandler.getErrors().size() > 0) {
errorHandler.logFindings(log, contextXml.toString());
ok = false;
}
if (log.isDebugEnabled()) {
log.debug("Successfully processed context [" + context.getName()
+ "] configuration file [" + contextXml + "]");
}
} catch (SAXParseException e) {
log.error(sm.getString("contextConfig.contextParse",
context.getName()), e);
log.error(sm.getString("contextConfig.defaultPosition",
"" + e.getLineNumber(),
"" + e.getColumnNumber()));
ok = false;
} catch (Exception e) {
log.error(sm.getString("contextConfig.contextParse",
context.getName()), e);
ok = false;
} finally {
try {
if (stream != null) {
stream.close();
}
} catch (IOException e) {
log.error(sm.getString("contextConfig.contextClose"), e);
}
}
} //before_start事件
ContextConfig.beforeStart(); protected synchronized void beforeStart() { try {
fixDocBase();
} catch (IOException e) {
log.error(sm.getString(
"contextConfig.fixDocBase", context.getName()), e);
} antiLocking();
} ContextConfig.fixDocBase()
//对docBase做调整
protected void fixDocBase() throws IOException { Host host = (Host) context.getParent();
File appBase = host.getAppBaseFile(); String docBase = context.getDocBase();
//如果没有设置docBase,那么就根据应用路径重新设置路径
if (docBase == null) {
// Trying to guess the docBase according to the path
String path = context.getPath();
if (path == null) {
return;
}
//通过路径和版本去获取docBase
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();
}
}
//判断这个docBase路径是否是webapps下的
boolean docBaseInAppBase = docBase.startsWith(appBase.getPath() + File.separatorChar);
//如果这个docBase指定的是一个war包,那么就将其解压
if (docBase.toLowerCase(Locale.ENGLISH).endsWith(".war") && !file.isDirectory()) {
URL war = UriUtil.buildJarUrl(new File(docBase));
if (unpackWARs) {
//解压war包,返回解压后的目录路径,war解析源码请看war包解析笔记
docBase = ExpandWar.expand(host, war, pathName);
file = new File(docBase);
docBase = file.getCanonicalPath();
if (context instanceof StandardContext) {
//设置未解压时指定的位置,因为解压时会将解压后的内容放到host指定的appBase目录下
((StandardContext) context).setOriginalDocBase(origDocBase);
}
} else {
//如果不需要解压,那么就校验对应的docbase是由已经存在了,如果不存在,直接报错
ExpandWar.validate(host, war, pathName);
}
} else {
File docDir = new File(docBase);
File warFile = new File(docBase + ".war");
URL war = null;
//如果war包存在,并且是在APPBase中的,那么直接获取其war的url
if (warFile.exists() && docBaseInAppBase) {
war = UriUtil.buildJarUrl(warFile);
}
//如果目录存在,并且是war包,允许解压
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);
//如果目录为appBase下的,那么直接截取到appBase后面一截
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);
} protected synchronized void startInternal() throws LifecycleException {
setConfigured(false);
boolean ok = true; // Currently this is effectively a NO-OP but needs to be called to
// ensure the NamingResources follows the correct lifecycle
if (namingResources != null) {
namingResources.start();
} // Post work directory 生成工作目录,这个目录用于存放编译后的jsp文件,一般生成的目录格式为tomcat安装目录work+engine名+host名+context的baseName
postWorkDirectory(); // 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();
}
//设置web加载器
if (getLoader() == null) {
WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
webappLoader.setDelegate(getDelegate());
setLoader(webappLoader);
} 。。。省略代码 // Notify our interested LifecycleListeners
fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null); // Start our child containers, if not already started,开始子容器context的子容器是wrapper
for (Container child : findChildren()) {
if (!child.getState().isAvailable()) {
child.start();
}
} // Start the Valves in our pipeline (including the basic),
// if any
if (pipeline instanceof Lifecycle) {
((Lifecycle) pipeline).start();
}
//省略了集群管理代码 // Configure default manager if none was specified
if (contextManager != null) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("standardContext.manager",
contextManager.getClass().getName()));
}
setManager(contextManager);
}
// Set up the context init params 设置初始化参数
mergeParameters(); // Call ServletContainerInitializers
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;
}
} // Configure and call application event listeners 实例化监听器,并且调用实现了ServletContextListener监听器的初始化方法
if (ok) {
if (!listenerStart()) {
log.error(sm.getString("standardContext.listenerFail"));
ok = false;
}
} // Check constraints for uncovered HTTP methods
// Needs to be after SCIs and listeners as they may programmatically
// change constraints
if (ok) {
checkConstraintsForUncoveredMethods(findConstraints());
} try {
// Start manager session管理器
Manager manager = getManager();
if (manager instanceof Lifecycle) {
((Lifecycle) manager).start();
}
} catch(Exception e) {
log.error(sm.getString("standardContext.managerFail"), e);
ok = false;
} // Configure and call application filters 实例化filter,并且调用其初始化方法
if (ok) {
if (!filterStart()) {
log.error(sm.getString("standardContext.filterFail"));
ok = false;
}
} // Load and initialize all "load on startup" servlets 实例化设置了load on startup的Servlet并调用初始化方法
if (ok) {
if (!loadOnStartup(findChildren())){
log.error(sm.getString("standardContext.servletFail"));
ok = false;
}
} // Start ContainerBackgroundProcessor thread
super.threadStart();
} finally {
// Unbinding thread
unbindThread(oldCCL);
} // Set available status depending upon startup success
if (ok) {
if (log.isDebugEnabled())
log.debug("Starting completed");
} else {
log.error(sm.getString("standardContext.startFailed", getName()));
} startTime=System.currentTimeMillis(); // Send j2ee.state.running notification
if (ok && (this.getObjectName() != null)) {
Notification notification =
new Notification("j2ee.state.running", this.getObjectName(),
sequenceNumber.getAndIncrement());
broadcaster.sendNotification(notification);
} // 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(); // Reinitializing if something went wrong
if (!ok) {
setState(LifecycleState.FAILED);
} else {
setState(LifecycleState.STARTING);
}
} configure_start事件
ContextConfig.configureStart() protected synchronized void configureStart() {
// Called from StandardContext.start()
//开始web.xml的配置
webConfig(); //如果未配置忽略应用注解配置,那么对filter,servlet,listener进行Resource注解的搜索
Resource配置在类,字段,方法上,会根据资源的类型进行划分资源类型,添加到不同资源集合中,比如环境变量,JNDI等等资源
if (!context.getIgnoreAnnotations()) {
applicationAnnotationsConfig();
}
if (ok) {
//将context约束的角色和wrapper中注解RunAs或配置中设置的角色添加到context容器中,重复的不会被再次添加
validateSecurityRoles();
} // Configure an authenticator if we need one
//配置验证器,如果没有Ralm或者实现了 Authenticator的管道阀,那么就会添加一个默认的NonLoginAuthenticator验证器
if (ok) {
authenticatorConfig();
}
// Make our application available if no problems were encountered
//如果配置context时没有遇到任何问题,那么就表示配置成功
if (ok) {
context.setConfigured(true);
} else {
log.error(sm.getString("contextConfig.unavailable"));
context.setConfigured(false);
} } ContextConfig.webConfig(); protected void webConfig() {
//new出一个webxml的解析器
WebXmlParser webXmlParser = new WebXmlParser(context.getXmlNamespaceAware(),
context.getXmlValidation(), context.getXmlBlockExternal()); Set<WebXml> defaults = new HashSet<>();
//获取webxml片段
defaults.add(getDefaultWebXmlFragment(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.
//解析应用程序jar包中META-INF/web-fragment.xml
//key为jar包的全路径名,value是从META-INF/web-fragment.xml解析后的WebXml对象
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) {
//查找META-INF/services/javax.servlet.ServletContainerInitializer配置文件,获取ServletContainerInitializer,这里的ServletContainerInitializer可以使用HandlesTypes注解指定需要处理的类型
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) {
//加载/WEB-INF/classes下的资源
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;
}
//使用自定义的字节码解析器去解析class文件,寻找有@WebServlet,@WebFilter,@WebListener,然后将解析好的ServletDef,FilterDef,Listener注入到WebXml对象当中
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)
//解析jar包中的注解Servlet,Listener,FIlter
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
//合并片段配置文件的内容到主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
//准备数据转换被显示配置的jsp为servlet,指定初始化参数,指定ServletClass为org.apache.jasper.servlet.JspServlet
if (ok) {
convertJsps(webXml);
} // Step 9. Apply merged web.xml to Context 将web.xml配置的数据设置到context中
if (ok) {
configureContext(webXml);
}
} else {
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 从jar中中查找静态资源
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 应用ServletContainerInitializer Initializer --》要处理的类型(可能是注解标识的,也可能就是指定的类型)
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());
}
}
}
} ContextConfig.getDefaultWebXmlFragment private WebXml getDefaultWebXmlFragment(WebXmlParser webXmlParser) { // Host should never be null
Host host = (Host) context.getParent(); DefaultWebXmlCacheEntry entry = hostWebXmlCache.get(host);
//获取全局webxml配置文件,config/web.xml
InputSource globalWebXml = getGlobalWebXmlSource();
//获取host级别的xml配置文件,文件名为web.xml.default
InputSource hostWebXml = getHostWebXmlSource(); long globalTimeStamp = 0;
long hostTimeStamp = 0; if (globalWebXml != null) {
URLConnection uc = null;
try {
//获取systemid对应数据的最后修改时间
URL url = new URL(globalWebXml.getSystemId());
uc = url.openConnection();
globalTimeStamp = uc.getLastModified();
} catch (IOException e) {
globalTimeStamp = -1;
} finally {
if (uc != null) {
try {
uc.getInputStream().close();
} catch (IOException e) {
ExceptionUtils.handleThrowable(e);
globalTimeStamp = -1;
}
}
}
} if (hostWebXml != null) {
URLConnection uc = null;
try {
//获取systemid对应数据的最后修改时间
URL url = new URL(hostWebXml.getSystemId());
uc = url.openConnection();
hostTimeStamp = uc.getLastModified();
} catch (IOException e) {
hostTimeStamp = -1;
} finally {
if (uc != null) {
try {
uc.getInputStream().close();
} catch (IOException e) {
ExceptionUtils.handleThrowable(e);
hostTimeStamp = -1;
}
}
}
}
//如果已经解析过了,那么关闭资源,直接返回已经解析好的WebXml
if (entry != null && entry.getGlobalTimeStamp() == globalTimeStamp &&
entry.getHostTimeStamp() == hostTimeStamp) {
InputSourceUtil.close(globalWebXml);
InputSourceUtil.close(hostWebXml);
return entry.getWebXml();
} // Parsing global web.xml is relatively expensive. Use a sync block to
// make sure it only happens once. Use the pipeline since a lock will
// already be held on the host by another thread
synchronized (host.getPipeline()) {
entry = hostWebXmlCache.get(host);
if (entry != null && entry.getGlobalTimeStamp() == globalTimeStamp &&
entry.getHostTimeStamp() == hostTimeStamp) {
return entry.getWebXml();
}
//直接new出一个WebXml对象
WebXml webXmlDefaultFragment = createWebXml();
webXmlDefaultFragment.setOverridable(true);
// Set to distributable else every app will be prevented from being
// distributable when the default fragment is merged with the main
// web.xml
webXmlDefaultFragment.setDistributable(true);
// When merging, the default welcome files are only used if the app has
// not defined any welcomes files.
webXmlDefaultFragment.setAlwaysAddWelcomeFiles(false); // Parse global web.xml if present
if (globalWebXml == null) {
// This is unusual enough to log
log.info(sm.getString("contextConfig.defaultMissing"));
} else {
//解析全局web配置
if (!webXmlParser.parseWebXml(
globalWebXml, webXmlDefaultFragment, false)) {
ok = false;
}
} // Parse host level web.xml if present
// Additive apart from welcome pages
webXmlDefaultFragment.setReplaceWelcomeFiles(true);
//解析host级别的webxml,会覆盖掉全局的相同属性值的内容
if (!webXmlParser.parseWebXml(
hostWebXml, webXmlDefaultFragment, false)) {
ok = false;
} // Don't update the cache if an error occurs
if (globalTimeStamp != -1 && hostTimeStamp != -1) {
entry = new DefaultWebXmlCacheEntry(webXmlDefaultFragment,
globalTimeStamp, hostTimeStamp);
hostWebXmlCache.put(host, entry);
} return webXmlDefaultFragment;
}
} WebXmlParser.parseWebXml(InputSource source, WebXml dest,
boolean fragment) public boolean parseWebXml(InputSource source, WebXml dest,
boolean fragment) { boolean ok = true; if (source == null) {
return ok;
} XmlErrorHandler handler = new XmlErrorHandler(); Digester digester;
WebRuleSet ruleSet;
if (fragment) {//如果是web.xml配置片段,那么就使用片段Digester
digester = webFragmentDigester;
ruleSet = webFragmentRuleSet;
} else {
digester = webDigester;
ruleSet = webRuleSet;
} digester.push(dest);
digester.setErrorHandler(handler);
try {
digester.parse(source);
//如果解析出了错误,那么设置ok为false,意味着context启动失败
if (handler.getWarnings().size() > 0 ||
handler.getErrors().size() > 0) {
ok = false;
handler.logFindings(log, source.getSystemId());
}
} catch (SAXParseException e) {
log.error(sm.getString("webXmlParser.applicationParse",
source.getSystemId()), e);
log.error(sm.getString("webXmlParser.applicationPosition",
"" + e.getLineNumber(),
"" + e.getColumnNumber()));
ok = false;
} catch (Exception e) {
log.error(sm.getString("webXmlParser.applicationParse",
source.getSystemId()), e);
ok = false;
} finally {
InputSourceUtil.close(source);
digester.reset();
ruleSet.recycle();
} return ok;
} Digester public WebXmlParser(boolean namespaceAware, boolean validation,
boolean blockExternal) {
webRuleSet = new WebRuleSet(false);
webDigester = DigesterFactory.newDigester(validation,
namespaceAware, webRuleSet, blockExternal);
webDigester.getParser(); webFragmentRuleSet = new WebRuleSet(true);
webFragmentDigester = DigesterFactory.newDigester(validation,
namespaceAware, webFragmentRuleSet, blockExternal);
webFragmentDigester.getParser();
} public WebRuleSet(String prefix, boolean fragment) {
super();
this.prefix = prefix;
this.fragment = fragment; if(fragment) {
fullPrefix = prefix + "web-fragment";
} else {
fullPrefix = prefix + "web-app";
} absoluteOrdering = new AbsoluteOrderingRule(fragment);
relativeOrdering = new RelativeOrderingRule(fragment);
} 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());
//将上下文参数添加到context中
for (Entry<String, String> entry : webxml.getContextParams().entrySet()) {
context.addParameter(entry.getKey(), entry.getValue());
}
context.setDenyUncoveredHttpMethods(
webxml.getDenyUncoveredHttpMethods());
context.setDisplayName(webxml.getDisplayName());
context.setDistributable(webxml.isDistributable());
//EJB相关的引用资源,反正我不懂
for (ContextLocalEjb ejbLocalRef : webxml.getEjbLocalRefs().values()) {
context.getNamingResources().addLocalEjb(ejbLocalRef);
}
for (ContextEjb ejbRef : webxml.getEjbRefs().values()) {
context.getNamingResources().addEjb(ejbRef);
}
//
for (ContextEnvironment environment : webxml.getEnvEntries().values()) {
context.getNamingResources().addEnvironment(environment);
}
//添加错误页面
for (ErrorPage errorPage : webxml.getErrorPages().values()) {
context.addErrorPage(errorPage);
}
//添加过滤器,filterName->filterDef
for (FilterDef filter : webxml.getFilters().values()) {
if (filter.getAsyncSupported() == null) {
filter.setAsyncSupported("false");
}
context.addFilterDef(filter);
}
//FilterMap urlPattern与Servlet的关联
for (FilterMap filterMap : webxml.getFilterMappings()) {
context.addFilterMap(filterMap);
}
context.setJspConfigDescriptor(webxml.getJspConfigDescriptor());
//添加监听器到context的数组中
for (String listener : webxml.getListeners()) {
context.addApplicationListener(listener);
}
//添加locale-》字符集
for (Entry<String, String> entry :
webxml.getLocaleEncodingMappings().entrySet()) {
context.addLocaleEncodingMappingParameter(entry.getKey(),
entry.getValue());
}
// Prevents IAE 设置登录配置
if (webxml.getLoginConfig() != null) {
context.setLoginConfig(webxml.getLoginConfig());
}
//消息目标引用
for (MessageDestinationRef mdr :
webxml.getMessageDestinationRefs().values()) {
context.getNamingResources().addMessageDestinationRef(mdr);
} // messageDestinations were ignored in Tomcat 6, so ignore here context.setIgnoreAnnotations(webxml.isMetadataComplete());
//设置媒体类型
for (Entry<String, String> entry :
webxml.getMimeMappings().entrySet()) {
context.addMimeMapping(entry.getKey(), entry.getValue());
}
// Name is just used for ordering 添加JNDI引用
for (ContextResourceEnvRef resource :
webxml.getResourceEnvRefs().values()) {
context.getNamingResources().addResourceEnvRef(resource);
}
//添加JNDI
for (ContextResource resource : webxml.getResourceRefs().values()) {
context.getNamingResources().addResource(resource);
}
//设置权限
boolean allAuthenticatedUsersIsAppRole =
webxml.getSecurityRoles().contains(
SecurityConstraint.ROLE_ALL_AUTHENTICATED_USERS);
for (SecurityConstraint constraint : webxml.getSecurityConstraints()) {
if (allAuthenticatedUsersIsAppRole) {
constraint.treatAllAuthenticatedUsersAsApplicationRole();
}
context.addConstraint(constraint);
}
//添加角色
for (String role : webxml.getSecurityRoles()) {
context.addSecurityRole(role);
}
//添加服务引用
for (ContextService service : webxml.getServiceRefs().values()) {
context.getNamingResources().addService(service);
}
//添加ServletDef,包装成StandardWrapper容器
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);
}
//添加ServletMapping urlPattern->servletName
for (Entry<String, String> entry :
webxml.getServletMappings().entrySet()) {
context.addServletMappingDecoded(entry.getKey(), entry.getValue());
}
//获取session配置
SessionConfig sessionConfig = webxml.getSessionConfig();
if (sessionConfig != null) {
if (sessionConfig.getSessionTimeout() != null) {
context.setSessionTimeout(
sessionConfig.getSessionTimeout().intValue());
}
SessionCookieConfig scc =
context.getServletContext().getSessionCookieConfig();
scc.setName(sessionConfig.getCookieName());
scc.setDomain(sessionConfig.getCookieDomain());
scc.setPath(sessionConfig.getCookiePath());
scc.setComment(sessionConfig.getCookieComment());
if (sessionConfig.getCookieHttpOnly() != null) {
scc.setHttpOnly(sessionConfig.getCookieHttpOnly().booleanValue());
}
if (sessionConfig.getCookieSecure() != null) {
scc.setSecure(sessionConfig.getCookieSecure().booleanValue());
}
if (sessionConfig.getCookieMaxAge() != null) {
scc.setMaxAge(sessionConfig.getCookieMaxAge().intValue());
}
if (sessionConfig.getSessionTrackingModes().size() > 0) {
context.getServletContext().setSessionTrackingModes(
sessionConfig.getSessionTrackingModes());
}
} // Context doesn't use version directly
//添加欢迎配置
for (String welcomeFile : webxml.getWelcomeFiles()) {
/*
* The following will result in a welcome file of "" so don't add
* that to the context
* <welcome-file-list>
* <welcome-file/>
* </welcome-file-list>
*/
if (welcomeFile != null && welcomeFile.length() > 0) {
context.addWelcomeFile(welcomeFile);
}
} // Do this last as it depends on servlets
for (JspPropertyGroup jspPropertyGroup :
webxml.getJspPropertyGroups()) {
String jspServletName = context.findServletMapping("*.jsp");
if (jspServletName == null) {
jspServletName = "jsp";
}
if (context.findChild(jspServletName) != null) {
for (String urlPattern : jspPropertyGroup.getUrlPatterns()) {
context.addServletMappingDecoded(urlPattern, jspServletName, true);
}
} else {
if(log.isDebugEnabled()) {
for (String urlPattern : jspPropertyGroup.getUrlPatterns()) {
log.debug("Skipping " + urlPattern + " , no servlet " +
jspServletName);
}
}
}
}
//添加初始方法
for (Entry<String, String> entry :
webxml.getPostConstructMethods().entrySet()) {
context.addPostConstructMethod(entry.getKey(), entry.getValue());
}
//添加销毁方法
for (Entry<String, String> entry :
webxml.getPreDestroyMethods().entrySet()) {
context.addPreDestroyMethod(entry.getKey(), entry.getValue());
}
}

最新文章

  1. Web Map Gis 开发系列索引
  2. jsp中超链接路径的写法
  3. .net core 关键概念
  4. UniversalApp启动页面设置
  5. Scala语言专题
  6. 判断UserAgent是否来自微信
  7. SecurityCRT输出日志重定向
  8. 10.23 noip模拟试题
  9. Android——在活动中使用Menu
  10. UNIX基础知识--&lt;&lt;UNIX 环境编程&gt;&gt;读书笔记
  11. 从零开始用 Flask 搭建一个网站(三)
  12. C#打印九九乘法表
  13. VS2015企业版序列号
  14. JavaMail开发教程01开山篇
  15. Java ArrayList正确循环添加删除元素方法及分析
  16. whereis、which、find的区别
  17. github提交代码失败
  18. 【bzoj5072】[Lydsy十月月赛]小A的树 树形背包dp
  19. Oracle之外键(Foreign Key)使用方法具体解释(二)- 级联删除(DELETE CASCADE)
  20. orcl创建数据库

热门文章

  1. GIS热力图制作与位置大数据分析
  2. 节能减排到底如何----google earth engine 告诉你!!
  3. IM推送保障及网络优化详解(一):如何实现不影响用户体验的后台保活
  4. 【朝花夕拾】Android自定义View篇之(六)Android事件分发机制(中)从源码分析事件分发逻辑及经常遇到的一些“诡异”现象
  5. 搭建本地pip源
  6. 基于STM32之UART串口通信协议(四)Printf发送
  7. RocketMQ与MYSQL事务消息整合
  8. 并发编程-concurrent指南-信号量Semaphore
  9. 【POJ - 2386】Lake Counting (dfs+染色)
  10. UVALive 7037:The Problem Needs 3D Arrays(最大密度子图)