Tomcat的Catalina篇2-组件初始化init()方法

欢迎大家关注 github.com/hsfxuebao ,希望对大家有所帮助,要是觉得可以的话麻烦给点一下Star哈

1. 初始化流程图

image.png

2.初始化加载组件的Catalina

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

// catalina的加载信息
public void load() {

    if (loaded) {
        return;
    }
    loaded = true;

    long t1 = System.nanoTime();

    initDirs();

    // Before digester - it may be needed
    // 初始化jmx的环境变量
    initNaming();

    // Parse main server.xml
    // 解析服务器的server.xml文件 使用Digester 技术进行xml文档对象解析
    parseServerXml(true);
    Server s = getServer();
    if (s == null) {
        return;
    }

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

    // Stream redirection
    initStreams();

    // Start the new server
    try {
        // 服务器执行初始化 开始调用的Server的初始化方法注意Server是一个接口
        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);
        }
    }

    if(log.isInfoEnabled()) {
        log.info(sm.getString("catalina.init", Long.toString(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - t1))));
    }
}
复制代码

核心步骤如下:

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

注:这里需要明白两点

  • 所有调用 init 方法的地方,比如 server.init 或者后面的 service.init 实际都是调用StandardServer、StandardService的 init 方法,因为 Server 和 Service 都是接口,Standard~~是实现类
  • Standard~~中的 init 方法实际调用的是父类的 initInternal 方法

3. 初始化Server

直接看StandardServer.initInternal():

protected void initInternal() throws LifecycleException {

    super.initInternal();

    // 以下就是第一个大组件Server的初始化过程
    // Initialize utility executor
    reconfigureUtilityExecutor(getUtilityThreadsInternal(utilityThreads));
    register(utilityExecutor, "type=UtilityExecutor");

    // Register global String cache
    // Note although the cache is global, if there are multiple Servers
    // present in the JVM (may happen when embedding) then the same cache
    // will be registered under multiple names
    /** 1.往jmx中注册全局的String cache,尽管这个cache是全局,但是如果在同一个jvm中存在多个Server,
     * 那么则会注册多个不同名字的StringCache,这种情况在内嵌的tomcat中可能会出现
     */
    onameStringCache = register(new StringCache(), "type=StringCache");

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

    // Register the naming resources
    // 3.JNDI初始化, 往jmx中注册全局的NamingResources
    globalNamingResources.init();

    ...
    // Initialize our defined Services
    // 4. 所有service初始化
    for (Service service : services) {
        service.init();
    }
}
复制代码

核心步骤如下:

  1. 先是调用super.initInternal(),把自己注册到jmx
  2. 然后注册StringCache和MBeanFactory
  3. 初始化NamingResources,就是server.xml中指定的GlobalNamingResources
  4. 调用Service子容器的init方法,让Service组件完成初始化,注意:在同一个Server下面,可能存在多个 Service组件
  • JMX:Java Management Extensions,是通过MBean来监控,管理和远程连接Tomcat的一个框架

4.初始化Service

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

protected void initInternal() throws LifecycleException {

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

    // 2.若引擎不为空,先让引擎初始化
    if (engine != null) {
        engine.init();
    }

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

    // Initialize mapper listener
    // 4. mapperListener初始化,后面请求映射使用
    mapperListener.init();

    // Initialize our defined Connectors
    // 5. 所有connector初始化,而Connector又会对ProtocolHandler进行初始化,开启应用端口的监听
    synchronized (connectorsLock) {
        for (Connector connector : connectors) {
            connector.init();
        }
    }
}
复制代码

核心步骤如下:

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

5.初始化Engine

直接看StandardEngine.initInternal():

protected void initInternal() throws LifecycleException {
    // Ensure that a Realm is present before any attempt is made to start
    // one. This will create the default NullRealm if necessary.
    getRealm();
    super.initInternal();
}
复制代码

StandardEngine在init阶段,需要获取Realm,这个Realm是干嘛用的?

  • Realm(域)是用于对单个用户进行身份验证的底层安全领域的只读外观,并标识与这些用户相关联的安全角色。
  • 域可以在任何容器级别上附加,但是通常只附加到Context,或者更高级别的容器。
public Realm getRealm() {
    Realm configured = super.getRealm();
    if (configured == null) {
        configured = new NullRealm();
        this.setRealm(configured);
    }
    return configured;
}
复制代码

StandardEngine继承至ContainerBase,而ContainerBase重写了initInternal()方法;用于初始化start、stop线程池

  • 在start的时候,如果发现有子容器,则会把子容器的start操作放在线程池中进行处理
  • 在stop的时候,也会把stop操作放在线程池中处理
// 默认是一个线程
private int startStopThreads = 1;
protected ThreadPoolExecutor startStopExecutor;
复制代码

这个startStopExecutor 初始化是在StandardServer中进行的,源码如下:

private synchronized void reconfigureUtilityExecutor(int threads) {
    // The ScheduledThreadPoolExecutor doesn't use MaximumPoolSize, only CorePoolSize is available
    if (utilityExecutor != null) {
        utilityExecutor.setCorePoolSize(threads);
    } else {
        ScheduledThreadPoolExecutor scheduledThreadPoolExecutor =
                new ScheduledThreadPoolExecutor(threads,
                        new TaskThreadFactory("Catalina-utility-", utilityThreadsAsDaemon, Thread.MIN_PRIORITY));
        scheduledThreadPoolExecutor.setKeepAliveTime(10, TimeUnit.SECONDS);
        scheduledThreadPoolExecutor.setRemoveOnCancelPolicy(true);
        scheduledThreadPoolExecutor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
        utilityExecutor = scheduledThreadPoolExecutor;
        utilityExecutorWrapper = new org.apache.tomcat.util.threads.ScheduledThreadPoolExecutor(utilityExecutor);
    }
}

复制代码

在前面的文章中我们介绍了Container组件,StandardEngine作为顶层容器,它的直接子容器是StardandHost,但是对StandardEngine的代码分析,我们并没有发现它会对子容器StardandHost进行初始化操作,StandardEngine不按照套路出牌,将子容器的初始化过程放在start阶段。可能Host、Context、Wrapper这些容器和具体的webapp应用相关联了,初始化过程会更加耗时,因此在start阶段用多线程完成初始化以及start生命周期,否则,像顶层的Server、Service等组件需要等待Host、Context、Wrapper完成初始化才能结束初始化流程,整个初始化过程是具有传递性的。

6.初始化Connector

Connector也是继承至LifecycleMBeanBase,公共的初始化逻辑都是一样的。

Connector容器的主要作用:初始化ProtocolHandler,选择合适的协议

  • 我们先来看下Connector的默认配置,大部分属性配置都可以在Connector类中找到,tomcat默认开启了HTTP/1.1、AJP/1.3,其实AJP的用处不大,可以去掉。
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000"  redirectPort="8443" />
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
复制代码
  • Connector定义了很多属性,比如port、redirectPort、maxCookieCount、maxPostSize等等,比较有意思的是竟然找不到connectionTimeout的定义,全文搜索后发现使用了属性名映射,估计是为了兼容以前的版本

我们来看initInternal()方法如下:

protected void initInternal() throws LifecycleException {

    // 1.注册jmx
    super.initInternal();

    if (protocolHandler == null) {
        throw new LifecycleException(
                sm.getString("coyoteConnector.protocolHandlerInstantiationFailed"));
    }

    // Initialize adapter
    // 2. 初始化适配器,这个适配器是用于Coyote的Request、Response与HttpServlet的Request、Response适配的
    adapter = new CoyoteAdapter(this);
    // 3.protocolHandler需要指定Adapter用于处理请求
    protocolHandler.setAdapter(adapter);
    ...

    try {
        // 4.初始化ProtocolHandler,这个init不是Lifecycle定义的init,而是ProtocolHandler接口的init协议处理器初始化
        protocolHandler.init();
    } catch (Exception e) {
        throw new LifecycleException(
                sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);
    }
}
复制代码

核心步骤如下:

  1. 注册jmx
  2. 实例化Coyote适配器,这个适配器是用于Coyote的Request、Response与HttpServlet的Request、Response适配的,后续的博客会进行深入分析
  3. 为ProtocolHander指定CoyoteAdapter用于处理请求
  4. 初始化ProtocolHander

7.初始化protocolHandler

首先,我们来认识下ProtocolHandler,它是一个抽象的协议实现,它不同于JNI这样的Jk协议,它是单线程、基于流的协议。ProtocolHandler是一个Cycote连接器实现的主要接口,而Adapter适配器是由一个CoyoteServlet容器实现的主要接口,定义了处理请求的抽象接口。

ProtocolHandler的子类如下所示,AbstractProtocol(org.apache.coyote)是基本的实现,而NIO默认使用的是Http11NioProtocol。源码如下:

public void init() throws Exception {
    if (getLog().isInfoEnabled()) {
        getLog().info(sm.getString("abstractProtocolHandler.init", getName()));
        logPortOffset();
    }

    // 1.完成jmx注册
    if (oname == null) {
        // Component not pre-registered so register it
        oname = createObjectName();
        if (oname != null) {
            Registry.getRegistry(null, null).registerComponent(this, oname, null);
        }
    }

    ... 
    // 2.初始化endpoint
    endpoint.init();
}
复制代码

8.初始化EndPoint

NioEndpoint初始化过程,最重要的是完成端口和地址的绑定监听工作,即进行最基本的Socket操作,封装Request,Response;即实现TCP/IP协议。

链路如下:AbstractEndpoint.init()->bindWithCleanup() -> NioEndpoint.bind() -> initServerSocket(),我们详细看下:

protected void initServerSocket() throws Exception {
    ...
    } else {
        // 实例化ServerSocketChannel,并且绑定端口和地址
        serverSock = ServerSocketChannel.open();
        socketProperties.setProperties(serverSock.socket());
        InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
        // 绑定端口
        serverSock.bind(addr, getAcceptCount());
    }
    serverSock.configureBlocking(true); //mimic APR behavior
}
复制代码

至此,初始化流程结束!

默认情况下,Server只有一个Service组件,Service组件先后对Engine、Connector进行初始化。而Engine组件并不会在初始化阶段对子容器进行初始化,Host、Context、Wrapper容器的初始化是在start阶段完成的。tomcat默认会启用HTTP1.1和AJP的Connector连接器,这两种协议默认使用Http11NioProtocol、AJPNioProtocol进行处理。

参考文章

tomcat-9.0.60-src源码解析 
Tomcat架构解析
Tomcat启动流程源码分析(中)init初始化组件

猜你喜欢

转载自juejin.im/post/7083830518105505799
今日推荐