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的定义,但不必去初始化,用到的时候才去启动(描述的不贴切)。
目录
相关文章
|
1月前
|
监控 Java 应用服务中间件
Spring Boot整合Tomcat底层源码分析
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置和起步依赖等特性,大大简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是其与Tomcat的整合。
56 1
|
3月前
|
监控 网络协议 应用服务中间件
【Tomcat源码分析】从零开始理解 HTTP 请求处理 (第一篇)
本文详细解析了Tomcat架构中复杂的`Connector`组件。作为客户端与服务器间沟通的桥梁,`Connector`负责接收请求、封装为`Request`和`Response`对象,并传递给`Container`处理。文章通过四个关键问题逐步剖析了`Connector`的工作原理,并深入探讨了其构造方法、`init()`与`start()`方法。通过分析`ProtocolHandler`、`Endpoint`等核心组件,揭示了`Connector`初始化及启动的全过程。本文适合希望深入了解Tomcat内部机制的读者。欢迎关注并点赞,持续更新中。如有问题,可搜索【码上遇见你】交流。
【Tomcat源码分析】从零开始理解 HTTP 请求处理 (第一篇)
|
3月前
|
人工智能 前端开发 Java
【Tomcat源码分析】启动过程深度解析 (二)
本文深入探讨了Tomcat启动Web应用的过程,重点解析了其加载ServletContextListener及Servlet的机制。文章从Bootstrap反射调用Catalina的start方法开始,逐步介绍了StandardServer、StandardService、StandardEngine、StandardHost、StandardContext和StandardWrapper的启动流程。每个组件通过Lifecycle接口协调启动,子容器逐层启动,直至整个服务器完全启动。此外,还详细分析了Pipeline及其Valve组件的作用,展示了Tomcat内部组件间的协作机制。
【Tomcat源码分析】启动过程深度解析 (二)
|
3月前
|
前端开发 Java 应用服务中间件
【Tomcat源码分析 】"深入探索:Tomcat 类加载机制揭秘"
本文详细介绍了Java类加载机制及其在Tomcat中的应用。首先回顾了Java默认的类加载器,包括启动类加载器、扩展类加载器和应用程序类加载器,并解释了双亲委派模型的工作原理及其重要性。接着,文章分析了Tomcat为何不能使用默认类加载机制,因为它需要解决多个应用程序共存时的类库版本冲突、资源共享、类库隔离及JSP文件热更新等问题。最后,详细展示了Tomcat独特的类加载器设计,包括Common、Catalina、Shared、WebApp和Jsp类加载器,确保了系统的稳定性和安全性。通过这种设计,Tomcat实现了不同应用程序间的类库隔离与共享,同时支持JSP文件的热插拔。
【Tomcat源码分析 】"深入探索:Tomcat 类加载机制揭秘"
|
3月前
|
设计模式 应用服务中间件 容器
【Tomcat源码分析】Pipeline 与 Valve 的秘密花园
本文深入剖析了Tomcat中的Pipeline和Valve组件。Valve作为请求处理链中的核心组件,通过接口定义了关键方法;ValveBase为其基类,提供了通用实现。Pipeline则作为Valve容器,通过首尾相连的Valve链完成业务处理。StandardPipeline实现了Pipeline接口,提供了详细的Valve管理逻辑。通过对代码的详细分析,揭示了模板方法模式和责任链模式的应用,展示了系统的扩展性和模块化设计。
【Tomcat源码分析】Pipeline 与 Valve 的秘密花园
|
3月前
|
设计模式 人工智能 安全
【Tomcat源码分析】生命周期机制 Lifecycle
Tomcat内部通过各种组件协同工作,构建了一个复杂的Web服务器架构。其中,`Lifecycle`机制作为核心,管理组件从创建到销毁的整个生命周期。本文详细解析了Lifecycle的工作原理及其方法,如初始化、启动、停止和销毁等关键步骤,并展示了LifecycleBase类如何通过状态机和模板模式实现这一过程。通过深入理解Lifecycle,我们可以更好地掌握组件生命周期管理,提升系统设计能力。欢迎关注【码上遇见你】获取更多信息,或搜索【AI贝塔】体验免费的Chat GPT。希望本章内容对你有所帮助。
|
4月前
|
网络协议 Java 应用服务中间件
Tomcat源码分析 (一)----- 手撕Java Web服务器需要准备哪些工作
本文探讨了后端开发中Web服务器的重要性,特别是Tomcat框架的地位与作用。通过解析Tomcat的内部机制,文章引导读者理解其复杂性,并提出了一种实践方式——手工构建简易Web服务器,以此加深对Web服务器运作原理的认识。文章还详细介绍了HTTP协议的工作流程,包括请求与响应的具体格式,并通过Socket编程在Java中的应用实例,展示了客户端与服务器间的数据交换过程。最后,通过一个简单的Java Web服务器实现案例,说明了如何处理HTTP请求及响应,强调虽然构建基本的Web服务器相对直接,但诸如Tomcat这样的成熟框架提供了更为丰富和必要的功能。
|
7月前
|
前端开发 Java 应用服务中间件
|
7月前
|
XML Java 应用服务中间件
SpringBoot配置外部Tomcat项目启动流程源码分析(长文)
SpringBoot配置外部Tomcat项目启动流程源码分析(长文)
577 0