Tomcat源码分析-启动分析(二) Catalina初始化

本文深入分析Tomcat的启动过程,重点在于Bootstrap的main方法和Catalina的初始化。从Bootstrap的main方法开始,初始化ClassLoader,接着探讨Catalina的load和start过程,包括Server、Service、Engine等组件的初始化。文章详细描述了每个组件初始化的步骤,如Server的生命周期管理、Service的Executor线程池和Engine的Realm配置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Bootstrap

Tomcat运行是通过Bootstrap的main方法,在开发工具中,我们只需要运行Bootstrap的main方法,便可以启动tomcat进行代码调试和分析。Bootstrap是tomcat的入口,它会完成初始化ClassLoader,实例化Catalina以及load、start动作。在这一篇文章中,我们将会对tomcat初始化过程进行分析。

main方法

首先实例化Bootstrap,并调用init方法对其初始化

Bootstrap bootstrap = new Bootstrap();

init

首先初始化commonLoader、catalinaLoader、sharedLoader,默认情况下这三个是相同的实例,用于加载不同的资源。然后,使用反射实例化Catalina,设置其parentClassLoader为sharedLoader

public void init() throws Exception {

    // 初始化commonLoader、catalinaLoader、sharedLoader,关于ClassLoader的后面再单独分析
    initClassLoaders();

    Thread.currentThread().setContextClassLoader(catalinaLoader);
    SecurityClassLoad.securityClassLoad(catalinaLoader);

    // 反射方法实例化Catalina,后面初始化Catalina也用了很多反射,不知道意图是什么
    Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
    Object startupInstance = startupClass.getConstructor().newInstance();

    // 反射调用setParentClassLoader方法,设置其parentClassLoader为sharedLoader
    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实例
    catalinaDaemon = startupInstance;

load & start

初始化Bootstrap之后,接下来就是加载配置,启动容器。而load、start实际上是由Bootstrap反射调用Catalina的load、start,这一部分代码将在下面的Catalina部分进行分析

  • 启动时,Catalina.setAwait(true),其目的是为了让tomcat在关闭端口阻塞监听关闭命令,参考Catalina.await()方法
  • deamon.load(args),实际上会去调用Catalina#load(args)方法,会去初始化一些资源,优先加载conf/server.xml,找不到再去加载server-embed.xml;此外,load方法还会初始化Server
  • daemon.start(),实例上是调用Catalina.start()
// daemon即Bootstrap实例
daemon.setAwait(true);
daemon.load(args);

Catalina

由前面的分析,可知Bootstrap中的load逻辑实际上是交给Catalina去处理的,下面我们对Catalina的初始化过程进行分析

load(init)

load阶段主要是通过读取conf/server.xml或者server-embed.xml,实例化Server、Service、Connector、Engine、Host等组件,并调用Lifecycle#init()完成初始化动作,以及发出INITIALIZING、INITIALIZED事件

1、 首先初始化jmx的环境变量
2、 定义解析server.xml的配置,告诉Digester哪个xml标签应该解析成什么类,如果我们要改变server.xml的某个属性值(比如优化tomcat线程池),直接查看对应实现类的setXXX方法即可
3、 解析conf/server.xml或者server-embed.xml,并且实例化对应的组件并且赋值操作,比如Server、Container、Connector等等
4、 为Server设置catalina信息,指定Catalina实例,设置catalina的home、base路径
5、 调用StarndServer#init()方法,完成各个组件的初始化,并且由parent组件初始化child组件,一层套一层,这个设计真心牛逼!

public void load() {

    initDirs();

    // 初始化jmx的环境变量
    initNaming();

    // Create and execute our Digester
    // 定义解析server.xml的配置,告诉Digester哪个xml标签应该解析成什么类
    Digester digester = createStartDigester();

    InputSource inputSource = null;
    InputStream inputStream = null;
    File file = null;
    try {

      // 首先尝试加载conf/server.xml,省略部分代码......
      // 如果不存在conf/server.xml,则加载server-embed.xml(该xml在catalina.jar中),省略部分代码......
      // 如果还是加载不到xml,则直接return,省略部分代码......

      try {
          inputSource.setByteStream(inputStream);

          // 把Catalina作为一个顶级实例
          digester.push(this);

          // 解析过程会实例化各个组件,比如Server、Container、Connector等
          digester.parse(inputSource);
      } catch (SAXParseException spe) {
          // 处理异常......
      }
    } finally {
        // 关闭IO流......
    }

    // 给Server设置catalina信息
    getServer().setCatalina(this);
    getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
    getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());

    // Stream redirection
    initStreams();

    // 调用Lifecycle的init阶段
    try {
        getServer().init();
    } catch (LifecycleException e) {
        // ......
    }

    // ......

load时序图如下所示(查看原图):

  • Digester利用jdk提供的sax解析功能,将server.xml的配置解析成对应的Bean,并完成注入,比如往Server中注入Service
  • EngineConfig,它是一个LifecycleListener实现,用于配置Engine,但是只会处理START_EVENT和STOP_EVENT事件
  • Connector默认会有两种:HTTP/1.1、AJP,不同的Connector内部持有不同的CoyoteAdapter和ProtocolHandler,在Connector初始化的时候,也会对ProtocolHandler进行初始化,完成端口的监听
  • ProtocolHandler常用的实现有Http11NioProtocol、AjpNioProtocol,还有apr系列的Http11AprProtocol、AjpAprProtocol,apr系列只有在使用apr包的时候才会使用到
  • 在ProtocolHandler调用init初始化的时候,还会去执行AbstractEndpoint的init方法,完成请求端口绑定、初始化NIO等操作,在tomcat7中使用JIoEndpoint阻塞IO,而tomcat8中直接移除了JIoEndpoint,具体信息请查看org.apache.tomcat.util.net这个包

Catalina在load结束之前,会调用Server的init()完成各个组件的初始化,下面我们来分析下各个组件在init初始化过程中都做了哪些操作

Server初始化

StandardServer是由Catalina进行init初始化的,调用的是LifecycleBase父类的init方法,而StandardServer继承至LifecycleMBeanBase,重写了initInternal方法。关于这块的知识,请参考上一篇Lifecycle的博客

StandardServer初始化的时序图如下所示,为了表述清楚,我这里把LifecycleBase、LifecycleMBeanBase拆开了,实际上是同一个StandardServer实例对象,存在继承关系
 

由上图可以很清晰地看到,StandardServer的初始化过程,先由父类LifecycleBase改变当前的state值并发出事件通知,那么这个时候StandardServer的子容器StandardService内部的state是否会发生改变呢,是否会发出事件通知呢? 当然是不会的,因为这个state值不是LifecycleBase的静态成员变量,StandardServer只能改变自己的值,而StandardService只有在被StandardServer调用init初始化的时候才会改变,二者拥有独立的状态。考虑到有其它线程可能会改变StandardServer的state值,比如利用jmx执行init操作,因此要考虑并发问题,所以LifecycleBase#init()使用了synchronized锁,并且state是volatile修饰的。

LifecycleBase改变state、发出事件通知之后,便会执行StandardServer自身的initInternal,我们来看看这个里面都干嘛了

protected void initInternal() throws LifecycleException {

    super.initInternal();

    // 往jmx中注册全局的String cache,尽管这个cache是全局听,但是如果在同一个jvm中存在多个Server,
    // 那么则会注册多个不同名字的StringCache,这种情况在内嵌的tomcat中可能会出现
    onameStringCache = register(new StringCache(), "type=StringCache");

    // 注册MBeanFactory,用来管理Server
    MBeanFactory factory = new MBeanFactory();
    factory.setContainer(this);
    onameMBeanFactory = register(factory, "type=MBeanFactory");

    // 往jmx中注册全局的NamingResources
    globalNamingResources.init();

    // Populate the extension validator with JARs from common and shared class loaders
    if (getCatalina() != null) {
        // 忽略ClassLoader操作
    }

    // 初始化内部的Service
    for (int i = 0; i < services.length; i++) {
        services[i].init();
    }

1、 先是调用super.initInternal(),把自己注册到jmx
2、 然后注册StringCache和MBeanFactory
3、 初始化NamingResources,就是server.xml中指定的GlobalNamingResources
4、 调用Service子容器的init方法,让Service组件完成初始化,注意:在同一个Server下面,可能存在多个Service组件

Service初始化

StandardService和StandardServer都是继承至LifecycleMBeanBase,因此公共的初始化逻辑都是一样的,这里不做过多介绍,我们直接看下initInternal

protected void initInternal() throws LifecycleException {

    // 往jmx中注册自己
    super.initInternal();

    // 初始化Engine
    if (engine != null) {
        engine.init();
    }

    // 存在Executor线程池,则进行初始化,默认是没有的
    for (Executor executor : findExecutors()) {
        if (executor instanceof JmxEnabled) {
            ((JmxEnabled) executor).setDomain(getDomain());
        }
        executor.init();
    }

    // 暂时不知道这个MapperListener的作用
    mapperListener.init();

    // 初始化Connector,而Connector又会对ProtocolHandler进行初始化,开启应用端口的监听
    synchronized (connectorsLock) {
        for (Connector connector : connectors) {
            try {
                connector.init();
            } catch (Exception e) {
                // 省略部分代码,logger and throw exception
            }
        }
    }

1、 首先,往jmx中注册StandardService
2、 初始化Engine,而Engine初始化过程中会去初始化Realm(权限相关的组件)
3、 如果存在Executor线程池,还会进行init操作,这个Excecutor是tomcat的接口,继承至java.util.concurrent.Executor、org.apache.catalina.Lifecycle
4、 初始化Connector连接器,默认有http1.1、ajp连接器,而这个Connector初始化过程,又会对ProtocolHandler进行初始化,开启应用端口的监听,后面会详细分析

Engine初始化

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值