coding……
但行好事 莫问前程

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

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

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

参考链接:

1. Engine、Host及Context的解析过程

2. Context 构建

3. Tomcat源码

赞(1) 打赏
Zhuoli's Blog » Tomcat源码解读『Context如何构建的』
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

zhuoli's blog

联系我关于我

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏