一、Tomcat 启动流程
步骤:
1、启动tomcat,需要调用 bin/startup.bat (在linux 目录下,需要调用 bin/startup.sh),在startup.bat 脚本中,调用了catalina.bat。
2、在catalina.bat 脚本文件中,调用了BootStrap 中的main方法。
3、在BootStrap 的main 方法中调用了init方法 , 来创建Catalina 及初始化类加载器。
4、在BootStrap 的main 方法中调用了load 方法 , 在其中又调用了Catalina的load方法。
5、在Catalina 的load方法中,需要进行一些初始化的工作,并需要构造Digester 对象,用于解析 XML。
6、然后在调用后续组件的初始化操作。加载Tomcat的配置文件,初始化容器组件,监听对应的端口号,准备接受客户端请求。
1.1、相关类解析
1.1.1、Lifecycle
由于所有的组件均存在初始化、启动、停止等生命周期方法,拥有生命周期管理的特性, 所以Tomcat在设计的时候, 基于生命周期管理抽象成了一个接口 Lifecycle ,而组件 Server、Service、Container、Executor、Connector 组件 , 都实现了一个生命周期的接口,从而具有了以下生命周期中的核心方法:
1、init():初始化组件
2、start():启动组件
3、stop():停止组件
4、destroy():销毁组件
1.1.2、各组件的默认实现
上面我们提到的Server、Service、Engine、Host、Context都是接口, 下图中罗列了这些接口的默认实现类。
当前对于 Endpoint组件来说,在Tomcat中没有对应的Endpoint接口, 但是有一个抽象类 AbstractEndpoint ,其下有三个实现类:NioEndpoint、Nio2Endpoint、AprEndpoint , 这三个实现类,分别对应于前面讲解链接器 Coyote时, 提到的链接器支持的三种IO模型:NIO、NIO2、APR,Tomcat8.5版本中,默认采用的是 NioEndpoint。
ProtocolHandler:Coyote协议接口,通过封装Endpoint和Processor , 实现针对具体协议的处理功能。Tomcat按照协议和IO提供了6个实现类。
AJP协议:
1、AjpNioProtocol:采用NIO的IO模型。
2、AjpNio2Protocol:采用NIO2的IO模型。
3、AjpAprProtocol:采用APR的IO模型,需要依赖于APR库。
HTTP协议:
1、Http11NioProtocol:采用NIO的IO模型,默认使用的协议(如果服务器没有安装
APR)。
2、Http11Nio2Protocol:采用NIO2的IO模型。
3、Http11AprProtocol:采用APR的IO模型,需要依赖于APR库。
二、源码分析
众所周知,org.apache.catalina.startup.Bootstrap类的main方法是tomcat启动的入口。
public static void main(String args[]) {
synchronized (daemonLock) {
if (daemon == null) {
// 在 init() 完成之前不要设置守护进程
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.init();
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
daemon = bootstrap;
} else {
// 当作为服务运行时,停止调用将在一个新线程上进行,
// 因此请确保使用正确的类加载器来防止一系列未找到的类异常
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) {
// 解开异常以获得更清晰的错误报告
if (t instanceof InvocationTargetException && t.getCause() != null) {
t = t.getCause();
}
handleThrowable(t);
t.printStackTrace();
System.exit(1);
}
}
在main方法中,Bootstrap主要做三件事:初始化、加载、响应启动或停止命令。
初始化
初始化过程会初始化tomcat所需的类加载器,并将catalinaLoader实例设置为当前线程的上下文类加载器。
public void init() throws Exception {
initClassLoaders(); // 设置当前线程的上下文类加载器
Thread.currentThread().setContextClassLoader(catalinaLoader);
}
根据配置catalina.properties,tomcat会默认初始化三种tomcat特有的类加载器。它们分别是:commonLoader、catalinaLoader和sharedLoader。不过,从源码中我们可以发现,catalinaLoader和sharedLoader默认并没有做任何配置。所以,最终只会初始化一种类加载器commonLoader。
如果在没有作个性化配置的情况下,catalinaLoader和sharedLoader都是指的是commonLoader。
commonLoader是tomcat所有类加载器的父类类加载器。
初始化类加载器之后,接下来,类加载器catalinaLoader的实例会加载类org.apache.catalina.startup.Catalina创建一个Catalina实例。
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance();
接着,利用反射原理,调用Catalina的setParentClassLoader方法将sharedLoader实例设置为Catalina实例的父级类加载器。
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);
最后,将Catalina实例赋值给catalina的守护进程的引用catalinaDaemon。
Object catalinaDaemon = startupInstance;
至此,初始化的工作就全部完成了。
加载
在完成Bootstrap初始化的工作之后,Bootstrap接下来会开始解析main方法的入参args,生成命令字符command。
根据不同类型的命令,Bootstrap会执行不同的业务逻辑,但是这里我们只看“start”命令。
if (command.equals("start")) {
// 设置Catalina实例的await属性为true,表示daemon会被阻塞
// 这里的daemon也就是 Bootstrap 的实例本身
daemon.setAwait(true);
// 调用加载方法
daemon.load(args);
// 启动 Tomcat
daemon.start();
// 如果服务器为空
if (null == daemon.getServer()) {
// 退出程序
System.exit(1);
}
}
Bootstrap首先会调用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);
}
由于是start,数组arguments为空,所以最后会调用Catalina的load()方法。
在这里tomcat会对Catalina实例进行初始化,其中依次包括初始化服务器Server组件、服务Service组件、连接器Connector、引擎Engine。
public void load() {
if (loaded) {
return;
}
loaded = true;
long t1 = System.nanoTime();
initDirs();
// Before digester - it may be needed
initNaming();
// 设置配置源
ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(
Bootstrap.getCatalinaBaseFile(), getConfigFile()
));
File file = configFile();
// 创建并执行我们的 Digester,在这里会初始化各组件
Digester digester = createStartDigester();
try (ConfigurationSource.Resource resource = ConfigFileLoader.getSource().getServerXml()) {
InputStream inputStream = resource.getInputStream();
InputSource inputSource = new InputSource(resource.getURI().toURL().toString());
inputSource.setByteStream(inputStream);
digester.push(this);
digester.parse(inputSource);
} catch (Exception e) {
log.warn(sm.getString("catalina.configFail", file.getAbsolutePath()), e);
if (file.exists() && !file.canRead()) {
log.warn(sm.getString("catalina.incorrectPermissions"));
}
return;
}
getServer().setCatalina(this);
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
// Stream redirection
initStreams();
// 启动新的服务器
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);
}
}
long t2 = System.nanoTime();
if (log.isInfoEnabled()) {
log.info(sm.getString("catalina.init", Long.valueOf((t2 - t1) / 1000000)));
}
}
服务器Server初始化后,就得到了Server对象实例。接下来,调用Server父类LifecycleBase的init()方法。
启动
在所有初始化工作完成之后,接下来Bootstrap就会执行start()方法,启动服务器。
至此,Tomcat完成启动。