Tomcat(四)Context构建

一 Host启动

如何构建的,了解了Context构建的两种方式:

  • Host启动,触发HostConfig的Lifecycle.START_EVENT事件监听,构建Context

  • 后台线程,定期去执行Host的backgroundProcess方法,触发HostConfig的Lifecycle.PERIODIC_EVENT事件监听,构建Context
    这里的构建Context,指的是构建一个Context对象,并将构建好的Context对象与Host组件关联起来(调用host.addChild(context)。

  • 多数情况下,并不需要在server.xml中配置Context,而是由HostConfig自动扫描部署目录,以context.xml文件为基础进行解析创建(如果通过IDE启动Tomcat并部署应用,其Context配置将会被动态更新到server.xml中)。

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

二 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应用。
其实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.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启动创建了这个异步线程,构建了一个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中完成的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

weixin_42242792

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值