上一篇文章我们介绍了server.xml是如何解析的,其中在介绍Context解析时,提到,多数情况下,并不需要在server.xml中配置Context,而是由HostConfig自动扫描部署目录,以context.xml文件为基础进行解析创建(如果通过IDE启动Tomcat并部署应用,其Context配置将会被动态更新到server.xml中)。
所以大多数情况下,Context并不会在server.xml解析时构建出来。那么Context是如何构建出来的?本篇文章就来探索一下Tomcat Context的构建过程。
假如我们再server.xml中只配置到Host组件先关的信息,那么解析结果自然是Host的children数组是空的,本来这个数组应该是用于存储Context组件的。那么何时,又是基于什么机制,完成了Context的实例化,并关联到Host组件?答案是我们上篇文章介绍的Host的一个重要的声明周期监听器HostConfig。
- Host启动
protected synchronized void startInternal() throws LifecycleException {
// Set error report valve
String errorValve = getErrorReportValveClass();
if ((errorValve != null) && (!errorValve.equals(""))) {
try {
boolean found = false;
Valve[] valves = getPipeline().getValves();
for (Valve valve : valves) {
if (errorValve.equals(valve.getClass().getName())) {
found = true;
break;
}
}
if(!found) {
Valve valve =
(Valve) Class.forName(errorValve).getConstructor().newInstance();
getPipeline().addValve(valve);
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString(
"standardHost.invalidErrorReportValveClass",
errorValve), t);
}
}
super.startInternal();
}
之前也介绍过Host启动,是通过直接调用父类ContainerBase的startInternal方法启动的。我们继续来跟一下ContainerBase的startInternal方法:
protected synchronized void startInternal() throws LifecycleException {
// Start our subordinate components, if any
logger = null;
getLogger();
Cluster cluster = getClusterInternal();
if (cluster instanceof Lifecycle) {
((Lifecycle) cluster).start();
}
Realm realm = getRealmInternal();
if (realm instanceof Lifecycle) {
((Lifecycle) realm).start();
}
// Start our child containers, if any
Container children[] = findChildren();
List<Future<Void>> results = new ArrayList<>();
for (Container child : children) {
results.add(startStopExecutor.submit(new StartChild(child)));
}
MultiThrowable multiThrowable = null;
for (Future<Void> result : results) {
try {
result.get();
} catch (Throwable e) {
log.error(sm.getString("containerBase.threadedStartFailed"), e);
if (multiThrowable == null) {
multiThrowable = new MultiThrowable();
}
multiThrowable.add(e);
}
}
if (multiThrowable != null) {
throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"),
multiThrowable.getThrowable());
}
// Start the Valves in our pipeline (including the basic), if any
if (pipeline instanceof Lifecycle) {
((Lifecycle) pipeline).start();
}
setState(LifecycleState.STARTING);
// Start our thread
if (backgroundProcessorDelay > 0) {
monitorFuture = Container.getService(ContainerBase.this).getServer()
.getUtilityExecutor().scheduleWithFixedDelay(
new ContainerBackgroundProcessorMonitor(), 0, 60, TimeUnit.SECONDS);
}
}
这个方法,当通过Host调用时,有以下几点值得思考:
server.xml解析时,并没有配置Context信息,所以findChildren()方法返回结果为空
setState(LifecycleState.STARTING)方法调用,看着应该是触发生命周期监听器的监听事件
方法最后,Start our thread,启动了后台线程,这个线程是做什么的
以上三个问题解释清楚,我们就能明白Context的构建原理了。
- HostConfig监听器
我们上篇文章介绍过,在server.xml解析时,代码中写死了向Host注册了一个生命周期监听器HostConfig。那么setState(LifecycleState.STARTING);方法的调用肯定也会激活该监听器的监听事件。
protected synchronized void setState(LifecycleState state, Object data)
throws LifecycleException {
setStateInternal(state, data, true);
}
private synchronized void setStateInternal(LifecycleState state, Object data, boolean check)
throws LifecycleException {
if (log.isDebugEnabled()) {
log.debug(sm.getString("lifecycleBase.setState", this, state));
}
if (check) {
// Must have been triggered by one of the abstract methods (assume
// code in this class is correct)
// null is never a valid state
if (state == null) {
invalidTransition("null");
// Unreachable code - here to stop eclipse complaining about
// a possible NPE further down the method
return;
}
// Any method can transition to failed
// startInternal() permits STARTING_PREP to STARTING
// stopInternal() permits STOPPING_PREP to STOPPING and FAILED to
// STOPPING
if (!(state == LifecycleState.FAILED ||
(this.state == LifecycleState.STARTING_PREP &&
state == LifecycleState.STARTING) ||
(this.state == LifecycleState.STOPPING_PREP &&
state == LifecycleState.STOPPING) ||
(this.state == LifecycleState.FAILED &&
state == LifecycleState.STOPPING))) {
// No other transition permitted
invalidTransition(state.name());
}
}
this.state = state;
String lifecycleEvent = state.getLifecycleEvent();
if (lifecycleEvent != null) {
fireLifecycleEvent(lifecycleEvent, data);
}
}
protected void fireLifecycleEvent(String type, Object data) {
LifecycleEvent event = new LifecycleEvent(this, type, data);
for (LifecycleListener listener : lifecycleListeners) {
listener.lifecycleEvent(event);
}
}
所以通过调用setState(LifecycleState.STARTING);方法,肯定会触发HostConfig对应的LifecycleState.STARTING对应类型的监听事件。
2.1 HostConfig.lifecycleEvent
public void lifecycleEvent(LifecycleEvent event) {
// Identify the host we are associated with
try {
host = (Host) event.getLifecycle();
if (host instanceof StandardHost) {
setCopyXML(((StandardHost) host).isCopyXML());
setDeployXML(((StandardHost) host).isDeployXML());
setUnpackWARs(((StandardHost) host).isUnpackWARs());
setContextClass(((StandardHost) host).getContextClass());
}
} catch (ClassCastException e) {
log.error(sm.getString("hostConfig.cce", event.getLifecycle()), e);
return;
}
// Process the event that has occurred
if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
check();
} else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
beforeStart();
} else if (event.getType().equals(Lifecycle.START_EVENT)) {
start();
} else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
stop();
}
}
public enum LifecycleState {
NEW(false, null),
INITIALIZING(false, Lifecycle.BEFORE_INIT_EVENT),
INITIALIZED(false, Lifecycle.AFTER_INIT_EVENT),
STARTING_PREP(false, Lifecycle.BEFORE_START_EVENT),
STARTING(true, Lifecycle.START_EVENT),
STARTED(true, Lifecycle.AFTER_START_EVENT),
STOPPING_PREP(true, Lifecycle.BEFORE_STOP_EVENT),
STOPPING(false, Lifecycle.STOP_EVENT),
STOPPED(false, Lifecycle.AFTER_STOP_EVENT),
DESTROYING(false, Lifecycle.BEFORE_DESTROY_EVENT),
DESTROYED(false, Lifecycle.AFTER_DESTROY_EVENT),
FAILED(false, null);
private final boolean available;
private final String lifecycleEvent;
private LifecycleState(boolean available, String lifecycleEvent) {
this.available = available;
this.lifecycleEvent = lifecycleEvent;
}
/**
* May the public methods other than property getters/setters and lifecycle
* methods be called for a component in this state? It returns
* <code>true</code> for any component in any of the following states:
* <ul>
* <li>{@link #STARTING}</li>
* <li>{@link #STARTED}</li>
* <li>{@link #STOPPING_PREP}</li>
* </ul>
*
* @return <code>true</code> if the component is available for use,
* otherwise <code>false</code>
*/
public boolean isAvailable() {
return available;
}
public String getLifecycleEvent() {
return lifecycleEvent;
}
}
通过LifecycleState枚举定义,STARTING对应的lifecycleEvent是Lifecycle.START_EVENT,所以会触发setState(LifecycleState.STARTING);方法,最终会调用到HostConfig的start()方法。
2.2 HostConfig.start()
public void start() {
if (log.isDebugEnabled())
log.debug(sm.getString("hostConfig.start"));
try {
ObjectName hostON = host.getObjectName();
oname = new ObjectName
(hostON.getDomain() + ":type=Deployer,host=" + host.getName());
Registry.getRegistry(null, null).registerComponent
(this, oname, this.getClass().getName());
} catch (Exception e) {
log.error(sm.getString("hostConfig.jmx.register", oname), e);
}
if (!host.getAppBaseFile().isDirectory()) {
log.error(sm.getString("hostConfig.appBase", host.getName(),
host.getAppBaseFile().getPath()));
host.setDeployOnStartup(false);
host.setAutoDeploy(false);
}
if (host.getDeployOnStartup())
deployApps();
}
默认配置 host.getDeployOnStartup() 返回true ,这样容器就会在启动的时候直接加载相应的web应用。如果在server.xml中Host节点的deployOnStartup属性设置为false ,则容器启动时不会加载应用,启动完之后不能立即提供web应用的服务。则需要通过上面说到的ContainerBackgroundProcessorMonitor后台线程创建并加载web应用,我们下面再详细介绍。
2.3 HostConfig.deployApps()
protected void deployApps() {
File appBase = host.getAppBaseFile();
File configBase = host.getConfigBaseFile();
String[] filteredAppPaths = filterAppPaths(appBase.list());
// Deploy XML descriptors from configBase
deployDescriptors(configBase, configBase.list());
// Deploy WARs
deployWARs(appBase, filteredAppPaths);
// Deploy expanded folders
deployDirectories(appBase, filteredAppPaths);
}
deployApps()方法中,会从各个路径加载构建Context,添加到Host组件中,并调用start方法启动Context应用。这里关于Context start方法的调用,我在看源码的时候,一度非常疑惑,因为在deployDescriptors、deployWARs和deployDirectories方法中,我只看到了Context的构建,并调用Host的addChild方法添加到Host中,并没有看到Context的start方法调用。其实Host的addChild方法,最终会调用到父类ContainerBase的addChildInternal方法,在该方法中,也会调用Context的start方法:
// 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) {
child.start();
}
} catch (LifecycleException e) {
log.error("ContainerBase.addChild: start: ", e);
throw new IllegalStateException("ContainerBase.addChild: start: " + e);
} finally {
fireContainerEvent(ADD_CHILD_EVENT, child);
}
到这里我们可以得出结论,如果在server.xml中没有配置Context相关信息,可以通过Host组件启动触发Host生命周期监听器HostConfig的START_EVENT时间监听构建Context并启动Context。
- ContainerBackgroundProcessorMonitor
在ContainerBase类的startInternal方法中最后会启动一个后台线程,这里我们来看一下这个线程的具体作用。
// Start our thread
if (backgroundProcessorDelay > 0) {
monitorFuture = Container.getService(ContainerBase.this).getServer()
.getUtilityExecutor().scheduleWithFixedDelay(
new ContainerBackgroundProcessorMonitor(), 0, 60, TimeUnit.SECONDS);
}
protected class ContainerBackgroundProcessorMonitor implements Runnable {
@Override
public void run() {
if (getState().isAvailable()) {
threadStart();
}
}
}
protected void threadStart() {
if (backgroundProcessorDelay > 0
&& (getState().isAvailable() || LifecycleState.STARTING_PREP.equals(getState()))
&& (backgroundProcessorFuture == null || backgroundProcessorFuture.isDone())) {
if (backgroundProcessorFuture != null && backgroundProcessorFuture.isDone()) {
// There was an error executing the scheduled task, get it and log it
try {
backgroundProcessorFuture.get();
} catch (InterruptedException | ExecutionException e) {
log.error(sm.getString(“containerBase.backgroundProcess.error”), e);
}
}
backgroundProcessorFuture = Container.getService(this).getServer().getUtilityExecutor()
.scheduleWithFixedDelay(new ContainerBackgroundProcessor(),
backgroundProcessorDelay, backgroundProcessorDelay,
TimeUnit.SECONDS);
}
}
backgroundProcessorDelay默认值为-1:
/**
- The processor delay for this component.
*/
protected int backgroundProcessorDelay = -1;
所以不难发现,其实Host启动时,调用ContainerBase的startInternal方法,其实并不会启动该异步线程。那么该异步线程是在什么时候启动的呢,答案是在Engine启动时创建该异步线程的。因为在Engine的构造函数中,会修改上述backgroundProcessorDelay值,如下:
public StandardEngine() {
super();
pipeline.setBasic(new StandardEngineValve());
/* Set the jmvRoute using the system property jvmRoute */
try {
setJvmRoute(System.getProperty("jvmRoute"));
} catch(Exception ex) {
log.warn(sm.getString("standardEngine.jvmRouteFail"));
}
// By default, the engine will hold the reloading thread
backgroundProcessorDelay = 10;
}
那么也就是说,Tomcat启动过程中,解析server.xml时碰到一个Engine节点就会提交一个异步线程。另外需要注意的是这里提交任务使用的是scheduleWithFixedDelay,所以会在固定时间间隔(10s)后,再提交一次任务。
接着,我们来看一下,Engine启动创建了这个异步线程,内部到底做了什么?在上述threadStart方法中,不难发现,构建了一个ContainerBackgroundProcessor任务,并执行。
/**
-
Private runnable class to invoke the backgroundProcess method
-
of this container and its children after a fixed delay.
*/
protected class ContainerBackgroundProcessor implements Runnable {@Override
public void run() {
processChildren(ContainerBase.this);
}protected void processChildren(Container container) {
ClassLoader originalClassLoader = null;try { if (container instanceof Context) { Loader loader = ((Context) container).getLoader(); // Loader will be null for FailedContext instances if (loader == null) { return; } // Ensure background processing for Contexts and Wrappers // is performed under the web app's class loader originalClassLoader = ((Context) container).bind(false, null); } container.backgroundProcess(); Container[] children = container.findChildren(); for (Container child : children) { if (child.getBackgroundProcessorDelay() <= 0) { processChildren(child); } } } catch (Throwable t) { ExceptionUtils.handleThrowable(t); log.error(sm.getString("containerBase.backgroundProcess.error"), t); } finally { if (container instanceof Context) { ((Context) container).unbind(false, originalClassLoader); } }
}
}
可以看到,ContainerBackgroundProcessor其实就是实现了执行当前容器及所有子容器的backgroundProcess方法。由于上述通过scheduleWithFixedDelay提交的异步任务,所以隔一段时间就会执行一次当前容器和所有子容器的backgroundProcess方法。
public void backgroundProcess() {
if (!getState().isAvailable())
return;
Cluster cluster = getClusterInternal();
if (cluster != null) {
try {
cluster.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.cluster",
cluster), e);
}
}
Realm realm = getRealmInternal();
if (realm != null) {
try {
realm.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.realm", realm), e);
}
}
Valve current = pipeline.getFirst();
while (current != null) {
try {
current.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.valve", current), e);
}
current = current.getNext();
}
fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null);
}
而容器的backgroundProcess方法,概括起来说就是逐个调用与容器相关其它内部组件的backgroundProcess方法。最后注册一个Lifecycle.PERIODIC_EVENT事件。而Host作为一种容器,通过上述过程,也会执行到backgroundProcess方法,并注册Lifecycle.PERIODIC_EVENT事件。上述HostConfig生命周期监听器也会被触发:
public void lifecycleEvent(LifecycleEvent event) {
// Identify the host we are associated with
try {
host = (Host) event.getLifecycle();
if (host instanceof StandardHost) {
setCopyXML(((StandardHost) host).isCopyXML());
setDeployXML(((StandardHost) host).isDeployXML());
setUnpackWARs(((StandardHost) host).isUnpackWARs());
setContextClass(((StandardHost) host).getContextClass());
}
} catch (ClassCastException e) {
log.error(sm.getString("hostConfig.cce", event.getLifecycle()), e);
return;
}
// Process the event that has occurred
if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
check();
} else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
beforeStart();
} else if (event.getType().equals(Lifecycle.START_EVENT)) {
start();
} else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
stop();
}
}
所以Lifecycle.PERIODIC_EVENT事件会触发check()方法调用,如下:
/**
-
Check status of all webapps.
*/
protected void check() {if (host.getAutoDeploy()) {
// Check for resources modification to trigger redeployment
DeployedApplication[] apps =
deployed.values().toArray(new DeployedApplication[0]);
for (DeployedApplication app : apps) {
if (!isServiced(app.name))
checkResources(app, false);
}// Check for old versions of applications that can now be undeployed if (host.getUndeployOldVersions()) { checkUndeploy(); } // Hotdeploy applications deployApps();
}
}
在check方法最后也会调用deployApps()方法,所以也会构建Context并启动Context。所以这也是为什么我们更新Tomcat web应用,而不需要重启tomcat的原因,因为web应用会通过这里讲的异步线程,重新加载。
以上就是Context的构建过程(构建Context并添加到Host中),Context内部的Servlet、Filter和Listener等信息还没有解析。Context子容器的解析是在Context的生命周期监听器ContextConfig中完成的,我们下篇文章再详细介绍。