Tomcat - Tomcat 8.5.55 启动过程源码分析阶段二_load加载初始化

文章目录

Tomcat - Tomcat 8.5.55 启动过程源码分析阶段二_load加载初始化


启动流程分析

Tomcat - Tomcat 8.5.55 启动过程源码分析阶段二_load加载初始化

Tomcat - Tomcat 8.5.55 启动过程源码分析阶段二_load加载初始化


Pre

Tomcat - Tomcat 8.5.55 启动过程源码分析阶段一_init实例化Bootstrap

我们分析了 init 的主要功能,实例化Bootstrap , 调用init 通过反射调用Catalina#setParentClassLoader ,后面调用的load 和 start方法 均为 反射调用的Catalina对象的load和start 方法。


load 加载初始化

总体预览

Tomcat - Tomcat 8.5.55 启动过程源码分析阶段二_load加载初始化

源码解析

Tomcat - Tomcat 8.5.55 启动过程源码分析阶段二_load加载初始化

load()

我们梳理关键脉络


  // Digester对象  用于解析 server.xml
  Digester digester = createStartDigester();



  // tomcat的配置文件  server.xml
   file = configFile();

 // 解析 xml  重点关注返回的对象  root
   digester.parse(inputSource);
   


进到这个parse方法

 /**
     * Parse the content of the specified input source using this Digester.
     * Returns the root element from the object stack (if any).
     *
     * @param input Input source containing the XML data to be parsed
     * @return the root object
     * @exception IOException if an input/output error occurs
     * @exception SAXException if a parsing exception occurs
     */
    public Object parse(InputSource input) throws IOException, SAXException {
        configure();
        getXMLReader().parse(input);
        return root;
    }

Tomcat - Tomcat 8.5.55 启动过程源码分析阶段二_load加载初始化

那我们看下我们source目录下的精简后的server.xml

Tomcat - Tomcat 8.5.55 启动过程源码分析阶段二_load加载初始化

结合tomcat总体的架构图,套娃结构 , server-service-----connector/container-----engine-----host-----context-----wrapper

Tomcat - Tomcat 8.5.55 启动过程源码分析阶段二_load加载初始化

刚才debug出来的root对象是不是很好理解了呢?

我们看下LifeCycle接口的继承关系, tomcat类的命名其实是很规范的 StandardXXXX

Tomcat - Tomcat 8.5.55 启动过程源码分析阶段二_load加载初始化

那继续看下 root对象的 及 配置文件的关系

Tomcat - Tomcat 8.5.55 启动过程源码分析阶段二_load加载初始化

Server初始化

继续往下跟


//  Servier初始化
  getServer().init();

调用的是 LifeCycle的init接口 , 跟进去可以看到是进入到了 LifeCycleBase这个抽象类的init方法

在init方法中 ,调用

 // 初始化的关键方法 (抽象方法 交由子类实现  设计模式中的模板模式)
   initInternal();

可以看到这个方法是抽象方法 , 具体的实现由继承了LifeCycleBase这个抽象的子类实现具体的业务逻辑。

这里使用了模板模式 . 我们看下LifeCycleBase抽行类的具体实现

Tomcat - Tomcat 8.5.55 启动过程源码分析阶段二_load加载初始化

很规范的实现 ,每个组件实例化 都要从LifeCycleBase中走一遍 ,自行实现 init方法。

既然这里是Server, 那到StandardServer # initInternal 中看下

最重要的代码 实例化Service

 for (Service service : services) {
            service.init();
        }

这里可以看出来,Service 支持配置多个,不过通常情况下,不建议这么做。 想配置多个,干嘛不多起个tomcat呢?


Service初始化

同样的模板模式 , 还是会调用到 StandardService # initInternal ,精简后的核心代码如下

 @Override
    protected void initInternal() throws LifecycleException {

        super.initInternal();

        if (engine != null) {
            engine.init();
        } 
        // Initialize our defined Connectors
        synchronized (connectorsLock) {
            for (Connector connector : connectors) { 
                    connector.init();    
            }
        }
    }

可以看到 StandardService # initInternal 中 主要干了两件事儿 engine.init 和 connector.init


Engine初始化

同样的老一套,模板模式, 跟进到 StandardEngine # initInternal

    @Override
    protected void initInternal() throws LifecycleException { 
        getRealm();
        super.initInternal();
    }

可以看到调用的是父类的 initInternal , 父类 ContainerBase

   @Override
    protected void initInternal() throws LifecycleException {
        BlockingQueue<Runnable> startStopQueue = new LinkedBlockingQueue<>();
        startStopExecutor = new ThreadPoolExecutor(
                getStartStopThreadsInternal(),
                getStartStopThreadsInternal(), 10, TimeUnit.SECONDS,
                startStopQueue,
                new StartStopThreadFactory(getName() + "-startStop-"));
        startStopExecutor.allowCoreThreadTimeOut(true);
        super.initInternal();
    }

干了件啥事儿? 无非就是实例化了一个连接池startStopExecutor , 这个线程池的主要作用是在后面的start方法中使用。Engine可以配置多个Host,这个连接池的作用主要是为了并行初始化Host


Connector 初始化

继续 StandardService # initInternal , 接着 Connector 实例化, for循环嘛 ,一看就是支持配置多个Connector

   for (Connector connector : connectors) { 
                    connector.init();    
            }

Connector# initInternal

核心代码

 @Override
    protected void initInternal() throws LifecycleException {

        super.initInternal();

        // Initialize adapter 并绑定 protocolHandler
        adapter = new CoyoteAdapter(this);
        protocolHandler.setAdapter(adapter);
 

         // protocolHandler初始化,主要是 内部EndPoint的初始化 
         protocolHandler.init();
     
    }

CoyoteAdapter 对应tomcat架构图,是不是就是 Http 和 Servlet 之间用来做转换的那个Adapter ?

protocolHandler.setAdapter(adapter) 绑定

Tomcat - Tomcat 8.5.55 启动过程源码分析阶段二_load加载初始化

继续看

  // protocolHandler初始化,主要是 内部EndPoint的初始化 
   protocolHandler.init();

进入 AbstractHttp11Protocol # init

  @Override
    public void init() throws Exception { 
        for (UpgradeProtocol upgradeProtocol : upgradeProtocols) {
            configureUpgradeProtocol(upgradeProtocol);
        }

        super.init();
    }

关注 super.init(); 调用抽象父类AbstractProtocol ,精简后的代码如下

 @Override
    public void init() throws Exception {
        
	
        String endpointName = getName();
        endpoint.setName(endpointName.substring(1, endpointName.length()-1));
        endpoint.setDomain(domain);
	    // 通信端点的初始化
        endpoint.init();
    }

核心: endpoint.init();

调用 AbstractEndpoint # init

public void init() throws Exception {
        if (bindOnInit) {
            bind();
            bindState = BindState.BOUND_ON_INIT;
        }
     
    }

重点是bind 方法

NioEndpoint ,我们这里的版本是tomcat8+, 默认的是NIO

精简后的核心代码如下:

 @Override
    public void bind() throws Exception {

        if (!getUseInheritedChannel()) {
        
            // 获取NIO 通道
            serverSock = ServerSocketChannel.open();
            socketProperties.setProperties(serverSock.socket());
            InetSocketAddress addr = (getAddress()!=null?new InetSocketAddress(getAddress(),getPort()):new InetSocketAddress(getPort()));
            // 绑定端口,但尚未使用accept获取客户端连接
            serverSock.socket().bind(addr,getAcceptCount());
            
        } 
    }

至此,load方法完毕


小结

通过一层层的分析,tomcat套娃式的设计,使用模板模式,一层层的初始化 ,是不是有了更深刻的理解了呢?

load完了,接下来我们来看下start阶段都干了啥事儿,继续下一篇 ~

上一篇:APM - Javassist 入门 生成一个简单类


下一篇:Java-IO流