执行startup.sh后tomcat各层都做了什么?

概述

启动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 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 定位了相应的容器,并且把容器对象保存到了请求对象中。

你可能感兴趣的:(执行startup.sh后tomcat各层都做了什么?)