Tomcat(五)Context构建

上一篇文章我们介绍了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。

  1. 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的构建原理了。

  1. 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。

  1. 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中完成的,我们下篇文章再详细介绍。

上一篇:@OnLifecycleEvent 遭废弃,推荐使用 DefaultLifecycleObserver 替代


下一篇:基于FPGA的数字抢答器VHDL开发