上篇文章我们介绍了Tomcat的核心组件的的基本功能以及在Tomcat中的实现,但是这些组件类是如何被启动起来的?比如我们知道Context组件的功能是管理子容器Wraper的生命周期,那么在Context组件启动时,肯定要创建Wrapper实例并启动Wrapper。同理,我们知道Tomcat这么多组件,肯定有一个先后的启动过程(因为Tomcat的组件本来就是分层级的)。本篇文章我们就来介绍一下,Tomcat是怎么启动的。
使用过Tomcat的我们都知道,可以通过Tomcat的/bin目录下的脚本startup.sh来启动Tomcat,那么这个脚本肯定就是Tomcat的启动入口了,执行过这个脚本之后发生了什么呢?
- Tomcat本质上也是一个Java程序,因此startup.sh脚本会启动一个JVM来运行Tomcat的启动类 Bootstrap
- Bootstrap的主要任务是初始化Tomcat的类加载器,并且创建Catalina。关于Tomcat为什么需要自己的类加载器,我们后面再介绍
- Catalina是一个启动类,它通过解析server.xml,创建相应的组件,并调用 Server的start方法
- Server组件的职责就是管理Service组件,它会负责调用Service的start方法
- Service组件的职责就是管理连接器和顶层容器Engine,它会调用连接器和 Engine的start方法
- Engine组建负责启动管理子容器,通过调用Host的start方法,将Tomcat各层容器启动起来(这里是分层级的,上层容器管理下层容器)
1. Tomcat一键启停机制——LifeCycle
我们再来回顾一下Tomcat各组件之间的关系,很容易发现:如果想让Tomcat能够对外提供服务,我们需要创建、组装并启动这些组件;在服务停止的时候,我们还需要释放资源,销毁这些组件,因此这是一个动态的过程。也就是说,Tomcat 需要动态地管理这些组件的生命周期。
Tomcat这么大一个框架,如何统一管理组件的创建、初始化、启动、停止和销毁?如何做到代码逻辑清晰?如何方便地添加或者删除组件?如何做到组件启动和停止不遗漏、不重复?
在回答上述问题之前,先来看看组件之间的关系,可以发现它们具有两层关系:
- 组件有大有小,大组件管理小组件,比如Server管理Service,Service又管理连接器和容器
- 组件有外有内,外层组件控制内层组件,比如连接器是外层组件,负责对外交流,外层组件调用内层组件完成业务功能。也就是说,请求的处理过程是由外层组件来驱动的
这两层关系决定了系统在创建组件时应该遵循一定的顺序:
- 先创建子组件,再创建父组件,子组件需要被“注入”到父组件中
- 先创建内层组件,再创建外层组件,内层组建需要被“注入”到外层组件
因此,最直观的做法就是将图上所有的组件按照先小后大、先内后外的顺序创建出来,然后组装在一起。但实际上这个思路其实很有问题,因为这样不仅会造成代码逻辑混乱和组件遗漏,而且也不利于后期的功能扩展。
所以需要一个一种通用的、统一的方法来管理Tomcat各组件的生命周期,最好可以实现“一键启动”的效果。而Tomcat就是用LifeCycle实现了Tomcat一键启停的机制。
1.1 什么是LifeCycle
文章最开始介绍的每个组件都要经历创建、初始化、启动这几个过程,这些状态以及状态的转化是不变的。而每个具体组件的初始化方法,启动方法是不一样的。Tomcat把不变的抽象出来成为一个接口,这个接口跟生命周期有关,叫作 LifeCycle。LifeCycle接口定义如下,每个具体的组件去实现这些方法。
public interface Lifecycle {
// 添加监听器
public void addLifecycleListener(LifecycleListener listener);
// 获取所以监听器
public LifecycleListener[] findLifecycleListeners();
// 移除某个监听器
public void removeLifecycleListener(LifecycleListener listener);
// 初始化方法
public void init() throws LifecycleException;
// 启动方法
public void start() throws LifecycleException;
// 停止方法,和start对应
public void stop() throws LifecycleException;
// 销毁方法,和init对应
public void destroy() throws LifecycleException;
// 获取生命周期状态
public LifecycleState getState();
// 获取字符串类型的生命周期状态
public String getStateName();
}
这里我们把LifeCycle接口定义分为两部分,一部分是组件的生命周期方法,比如init()、start()、stop()、destroy()。另一部分是扩展接口就是状态和监听器,关于状态和监听器,我们文章下面再介绍。
因为所有的组件都实现了LifeCycle接口,如果可以在父组件的init()方法里创建子组件并调用子组件的init()方法,在父组件的start()方法里调用子组件的start()方法,那么调用者就可以无差别的调用各组件的init()方法和start()方法,并且只要调用最顶层组件,也就是Server组件的init()和start()方法,整个Tomcat就被启动起来了。
1.2 LifeCycleBase抽象类
有了LifeCycle接口,我们就要用类去实现接口。一般来说实现类不止一个,不同的类在实现接口时往往会有一些相同的逻辑,如果让各个子类都去实现一遍,就会有重复代码。那子类如何重用这部分逻辑呢?其实就是定义一个基类来实现共同的逻辑,然后让各个子类去继承它,就达到了重用的目的。基类中往往会定义一些抽象方法,并调用这些方法来实现骨架逻辑,抽象方法是留给各个子类去实现,如下图所示:
LifeCycleBase实现了LifeCycle接口中所有的方法,还定义了相应的抽象方法交给具体子类去实现,这是典型的模板设计模式。
我们来简单看一下LifeCycleBase的init方法,如下:
- 检查状态的合法性,比如当前状态必须是NEW然后才能进行初始化
- 触发INITIALIZING事件的监听器,setStateInternal()方法中,会调用监听器的业务方法
- 调用具体子类实现的抽象方法initInternal()方法。为了实现一键式启动,具体组件在实现initInternal()方法时,又会调用它的子组件的init()方法
- 子组件初始化后,触发INITIALIZED事件的监听器,相应监听器的业务方法就会被调用
从而实现调用顶层组建的init方法就可以实现顶层及子组件的初始化,start()方法一个道理,这里不多介绍了。
1.3 LifeCycle扩展——事件监听机制
各个组件init()和start()方法的具体实现是复杂多变的,比如在Host容器的启动方法里需要扫描webapps目录下的Web 应用,创建相应的Context容器,如果将来需要增加新的逻辑,直接修改start()方法会违反开闭原则(为了扩展系统的功能, 你不能直接修改系统中已有的类,但是你可以定义新的类),那如何解决这个问题呢?
组件的init()和start()调用是由它的父组件的状态变化触发的,上层组件的初始化会触发子组件的初始化,上层组件的启动会触发子组件的启动。因此我们把组件的生命周期定义成一个个状态,把状态的转变看作是一个事件。而事件是有监听器的,在监听器里可以实现一些逻辑,并且监听器也可以方便的添加和删除,这就是典型的观察者模式。
这就是为什么LifeCycle接口中有两个添加和删除监听器的方法的原因。但除了上述监听器维护方法,我们还需要定义一些状态。所以Tomcat定义一个枚举LifeCycleState来表示组件有哪些状态。
可以看到组件的生命周期有NEW、INITIALIZING、INITIALIZED、STARTING_PREP、STARTING、STARTED等,而一旦组件到达相应的状态就触发相应的事件,比如 NEW状态表示组件刚刚被实例化。而当init()方法被调用时,状态就变成INITIALIZING 状态,这个时候,就会触发BEFORE_INIT_EVENT事件,如果有监听器在监听这个事件,它的方法就会被调用。
下面我们来看一下在上述init方法中触发事件监听的方法setStateInternal:
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);
// lifecycleListeners为监听器数组
for (LifecycleListener listener : lifecycleListeners) {
// 触发监听器的监听逻辑
listener.lifecycleEvent(event);
}
}
可以看到,通过setStateInternal方法,将LifeCycleBase的state成员变量设置为新的状态,并且触发了监听器的监听逻辑。那么什么时候、谁把监听器注册进来的呢?主要分为以下两种情况:
- Tomcat自定义了一些监听器,这些监听器是父组件在创建子组件的过程中注册到子组件的。比如MemoryLeakTrackingListener监听器,用来检测 Context容器中的内存泄漏,这个监听器是Host容器在创建Context容器时注册到Context中的
- 还可以在server.xml中定义自己的监听器,Tomcat在启动时会解析 server.xml,创建监听器并注册到容器组件
到这里我们可以得出结论:
- Tomcat通过LifeCycle实现了一键启停(调用顶层组件的init方法就可以实现所有组件的初始化,调用顶层组件的start方法就可以完成所有组件的启动)
- Tomcat通过LifeCycle事件监听机制,实现了对生命周期过程的优雅扩展
- 另外LifeCycle还实现了一个状态机,管理LifeCycle的状态转化(具体在LifeCycleBase的init、start、stop、destroy方法中,有兴趣可以去看一下,代码比较简单),比如:
- 当组件在
STARTING_PREP
、STARTING
或STARTED
时,调用start()
方法没有任何效果 - 当组件在
NEW
状态时,调用start()
方法会导致init()
方法被立刻执行,随后start()
方法被执行 - 当组件在
STOPPING_PREP
、STOPPING
或STOPPED
时,调用stop()
方法没有任何效果 - 当一个组件在
NEW
状态时,调用stop()
方法会将组件状态变更为STOPPED
,比较典型的场景就是组件启动失败,其子组件还没有启动。当一个组件停止的时候,它将尝试停止它下面的所有子组件,即使子组件还没有启动
- 当组件在
2. Bootstrap启动入口
2.1 main()
本文最开始,我们介绍到,Tomcat是通过startup.sh调用了Bootstra的main方法启动的,这里我们来分析一下Bootstrap的main方法。
public static void main(String args[]) {
synchronized (daemonLock) {
if (daemon == null) {
// Don't set daemon until init() has completed
Bootstrap bootstrap = new Bootstrap();
try {
// 1. 初始化Bootstrap,主要包括实例化Tomcat特有的类加载器和Catalina实例
bootstrap.init();
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
daemon = bootstrap;
} else {
// When running as a service the call to stop will be on a new
// thread so make sure the correct class loader is used to
// prevent a range of class not found exceptions.
Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
}
}
try {
String command = "start";
if (args.length > 0) {
command = args[args.length - 1];
}
if (command.equals("startd")) {
args[args.length - 1] = "start";
daemon.load(args);
daemon.start();
} else if (command.equals("stopd")) {
args[args.length - 1] = "stop";
daemon.stop();
} else if (command.equals("start")) {
daemon.setAwait(true);
daemon.load(args);
daemon.start();
if (null == daemon.getServer()) {
System.exit(1);
}
} else if (command.equals("stop")) {
daemon.stopServer(args);
} else if (command.equals("configtest")) {
daemon.load(args);
if (null == daemon.getServer()) {
System.exit(1);
}
System.exit(0);
} else {
log.warn("Bootstrap: command \"" + command + "\" does not exist.");
}
} catch (Throwable t) {
// Unwrap the Exception for clearer error reporting
if (t instanceof InvocationTargetException &&
t.getCause() != null) {
t = t.getCause();
}
handleThrowable(t);
t.printStackTrace();
System.exit(1);
}
}
核心方法就是调用init方法初始化deamon成员变量,以及当接收到start命令时,调用deamon的load和start方法启动。
2.2 init()
下面来看一下Bootstrap的init方法:
public void init() throws Exception {
// 1. 初始化Tomcat类加载器
initClassLoaders();
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
// Load our startup class and call its process() method
if (log.isDebugEnabled())
log.debug("Loading startup class");
// 2. 实例化Catalina实例
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance();
// 3. 反射调用Catalina的setParentClassLoader方法,将sharedLoader设置为Catalina的parentClassLoader成员变量
if (log.isDebugEnabled())
log.debug("Setting startup class properties");
String methodName = "setParentClassLoader";
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = Class.forName("java.lang.ClassLoader");
Object paramValues[] = new Object[1];
paramValues[0] = sharedLoader;
Method method =
startupInstance.getClass().getMethod(methodName, paramTypes);
method.invoke(startupInstance, paramValues);
catalinaDaemon = startupInstance;
}
- 创建commonLoader、catalinaLoader、sharedLoader类加载器(关于Tomcat的类加载器,后面文章单独介绍,这里我们把关注点放在Tomcat的启动上)
- 实例化org.apache.catalina.startup.Catalina对象,并赋值给静态成员 catalinaDaemon,以sharedLoader作为入参通过反射调用该对象的 setParentClassLoader方法
这里如果不深入探讨Tomcat类加载器的话,我们可以简单理解为init方法,就是用来构造Catalina实例的。
2.3 load()
private void load(String[] arguments) throws Exception {
// Call the load() method
String methodName = "load";
Object param[];
Class<?> paramTypes[];
if (arguments==null || arguments.length==0) {
paramTypes = null;
param = null;
} else {
paramTypes = new Class[1];
paramTypes[0] = arguments.getClass();
param = new Object[1];
param[0] = arguments;
}
Method method =
catalinaDaemon.getClass().getMethod(methodName, paramTypes);
if (log.isDebugEnabled()) {
log.debug("Calling startup class " + method);
}
method.invoke(catalinaDaemon, param);
}
通过反射调用catalinaDaemon对象的load方法,catalinaDaemon对象就是在上面的init方法中实例化的。Catalina的load方法,我们下面再介绍,不过我们可以猜想,Catalina的load方法中应该会完成一些Tomcat组件的初始化操作。
2.4 start()
public void start() throws Exception {
if (catalinaDaemon == null) {
init();
}
Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
method.invoke(catalinaDaemon, (Object [])null);
}
start方法比较简单,就是通过反射调用catalinaDaemon对象上的start方法,该start方法,完成了Tomcat的启动。
3. Catalina
通过上面的介绍我们知道,Bootstrap的main方法中,就是调用了Catalina的load和start方法,从而完成了Tomcat的启动。这里我们来继续看一下这两个方法。
3.1 load()
public void load() {
if (loaded) {
return;
}
loaded = true;
long t1 = System.nanoTime();
initDirs();
// Before digester - it may be needed
initNaming();
// 1. 解析server.xml,实例化各Tomcat组件
parseServerXml(true);
Server s = getServer();
if (s == null) {
return;
}
// 2. 为Server组件实例设置Catalina相关成员value
getServer().setCatalina(this);
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
// Stream redirection
initStreams();
// 3. 调用Server组件的init方法,初始化Tomcat各组件
try {
getServer().init();
} catch (LifecycleException e) {
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
throw new java.lang.Error(e);
} else {
log.error(sm.getString("catalina.initError"), e);
}
}
if(log.isInfoEnabled()) {
log.info(sm.getString("catalina.init", Long.toString(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - t1))));
}
}
Catalina的init方法主要完成server.xml的解析以及Tomcat各组建的初始化。这里要着重强调的的有两点:
- parseServerXml()方法完成了xml配置到Tomcat各组件的Java对象的转化,也就是说,通过调用该方法,我们之前介绍的Tomcat各组件都实例化出来了(这里parseServerXml()方法解析的细节以及关于使用web.xml配置Context,我们后面通过专门的文章再介绍)
- getServer().init()方法,使用了我们上面介绍的LifeCycle机制,完成了Server及以下组件的初始化
3.2 start()
public void start() {
if (getServer() == null) {
load();
}
if (getServer() == null) {
log.fatal(sm.getString("catalina.noServer"));
return;
}
long t1 = System.nanoTime();
// 1. start server
try {
getServer().start();
} catch (LifecycleException e) {
log.fatal(sm.getString("catalina.serverStartFail"), e);
try {
getServer().destroy();
} catch (LifecycleException e1) {
log.debug("destroy() failed for failed Server ", e1);
}
return;
}
if (log.isInfoEnabled()) {
log.info(sm.getString("catalina.startup", Long.toString(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - t1))));
}
// Register shutdown hook
if (useShutdownHook) {
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook);
// If JULI is being used, disable JULI's shutdown hook since
// shutdown hooks run in parallel and log messages may be lost
// if JULI's hook completes before the CatalinaShutdownHook()
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(
false);
}
}
if (await) {
await();
stop();
}
}
主要是调用Server组件的start方法,启动Tomcat的所有组件。
到这里我们知道,Tomcat是通过调用Catalina的load()和start()方法,完成了Tomcat的启动。load()方法负责组件的初始化,start()方法负责组件的启动。关于Tomcat组件的初始化,其中Host及其下级组件的初始化比较特殊,并没有在Engine的初始化方法中完成Host及下级组件的初始化。另外Context的实例化和启动过程也比较特殊(在上述parseServerXml方法中,并没有实例化,是在Host组件启动时动态生成的),另外我们知道Tomcat web应用支持热更新,也与Context的启动策略有关。关于Context实例化和初始化过程,我们先挖个坑,本篇文章中不做详细介绍,在后面用专门的文章详细介绍。
4. Server
这里我们着重来介绍一下Server组件的init和start方法。我们知道LifeCycle机制,init方法其实最终调用的是StandardServer的initInternal方法,start方法最终调用的是StandardServer的initInternal方法。
4.1 initInternal()
protected void initInternal() throws LifecycleException {
// 1. 调用LifeCycleMBeanBase的initInternal方法,注册JMX Mbean
super.initInternal();
// Initialize utility executor
reconfigureUtilityExecutor(getUtilityThreadsInternal(utilityThreads));
register(utilityExecutor, "type=UtilityExecutor");
// Register global String cache
// Note although the cache is global, if there are multiple Servers
// present in the JVM (may happen when embedding) then the same cache
// will be registered under multiple names
onameStringCache = register(new StringCache(), "type=StringCache");
// Register the MBeanFactory
MBeanFactory factory = new MBeanFactory();
factory.setContainer(this);
onameMBeanFactory = register(factory, "type=MBeanFactory");
// Register the naming resources
globalNamingResources.init();
// Populate the extension validator with JARs from common and shared
// class loaders
if (getCatalina() != null) {
ClassLoader cl = getCatalina().getParentClassLoader();
// Walk the class loader hierarchy. Stop at the system class loader.
// This will add the shared (if present) and common class loaders
while (cl != null && cl != ClassLoader.getSystemClassLoader()) {
if (cl instanceof URLClassLoader) {
URL[] urls = ((URLClassLoader) cl).getURLs();
for (URL url : urls) {
if (url.getProtocol().equals("file")) {
try {
File f = new File (url.toURI());
if (f.isFile() &&
f.getName().endsWith(".jar")) {
ExtensionValidator.addSystemResource(f);
}
} catch (URISyntaxException e) {
// Ignore
} catch (IOException e) {
// Ignore
}
}
}
}
cl = cl.getParent();
}
}
// 2. 调用所有子组件Service的init方法,初始化service
for (Service service : services) {
service.init();
}
}
4.2 startInternal()
protected void startInternal() throws LifecycleException {
// 1. 触发LifeCycle事件监听
fireLifecycleEvent(CONFIGURE_START_EVENT, null);
setState(LifecycleState.STARTING);
globalNamingResources.start();
// 2. 启动所有Server的子组件Service
synchronized (servicesLock) {
for (Service service : services) {
service.start();
}
}
if (periodicEventDelay > 0) {
monitorFuture = getUtilityExecutor().scheduleWithFixedDelay(
new Runnable() {
@Override
public void run() {
startPeriodicLifecycleEvent();
}
}, 0, 60, TimeUnit.SECONDS);
}
}
5. Service
5.1 initInternal()
protected void initInternal() throws LifecycleException {
super.initInternal();
if (engine != null) {
engine.init();
}
// Initialize any Executors
for (Executor executor : findExecutors()) {
if (executor instanceof JmxEnabled) {
((JmxEnabled) executor).setDomain(getDomain());
}
executor.init();
}
// Initialize mapper listener
mapperListener.init();
// Initialize our defined Connectors
synchronized (connectorsLock) {
for (Connector connector : connectors) {
connector.init();
}
}
}
- 首先,往jmx中注册StandardService
- 初始化Engine
- 如果存在Executor线程池,进行init操作,这个Excecutor是tomcat的接口,继承至java.util.concurrent.Executor和org.apache.catalina.Lifecycle
- 初始化Connector连接器,默认有http1.1、ajp连接器,而这个Connector初始化过程,又会对ProtocolHandler进行初始化
5.2 startInternal()
protected void startInternal() throws LifecycleException {
if(log.isInfoEnabled())
log.info(sm.getString("standardService.start.name", this.name));
setState(LifecycleState.STARTING);
// Start our defined Container first
if (engine != null) {
synchronized (engine) {
engine.start();
}
}
synchronized (executors) {
for (Executor executor: executors) {
executor.start();
}
}
mapperListener.start();
// Start our defined Connectors second
synchronized (connectorsLock) {
for (Connector connector: connectors) {
// If it has already failed, don't try and start it
if (connector.getState() != LifecycleState.FAILED) {
connector.start();
}
}
}
}
跟上述initInternal方法比较类似,只不过是调用各子组件(Engine、Connector)的start方法,启动子组件。
6. Engine
6.1 initInternal()
protected void initInternal() throws LifecycleException {
// Ensure that a Realm is present before any attempt is made to start
// one. This will create the default NullRealm if necessary.
getRealm();
super.initInternal();
}
可以看到,Engine的init()初始化方法中,并没有去调用子组件Host的init方法去初始化Host,那么Host组件是在什么时候初始化的呢?答案是在Host的start方法中,在start方法中先进行Host组件的初始化,然后再进行启动操作。
至于为什么没有在Engine的初始化方法中,初始化子组件Host。可能是出于效率考虑。Host、Context、Wrapper这些容器和具体的webapp应用相关联了,初始化过程会更加复杂耗时,在start阶段用多线程完成初始化以及start生命周期,效率可能会更高。
6.2 startInternal()
protected synchronized void startInternal() throws LifecycleException {
// Log our server identification information
if (log.isInfoEnabled()) {
log.info(sm.getString("standardEngine.start", ServerInfo.getServerInfo()));
}
// Standard container startup
super.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);
}
}
可以看到,在ContainerBase中,使用了多线程来启动子组件。并且在最后通过setState(LifecycleState.STARTING)触发了事件监听。
这里启动子组件,使用了线程池,将子组件的启动任务提交到线程池。
private static class StartChild implements Callable<Void> {
private Container child;
public StartChild(Container child) {
this.child = child;
}
@Override
public Void call() throws LifecycleException {
child.start();
return null;
}
}
可以看到,线程池中执行的任务最终调用的还是子组件(Host)的start()方法。我们来看一下Host的start()方法,其实Host的start方法是继承自ContainerBase的,如下:
public final synchronized void start() throws LifecycleException {
if (LifecycleState.STARTING_PREP.equals(state) || LifecycleState.STARTING.equals(state) ||
LifecycleState.STARTED.equals(state)) {
if (log.isDebugEnabled()) {
Exception e = new LifecycleException();
log.debug(sm.getString("lifecycleBase.alreadyStarted", toString()), e);
} else if (log.isInfoEnabled()) {
log.info(sm.getString("lifecycleBase.alreadyStarted", toString()));
}
return;
}
if (state.equals(LifecycleState.NEW)) {
init();
} else if (state.equals(LifecycleState.FAILED)) {
stop();
} else if (!state.equals(LifecycleState.INITIALIZED) &&
!state.equals(LifecycleState.STOPPED)) {
invalidTransition(Lifecycle.BEFORE_START_EVENT);
}
try {
setStateInternal(LifecycleState.STARTING_PREP, null, false);
startInternal();
if (state.equals(LifecycleState.FAILED)) {
// This is a 'controlled' failure. The component put itself into the
// FAILED state so call stop() to complete the clean-up.
stop();
} else if (!state.equals(LifecycleState.STARTING)) {
// Shouldn't be necessary but acts as a check that sub-classes are
// doing what they are supposed to.
invalidTransition(Lifecycle.AFTER_START_EVENT);
} else {
setStateInternal(LifecycleState.STARTED, null, false);
}
} catch (Throwable t) {
// This is an 'uncontrolled' failure so put the component into the
// FAILED state and throw an exception.
handleSubClassException(t, "lifecycleBase.startFail", toString());
}
}
可以看到,如果如果state为LifecycleState.NEW,会先进性初始化,然后在调用startInternal启动组件。这就是我们上面介绍的,Engine并没有在init方法中初始化子组件Host,而是在启动过程start方法中初始化的。
7. Host
7.1 initInternal()
protected void initInternal() throws LifecycleException {
reconfigureStartStopExecutor(getStartStopThreads());
super.initInternal();
}
可以看到Host的init方法中也没有初始化子组件Context,Context组件的初始化跟Host组件初始化类似,也是跟随者Context的启动start过程初始化的。
7.2 startInternal()
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的start方法中,仅仅是为Host的piline添加了一些Valve,随后调用了父类ContainerBase的startInternal方法。虽然ContainerBase的startInternal方法中可以启动子组件,但好像server.xml中并么有配置Host子组件Context相关的信息,所以也没办法去启动Context组件。那么Context子组件是如何实例化,并初始化、启动的的呢?这个过程比较复杂,我们后面的文章单独介绍。
关于Tomcat的启动过程,我们先介绍到这,至于Context和Wrapper组件的初始化和启动过程,我们后面再专门介绍。本文,我们介绍了Tomcat如何实现了一键启停,主要介绍了LifeCycle机制和一些基础类的初始化启动过程,相信通过本文,大家可以对Tomcat的“一键启停”过程有一个大致的认识。
参考链接:
1. Tomcat源码
2. 《深入了解Tomcat&Jetty》