当你双击startup.bat,或者通过ide运行一个web项目时,Tomcat就开始了它漫长而又有序的启动过程。
打开startup可以看到,它仅仅是一个门面,负责调用真正的启动文件catalina.sh。
所以我们找到catalina.sh并打开,就会发现它带着一些参数去调用了Tomcat的启动类Bootstrap,然后在Bootstrap中加载各式各样的组件。
组件启动过程
先来看一张时序图:
可以看到整个启动流程是比较清晰的,它顺着server.xml中定义的组件层层启动,并且启动方式完全一致:init()--> start()。
ok,让我们把源码部署下来,启动Bootstrap看看。
public static void main(String args[]) {
//首先判断Bootstrap daemon 是否为空,就是创建一个Bootstrap实例daemon
if (daemon == null) {
// Don't set daemon until init() has completed
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.init();
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
daemon = bootstrap;
} else {
Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
}
try {
//指定默认的执行指令是start
String command = "start";
if (args.length > 0) {
command = args[args.length - 1];
}
if (command.equals("startd")) {
args[args.length - 1] = "start";
//BootStrap的load方法,其实是在方法内部调用Calatina的load方法
daemon.load(args);
daemon.start();
} else if (command.equals("stopd")) {
args[args.length - 1] = "stop";
daemon.stop();
} else if (command.equals("start")) {
//setWait方法 主要是为了设置Server启动完成后是否进入等待状态的标志,如果为true则进入,否则不进入
daemon.setAwait(true);
//load方法用于加载配置文件,创建并初始化Server
daemon.load(args);
//start方法用于启动服务器
daemon.start();
} 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(),我们将断点打在这里,可以看到它实例化了一个Catalina对象并赋值:
if (log.isDebugEnabled())
log.debug("Loading startup class");
Class<?> startupClass = catalinaLoader
.loadClass("org.apache.catalina.startup.Catalina");
...
catalinaDaemon = startupInstance;
第二个断点打在load(),可以看出它通过反射来调用了Catalina的load():
第三个断点找到Catalina.load(),可以看到它要调用Server的init():
找一下这个所谓的getServer(),发现其是一个StandServer对象:
问题就来了,StandardServer中并没有一个init(),我们只好查看它的父类:
直到LifecycleBase,我们才找到了所谓的init()。一个典型的模板方法模式,StandardServer只需要实现钩子initInternal()即可。
那么跟紧节奏,我们返回到StandardServer.initInternal(),可以看到它循环的调用了所有Service的init():
找到这个Service数组,哦原来是个StandardService。
是不是很眼熟,心里已经有猜测了,我们看一眼类图来确认想法。
对比Server,一模一样?后面的组件就不再跟进啦,是雷同的。
组件生命周期管理
Tomcat会逐步唤醒需要用到的每一个组件。那么,Tomcat是怎么做到有条不紊的管理呢?
通过上面的分析我们也知道了,LifeBase来管理了Server及以下所有组件的初始化,如果具体的Server有个性化行为,只需要重写initInternal()这个钩子方法即可。
为什么LifecycleBase这么玩?
其实很多架构源码都是这么做的,包括JDK的容器源码。
一个类,有一个接口,同时抽象一个抽象骨架类,把通用的实现放在抽象骨架类中,这样设计就方便组件的管理。
组件的结构
小知识:有没有发现,多数组件都是一个接口,然后有一个叫StandardXXX的实现类。 Engine、Host、Context、Wrapper它们各自的实现类分别是StandardEngine,StandardHost,StandardContext, StandardWrapper,他们都在tomcat的org.apache.catalina.core包下。
它们之间的关系,可以查看tomcat的server.xml也能明白(根据节点父子关系),并且他们四个Standard 的 container都是直接继承抽象类:org.apache.catalina.core.ContainerBase: