Método init() de inicialización de 2 componentes de Catalina de Tomcat

Bienvenidos a todos a prestar atención a  github.com/hsfxuebao  , espero que les sea útil. Si creen que es posible, hagan clic en Estrella.

1. Diagrama de flujo de inicialización

imagen.png

2. Inicializar Catalina que carga componentes

Del análisis anterior, se puede ver que la lógica de carga en Bootstrap en realidad se entrega a Catalina para su procesamiento Analicemos el proceso de inicialización de Catalina. El código fuente de Catalina.load() es el siguiente:

// 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))));
    }
}
复制代码

Los pasos básicos son los siguientes:

  1. Primero inicialice las variables de entorno de jmx
  2. Defina la configuración de análisis de server.xml y dígale a Digester qué etiqueta xml debe analizarse en qué clase. Si queremos cambiar un determinado valor de atributo de server.xml (como optimizar el conjunto de subprocesos de Tomcat), podemos ver directamente el método setXXX de la clase de implementación correspondiente.
  3. Configure la información de catalina para el servidor, especifique la instancia de Catalina y configure las rutas base y de inicio de catalina
  4. Llame al método StarndServer#init() para completar la inicialización de cada componente e inicialice el componente secundario por el componente principal, capa por capa, este diseño es realmente impresionante

Nota: dos cosas deben entenderse aquí

  • Todos los lugares donde se llama al método init, como server.init o el siguiente service.init, en realidad están llamando al método init de StandardServer y StandardService, porque Server y Service son interfaces, y Standard~~ es la clase de implementación
  • El método init en Standard~~ en realidad llama al método initInternal de la clase principal

3. Inicializar servidor

Mire directamente a 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();
    }
}
复制代码

Los pasos básicos son los siguientes:

  1. Primero llame a super.initInternal() y regístrese con jmx
  2. Luego registre StringCache y MBeanFactory
  3. Inicialice NamingResources, que es el GlobalNamingResources especificado en server.xml
  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 análisis de código fuente Análisis de 
arquitectura de
Tomcat Proceso de inicio de Tomcat análisis de código fuente (medio) componente de inicialización init

Supongo que te gusta

Origin juejin.im/post/7083830518105505799
Recomendado
Clasificación