概述
启动tomcat的时候一般都会执行bin目录下的startup.sh或者startup.bat(Windows系统下),执行此脚本后具体是怎么启动tomcat的呢?请看下图
20200618-1.png
- 执行tomcat启动类Bootstrap
- 初始化tomcat的类加载器(tomcat实现了自己的类加载器来加载类,需要单独说明)
- 创建Catalina
- Catalina是tomcat的启动类,用于解析server.xml并创建server组件,并调用其start方法
- Server组件管理service组件并调用其start方法
- Service组件管理Engine和各个连接器组件,并调用他们的start方法
这样就完成了整个tomcat的启动。
Catalina
public void start() {
// 如果server为空,读取server.xml创建server
if (this.getServer() == null) {
this.load();
}
// 此时说明创建server失败,则报错退出
if (this.getServer() == null) {
log.fatal("Cannot start server. Server instance is not configured.");
} else {
long t1 = System.nanoTime();
// 启动server
try {
this.getServer().start();
} catch (LifecycleException var7) {
log.fatal(sm.getString("catalina.serverStartFail"), var7);
try {
this.getServer().destroy();
} catch (LifecycleException var6) {
log.debug("destroy() failed for failed Server ", var6);
}
return;
}
long t2 = System.nanoTime();
if (log.isInfoEnabled()) {
log.info("Server startup in " + (t2 - t1) / 1000000L + " ms");
}
// 创建并注册关闭钩子
if (this.useShutdownHook) {
if (this.shutdownHook == null) {
this.shutdownHook = new Catalina.CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(this.shutdownHook);
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager)logManager).setUseShutdownHook(false);
}
}
// 监听关闭请求
if (this.await) {
this.await();
this.stop();
}
}
}
catalina的start方法如上,参考注释部分。
关闭钩子的作用是什么呢?
为了应对异常关闭的情况,比如执行“Ctrl + C”关闭,此时出发钩子线程执行必要的清理工作,实现优雅关闭。
什么是关闭钩子呢?
其实就是一个线程,jvm在关闭之前,会调用该线程的run方法
tomcat的关闭钩子干了啥?
protected class CatalinaShutdownHook extends Thread {
@Override
public void run() {
try {
if (getServer() != null) {
Catalina.this.stop();
}
} catch (Throwable ex) {
...
}
}
}
实际上就是调了Catalina的stop方法,stop方法会释放和清理所有资源
Server组件
具体实现类是StandardServer,Server继承了LifecycleBase接口,子组件为Service,所以还需要管理Service的生命周期。
public void addService(Service service) {
service.setServer(this);
synchronized(this.servicesLock) {
//创建长度为原数组长度+1的数组
Service[] results = new Service[this.services.length + 1];
//复制数据到新数组
System.arraycopy(this.services, 0, results, 0, this.services.length);
//设置数组的最后一个元素为当前service
results[this.services.length] = service;
this.services = results;
// 启动service组件
if (this.getState().isAvailable()) {
try {
service.start();
} catch (LifecycleException var6) {
}
}
//触发监听事件
this.support.firePropertyChange("service", (Object)null, service);
}
}
- 代码中对于service数据长度的分配,每次创建时数组长度加1动态分配,不会一开始就分配很大,节约内存
- server组件有个功能就是启动一个Socket监听停止端口,这也是可以使用shutdown命令关闭tomcat的原因
- Catalina的start方法里this.await();一行,最终调用的是server的await()方法
- server的await()方法会创建一个Socket监听8005端口
- 如果该端口有连接到来就建立连接读取数据,如果读到的是SHUTDOWN命令就进入stop流程
Service组件
Service 组件的具体实现类是 StandardService,StandardService 继承了 LifecycleBase 抽象类,看看其成员变量
public class StandardService extends LifecycleBase implements Service {
//名字
private String name = null;
//Server实例
private Server server = null;
//连接器数组
protected Connector connectors[] = new Connector[0];
private final Object connectorsLock = new Object();
//对应的Engine容器
private Engine engine = null;
//映射器及其监听器
protected final Mapper mapper = new Mapper();
protected final MapperListener mapperListener = new MapperListener(this);
MapperListener是干什么的呢?
tomcat支持热部署,当web应用发生变化时,Mapper中的配置信息也需要跟着变化
MapperListener实际就是一个监听器,监听容器变化,实时更新信息到Mapper中
这里是典型的观察者模式的使用
service如果管理其他组件?
protected void startInternal() throws LifecycleException {
//1. 触发启动监听器
setState(LifecycleState.STARTING);
//2. 先启动Engine,Engine会启动它子容器
if (engine != null) {
synchronized (engine) {
engine.start();
}
}
//3. 再启动Mapper监听器
mapperListener.start();
//4.最后启动连接器,连接器会启动它子组件,比如Endpoint
synchronized (connectorsLock) {
for (Connector connector: connectors) {
if (connector.getState() != LifecycleState.FAILED) {
connector.start();
}
}
}
}
看顺序是现启动Engine再启动Mapper监听器,最后启动连接器组件
原因是先启动内部组件并监听,然后再启动外部组件对外提供服务
Engine组件
- engine本身是个容器,继承了 ContainerBase 基类,并且实现了 Engine 接口
- engine的子容器是host,它持有一个Host容器数组
protected final HashMap<String, Container> children = new HashMap<>();
对于容器的启停管理,增删改查等功能都抽象到了ContainerBase基础类中,注意ContainerBase 会用专门的线程池来启动子容器。
for (int i = 0; i < children.length; i++) {
results.add(startStopExecutor.submit(new StartChild(children[i])));
}
- 所以Engine启动host时直接使用了该方法
- Engine做了什么事呢?其实就是把请求转发给某个Host来处理
- Engine如何知道请求该转发到哪个Host呢?请求到达 Engine 容器中之前,Mapper 组件已经对请求进行了路由处理,Mapper 组件通过请求的 URL 定位了相应的容器,并且把容器对象保存到了请求对象中。
网友评论