TOMCAT 源码分析 -- 启动(下)

简介: TOMCAT 源码分析 -- 启动
  1. 初始化协议处理器
// AbstractProtocol.java (Http11NioProtocol.java)
  @Override
       public void init() throws Exception {
        // 初始化终点(端点)
           String endpointName = getName();
           endpoint.setName(endpointName.substring(1, endpointName.length()-1));
           endpoint.setDomain(domain);
           endpoint.init();
       }   
  // 终(端)点初始化 --这一步,连接器就要让终点去绑定端口了
  // AbstractEndpoint.java  
  public final void init() throws Exception {
        if (bindOnInit) {
            bindWithCleanup();
            bindState = BindState.BOUND_ON_INIT;
        }
    }
  // NioEndpoint.java
  protected void initServerSocket() throws Exception {
        if (!getUseInheritedChannel()) {
         serverSock = ServerSocketChannel.open();
            socketProperties.setProperties(serverSock.socket());
            InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
            // 最终这一步去绑定了8080端口
            serverSock.socket().bind(addr,getAcceptCount());
        } else {
        }
        serverSock.configureBlocking(true); //mimic APR behavior
    }

5. 回归Bootstrap

接着单步执行会发现它一层一层往外走,最终回到了Bootstrap中,进入下一步启动-start()。

启动-start

启动过程通过反射调用了Catalina的start方法。

// Bootstrap.java
    public void start() throws Exception {
        Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
        method.invoke(catalinaDaemon, (Object [])null);
    }
  // Catalina.java
    public void start() {
    // 获取不到Server则调用之前的加载,到这里他已经加载过了,不会进这个分支
        if (getServer() == null) {
            load();
        }
        // 正式调用Server的启动生命周期
        try {
            getServer().start();
        } catch (LifecycleException e) {
        }
    }

启动步骤按顺序分以下几个:

1. Server的启动

可以发现它也是交由超类的final start进行实现,可想而知,所有容器的启动,也都会进这个start方法。

并且与load()方法相似startInternal都交由子类具体实现其内部逻辑

// LifecycleBase.java
  @Override
    public final synchronized void start() throws LifecycleException {
    // ...
        try {
            setStateInternal(LifecycleState.STARTING_PREP, null, false);
            // startInternal交由子类具体实现内部逻辑
            startInternal();
        } catch (Throwable t) {
        }
    }

具体进入startInternal看实现,可以看到实现在StandardServer中

// org.apache.catalina.core.StandardServer#startInternal
    @Override
    protected void startInternal() throws LifecycleException {
        // 触发生命周期的事件 -- START
        fireLifecycleEvent(CONFIGURE_START_EVENT, null);
        setState(LifecycleState.STARTING);
        globalNamingResources.start();
        // 对定义的Service进行start
        synchronized (servicesLock) {
            for (Service service : services) {
                service.start();
            }
        }
    }

2. Service启动

同理,service.start();进入了超类的start()方法,且最终调用自身StandardService的startInternal方法进行实现(下同之处自动省略)。

// org.apache.catalina.core.StandardService#startInternal
    @Override
    protected void startInternal() throws LifecycleException {
        // 第一步启动引擎
        if (engine != null) {
            synchronized (engine) {
                engine.start();
            }
        }
    // 第二步启动执行器
        synchronized (executors) {
            for (Executor executor: executors) {
                executor.start();
            }
        }
    // 第三步启动监听器
        mapperListener.start();
        // 第四步启动已经成功加载的连接器
        // 那么为什么会有失败的呢,最直接的就是端口被占用,无法绑定bind()
        synchronized (connectorsLock) {
            for (Connector connector: connectors) {
                // If it has already failed, don't try and start it
                if (connector.getState() != LifecycleState.FAILED) {
                    connector.start();
                }
            }
        }
    }

3. 启动引擎、执行器、监听器、连接器

  1. 启动引擎
// org.apache.catalina.core.StandardEngine#startInternal
    @Override
    protected synchronized void startInternal() throws LifecycleException {
        // Standard container startup
        // 调用了超类[ContainerBase]中的startInternal
        super.startInternal();
    }
  // org.apache.catalina.core.ContainerBase#startInternal
    @Override
    protected synchronized void startInternal() throws LifecycleException {
    // debug 发现进来为null
        Cluster cluster = getClusterInternal();
        if (cluster instanceof Lifecycle) {
            logger.info(String.format("cluster[%s] 开始执行生命周期之start()", cluster.getClusterName()));
            ((Lifecycle) cluster).start();
        }
        // 会获取到Load方法加载进来的`LockOutRealm`
        Realm realm = getRealmInternal();
        if (realm instanceof Lifecycle) {
            logger.info(String.format("realm-属于container[%s] 开始执行生命周期之start()", realm.getContainer().getName()));
            // 最终执行的为org.apache.catalina.realm.CombinedRealm#startInternal
            ((Lifecycle) realm).start();
        }
        // Start our child containers, if any
        // 最终找到了Engine下定义的Host [StandardHost]
        Container children[] = findChildren();
        List<Future<Void>> results = new ArrayList<>();
        for (Container child : children) {
            // 使用了load()时初始化的线程池
            logger.info("开始使用线程池提交多线程去调用子Container[%s]的call方法初始化");
            results.add(startStopExecutor.submit(new StartChild(child)));
        }
        MultiThrowable multiThrowable = null;
    // 使用Future#get进行阻塞,获取Host的初始化结果
        for (Future<Void> result : results) {
            try {
                result.get();
            } catch (Throwable e) {
                log.error(sm.getString("containerBase.threadedStartFailed"), e);
                if (multiThrowable == null) {
                    multiThrowable = new MultiThrowable();
                }
                multiThrowable.add(e);
            }
        }
        if (multiThrowable != null) {
            throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"),
                    multiThrowable.getThrowable());
        }
        // Start the Valves in our pipeline (including the basic), if any
        if (pipeline instanceof Lifecycle) {
            logger.debug(String.format("开始管道pipeline[%s]的生命周期之start()", pipeline.getContainer().getName()));
            ((Lifecycle) pipeline).start();
        }
        // 声明周期改成启动中
        setState(LifecycleState.STARTING);
        // Start our thread
        if (backgroundProcessorDelay > 0) {
            monitorFuture = Container.getService(ContainerBase.this).getServer()
                    .getUtilityExecutor().scheduleWithFixedDelay(
                            new ContainerBackgroundProcessorMonitor(), 0, 60, TimeUnit.SECONDS);
        }
    }
  1. 启动Cluster
    获得对象为null,无需启动
  2. 启动Realm
    有对象,跳过
  3. 启动子容器Host
    根据上述代码可知,它会被提交到线程池,进行多线程启动,将会调用到线程的call方法
// org.apache.catalina.core.ContainerBase.StartChild
  // 超类中的静态内部类
    private static class StartChild implements Callable<Void> {
        @Override
        public Void call() throws LifecycleException {
            // 调用子容器的start方法,在这步中,子容器为`StandardHost`
            child.start();
            //DEBUG: child: "StandardEngine[Catalina].StandardHost[localhost]"
            return null;
        }
    }
  1. 接着通过StandardHost的start生命周期又进入startInternal方法
// org.apache.catalina.core.StandardHost#startInternal
    @Override
    protected synchronized void startInternal() throws LifecycleException {
        // 检查管道中有没有报错
        // 真正的启动交由超类实现
        super.startInternal();
    }
  // 接着又交由到ContainerBase这个熟悉的超类进行实现,在启动-start章节的3.1中已经贴出过,这里不再详细展示,主要就是多线程去启动器子容器 -- 套娃模式的好处
  // org.apache.catalina.core.ContainerBase#startInternal
    @Override
    protected synchronized void startInternal() throws LifecycleException {
        // Start our child containers, if any
        Container children[] = findChildren();
        // 由于在server.xml中没有在<Host>节点下再定义<Context>容器,所以这次children数组是空数组
        List<Future<Void>> results = new ArrayList<>();
        for (Container child : children) {
            logger.info("开始使用线程池提交多线程去调用子Container[%s]的call方法初始化");
            results.add(startStopExecutor.submit(new StartChild(child)));
        }
    }
  1. 启动管道pipeline
    跳过
  1. 启动执行器
    在执行完多线程对Host的启动后,一路点击Step Out跳回到StandardService中的startInternal方法。
    若已经分不清层次可以在StandardService类中搜索下面这段代码并打上断点。
//  org.apache.catalina.core.StandardService#startInternal
        synchronized (executors) {
            for (Executor executor: executors) {
                executor.start();
            }
        }
  1. 不过executors数组长度为0,在这儿就跳过了。
  2. 启动监听器
// org.apache.catalina.mapper.MapperListener#startInternal
   @Override
    public void startInternal() throws LifecycleException {
    // 获取引擎
        Engine engine = service.getContainer();
    // 添加监听器进引擎
        addListeners(engine);
        Container[] conHosts = engine.findChildren();
        for (Container conHost : conHosts) {
            Host host = (Host) conHost;
            if (!LifecycleState.NEW.equals(host.getState())) {
                // Registering the host will register the context and wrappers
                registerHost(host);
            }
        }
    }
  // org.apache.catalina.mapper.MapperListener#addListeners
    private void addListeners(Container container) {
        // 把监听器[回调接口]注册进容器、生命周期、并递归对子容器进行注册
        container.addContainerListener(this);
        container.addLifecycleListener(this);
        for (Container child : container.findChildren()) {
            // 通过一共5层递归,观察到层次如下
            // StandardEngine[Catalina].StandardHost[localhost].StandardContext[].StandardWrapper[default]
            addListeners(child);
        }
    }
  1. 可以观察到套娃一样对子容器进行递归添加监听器层次为:StandardEngine[Catalina].StandardHost[localhost].StandardContext[].StandardWrapper[default]。
  2. 启动连接器
    可以在org.apache.catalina.core.StandardService#startInternal中搜索for (Connector connector: connectors)打上断点,快速从上一步的递归中出来,再进入connector.start();。
    ps: 这时候在windows平台上遇到了端口被占用,如何杀死占用了8080端口的进程呢?
<!-- 先找出占用8080端口的进程 -->
netstat -ano| findstr "8080"
<!-- 杀死占用8080端口的进程[此处刚好为7400] -->
taskkill /f /pid 7400
// org.apache.catalina.connector.Connector#startInternal
    @Override
    protected void startInternal() throws LifecycleException {
        try {
            // 启动协议处理器
            protocolHandler.start();
        } catch (Exception e) {
        }
    }
// org.apache.coyote.AbstractProtocol#start
    @Override
    public void start() throws Exception {
    // 启动端点
        endpoint.start();
    }
// org.apache.tomcat.util.net.AbstractEndpoint#start
    public final void start() throws Exception {
        startInternal();
    }
// org.apache.tomcat.util.net.NioEndpoint#startInternal
   @Override
    public void startInternal() throws Exception {
        if (!running) {
            // 读取配置
            // Create worker collection
            if (getExecutor() == null) {
                // 初始化线程池,任务队列
                createExecutor();
            }
       // 创建限制锁
            initializeConnectionLatch();
            // 创建NIO的poller线程
            poller = new Poller();
            Thread pollerThread = new Thread(poller, getName() + "-ClientPoller");
            pollerThread.setPriority(threadPriority);
            pollerThread.setDaemon(true);
            pollerThread.start();
       // 真正开始接收NIO端口的请求
            startAcceptorThread();
        }
    }
// org.apache.tomcat.util.net.AbstractEndpoint#startAcceptorThread
    protected void startAcceptorThread() {
        acceptor = new Acceptor<>(this);
        String threadName = getName() + "-Acceptor"; // http-nio-8080-Acceptor
        acceptor.setThreadName(threadName);
        Thread t = new Thread(acceptor, threadName);
        t.setPriority(getAcceptorThreadPriority());
        t.setDaemon(getDaemon());
        t.start(); // Thread[http-nio-8080-Acceptor,5,main]
    }
  1. 回归BootStrap
    一直Step Out回到BootStrap中,完成启动。

总结

  • 加载(load)过程主要完成 配置读取-实例化容器等组件、创建线程池、占用监控端口。
  • 启动(start)过程主要完成 顺序/多线程启动各层容器、开始接收端口的请求数据。

收获

  • 组件模块化,在超类中定义基本操作,在子类中定义具体的一部分的实现。架构脉络清晰,层次分明,节省大量代码量。
  • 加载与启动整体解耦分离,就不必在启动过程中在意所需组件是否加载。有点像Spring的懒加载,读取了Bean的定义,但不必去初始化,用到的时候才去启动(描述的不贴切)。
目录
相关文章
|
2月前
|
XML Java 应用服务中间件
SpringBoot配置外部Tomcat项目启动流程源码分析(长文)
SpringBoot配置外部Tomcat项目启动流程源码分析(长文)
120 0
|
12月前
|
存储 缓存 前端开发
07.Tomcat源码分析——类加载体系
由于在生产环境中,Tomcat一般部署在Linux系统下,所以本文将以 startup.sh shell脚本为准,对Tomcat的启动进行分析。
36 0
07.Tomcat源码分析——类加载体系
|
前端开发 Java 应用服务中间件
TOMCAT 源码分析 -- 一次请求
TOMCAT 源码分析 -- 一次请求
73 0
|
Java 应用服务中间件
TOMCAT 源码分析 -- 构建环境
TOMCAT 源码分析 -- 构建环境
81 0
|
XML 前端开发 Java
TOMCAT 源码分析 -- 启动(上)
TOMCAT 源码分析 -- 启动
113 0
|
Java 应用服务中间件 容器
Tomcat源码分析之中文乱码(一)
Tomcat源码分析之中文乱码(一)
147 0
|
算法 安全 应用服务中间件
Tomcat源码分析之 doGet方法(四)
Tomcat源码分析之 doGet方法(四)
38 0
|
设计模式 应用服务中间件 容器
Tomcat源码分析之 doGet方法(三)
Tomcat源码分析之 doGet方法(三)
46 0
|
应用服务中间件 索引
Tomcat源码分析之 doGet方法(二)
Tomcat源码分析之 doGet方法(二)
61 0
|
算法 应用服务中间件
Tomcat源码分析之 doGet方法(一)
Tomcat源码分析之 doGet方法(一)
73 0