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

原文地址:https://blog.csdn.net/weixin_42242792/article/details/120492558

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


&lt;servlet&gt; &lt;servlet-name&gt;tomcatpooljsp&lt;/servlet-name&gt; &lt;jsp-file&gt;/WEB-INF/tomcatpool.jsp&lt;/jsp-file&gt; &lt;/servlet&gt; &lt;servlet-mapping&gt; &lt;servlet-name&gt;tomcatpooljsp&lt;/servlet-nam...
遵循Java Servlet 规范第4节中的建议 ,Apache Tomcat实现了系统地重新加载Java类的方法,以允许在不重新启动整个服务器的情况下更新应用程序的组件。 此功能对于开发非常重要,因为事实证明,随着服务器启动和重启时间的延长,这会严重浪费开发人员的时间。实际上,Java EE堆栈应用服务器的服务器重新启动时间很慢,这是Tomcat广泛用于个人和企业级项目的推动力之一。但是,即使Tomcat也无法 像运行时重新加载应用程序一样快地启动。通过仅重新加载隔离的应用程序的更改的类,开发人员..
JMX(Java管理扩展)是一项非常强大的技术,可让您管理,监视和配置Tomcat MBean。如果您是Tomcat管理员,那么您应该熟悉如何在tomcat中启用JMX来监视堆内存,线程,CPU使用率,类以及配置各种MBean。在本文中,我将讨论如何使用JConsole启用并连接到Tomcat。我假设您已经安装了Tomcat(如果没有);您可以参考安装指南。转到安装了Tomcat的路径 转到bin文件夹 将文件创建为“ setenv.sh” 使用vi编辑器修改文件并添加以下内容
总览介绍 建立 取得Java 获取TomCat 将TomCat安装为Windows服务 将TomCat设置为Linux服务(系统化) 使用Nginx作为反向代理 基本用法 手动启动和停止TomCat 验证TomCat服务器正在运行 服务静态文件 服务Java服务器页面(JSP) 修改设定 部署网络应用 使用管理网页界面 创建一个TomCat管理员用户 访问管理网络应用 管理网络应用 结论 参考链接介绍在最简单的概念中,To.
PSI Probe是Lambda Probe的社区驱动分支,使用相同的开源许可证(GPLv2)分发。它旨在替换和扩展Tomcat Manager,从而使管理和监视Apache Tomcat实例更加容易。与许多其他服务器监视工具不同,PSI Probe不需要对现有应用程序进行任何更改。它通过可访问Web的界面提供所有功能,只需将其部署到服务器即可使用。这些功能包括:请求:即使在每个应用程序的基础上,实时监视流量。 会话:浏览/搜索属性,查看上一个IP,到期,估计大小。 JSP:浏览,查看源代码,进
监视和管理Tomcat目录介绍 启用JMX远程 使用JMX远程Ant任务管理Tomcat JMXAccessorOpenTask-JMX打开连接任务 JMXAccessorGetTask:获取属性值Ant任务 JMXAccessorSetTask:设置属性值Ant任务 JMXAccessorInvokeTask:调用MBean操作Ant任务 JMXAccessorQueryTask:查询MBean Ant任务 JMXAccessorCreateTask:远程创建MBean Ant任
1.tomcat与jetty都是一种servlet引擎,他们都支持标准的servlet规范和javaEE规范
“The origin server did not find a current representation for the target resource...
Artifacts是maven中的一个概念,表示某个module要如何打包,例如war exploded、war、jar、ear等等这种打包形式;
使用 IDEA 编辑器开发项目十分便捷,这里介绍使用 IDEA 编辑器添加 Tomcat
这篇“servlet和tomcat的知识点有哪些”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅...
这篇文章主要讲解了“Tomcat管理平台实例分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Tomcat管理平...
本文小编为大家详细介绍“tomcat虚拟主机怎么配置”,内容详细,步骤清晰,细节处理妥当,希望这篇“tomcat虚拟主机怎么配置”文章能帮助大家解决疑惑,下面跟
今天小编给大家分享一下tomcat相关配置与eclipse集成的方法的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家
这篇“Tomcat之web应用的目录组成结构是怎样的”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,
今天小编给大家分享一下tomcat目录映射的方法的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大...
这篇“tomcat的环境怎么配置”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文...
环境:tomcat:apache-tomcat-7.0.35 cactiEZ:10.1系统:centos5.6_x64一、配置tomcat服务器1、添加账号vim tomcat-users.xml 重启tomcat2、安装snmp协议yum...
一、 软环下载地址软件链接地址https://files.cnblogs.com/files/jinrf/openssl-1.0.2-latest.tar.gzhttps://files.cnblogs.com/files/jinrf/apr-util-1.6...