Tomcat启动过程是怎么样的?

一、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完成启动。

  • 18
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值