Tomcat源码解析——启动过程分析

本文详细分析了Tomcat的启动流程,从Bootstrap入口开始,经过Catalina的初始化和组件启动,再到项目加载和Context初始化,揭示了Tomcat的内部工作机制。了解Tomcat的启动过程有助于开发者深入理解其架构和功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近面试一些1~3年的开发人员,问他们tomcat的基础架构发现都是一脸懵逼,这里开贴大概写一下tomcat的整体架构以及启动流程,版本基于7.0.85,后续会不断对文章进行更新。

一、tomcat基础架构

上个图,这张图算是说的比较明白的。

大家可以去翻一下server.xml,精简后会发现如下结构:

<?xml version='1.0' encoding='utf-8'?>  
    <Server port="8005" shutdown="SHUTDOWN">  
        <Service name="Catalina">  
            <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8"/>  
            <Engine name="Catalina" defaultHost="localhost">  
                <Host name="localhost">  
                    <Context path="" docBase="WORKDIR" reloadable="true"/>  
                </Host>  
            </Engine>  
      </Service>  
  </Server>  

这个配置文件是tomcat在启动时的核心配置,后面会用到。下面简单说下各个组件的作用:

1.Server

Server表示整个的Catalina Servlet容器。Tomcat提供了Server接口的一个默认实现,这通常不需要用户自己去实现。在Server容器中,可以包含一个或多个Service组件。

2.Service

Service是存活在Server内部的中间组件,它将一个或多个连接器(Connector)组件绑定到一个单独的引擎(Engine)上。在Server中,可以包含一个或多个Service组件。Service也很少由用户定制,Tomcat提供了Service接口的默认实现,而这种实现既简单又能满足应用。

3.Connector

连接器(Connector)处理与客户端的通信,它负责接收客户请求,以及向客户返回响应结果。在Tomcat中,有多个连接器可以使用。

4.Engine

在Tomcat中,每个Service只能包含一个Servlet引擎(Engine)。引擎表示一个特定的Service的请求处理流水线。作为一个Service可以有多个连接器,引擎从连接器接收和处理所有的请求,将响应返回给适合的连接器,通过连接器传输给用户。用户允许通过实现Engine接口提供自定义的引擎,但通常不需要这么做。

5.Host

Host表示一个虚拟主机,一个引擎可以包含多个Host。用户通常不需要创建自定义的Host,因为Tomcat给出的Host接口的实现(类StandardHost)提供了重要的附加功能。

6.Context

一个Context表示了一个Web应用程序,运行在特定的虚拟主机中。什么是Web应用程序呢?在Sun公司发布的JavaServlet规范中,对Web应用程序做出了如下的定义:“一个Web应用程序是由一组Servlet、HTML页面、类,以及其他的资源组成的运行在Web服务器上的完整的应用程序。它可以在多个供应商提供的实现了Servlet规范的Web容器中运行”。一个Host可以包含多个Context(代表Web应用程序),每一个Context都有一个唯一的路径。用户通常不需要创建自定义的Context,因为Tomcat给出的Context接口的实现(类StandardContext)提供了重要的附加功能。

这部分参考原文https://blog.youkuaiyun.com/sundacheng1989/article/details/79064112 ,原文还介绍了一下tomcat的启动脚本,感兴趣的同学可以稍微看一下,需要注意的是首先bootstrap.jar 和 tomcat-juli.jar要添加到classpath下,剩下的就是执行Bootstrap类中的main方法。所以所谓的tomcat容器,其实就是一个普通的java程序。只不过tomcat写了一个await方法,让这个java程序可以一直执行,直到收到shutdow通知,或者进程被kill。

二、类图分析

如何编译tomcat源码,参考文末链接。不复杂,如果你熟悉ant编译的话会更简单。这里先上一张启动过程中要涉及的几个类

1.Lifecycle

tomcat中核心组件都实现了该接口,该接口的作用跟它的名字一样,定义了tomcat容器各组件的生命周期。在源码文件的类注释上详细描述了tomcat容器各个状态以及转换方式:

Lifecycle类中只定义了几个管理生命周期的基本方法:init,start,stop,destroy,getState,getStateName,另外还提供了三个跟生命周期Listener相关的方法,暂时不看。

2.LifecycleMBeanBase

tomcat把自己管理的bean统一命名为MBean,LifecycleMBeanBase通过实现MBeanRegistration的方式来把自己变成一个可被tomcat管理的bean,同时继承LifecycleBase。LifecycleBase是Lifecycle接口的一个比较重要的实现,实现了Lifecycle接口中对生命周期的管理方法。

@Override
    public final synchronized void init() throws LifecycleException {
        if (!state.equals(LifecycleState.NEW)) {
            invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
        }

        try {
            setStateInternal(LifecycleState.INITIALIZING, null, false);
            initInternal();
            setStateInternal(LifecycleState.INITIALIZED, null, false);
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            setStateInternal(LifecycleState.FAILED, null, false);
            throw new LifecycleException(
                    sm.getString("lifecycleBase.initFail",toString()), t);
        }
    }
protected abstract void initInternal() throws LifecycleException;

init方法的代码不复杂,阅读可知其主要是负责各个组件的状态装换,至于组件初始化的具体细节,通过initInternal方法交由各个组件自己实现。大家可以看一下其他几个方法例如start,destroy等,基本上都是同一个设计思路。

3.Server,Servie,Container

这三个接口均直接实现了Lifecycle接口。其中Server,Service前面已经介绍过了,与配置文件对应。这里简单说下Container。

tomcat核心组件由六部分组成,Server,Service有独立接口,Connector直接是实现类,剩下的三个组件,即Service管理的Engine,Host,Context均实现Container接口,Container接口要对外提供能够接受请求并且返回响应,简单来说,这三个组件构成了tomcat的核心servlet处理引擎,因为tomcat本质上就是一个web容器,其中Server,Service,Connector都是tomcat特有的概念,而Container下面的三个概念,是所有支持servlet模型的web容器所都要实现的功能,主要提供对servlet生命周期的管理功能。

三、启动流程

1、Bootstrap

分析catalina.sh可知tomcat的启动入口是Bootstrap类的main方法:

public static void main(String args[]) {
        //是否已经启动
        if (daemon == null) {
            // Don't set daemon until init() has completed
            Bootstrap bootstrap = new Bootstrap();
            try {
                //初始化catalina
                bootstrap.init();
            } catch (Throwable t) {
                handleThrowable(t);
                t.printStackTrace();
                return;
            }
            daemon = bootstrap;
        } else {
            // When running as a service the call to stop will be on a new
            // thread so make sure the correct class loader is used to prevent
            // a range of class not found exceptions.
            Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
        }

        try {
            String command = "start";
            if (args.length > 0) {
                command = args[args.length - 1];
            }

            if (command.equals("startd")) {
                args[args.length - 1] = "start";
                daemon.load(args);
                daemon.start();
            } else if (command.equals("stopd")) {
                args[args.length - 1] = "stop";
                daemon.stop();
            } else if (command.equals("start")) {
                daemon.setAwait(true);
                daemon.load(args);
                daemon.start();
            } else if (command.equals("stop")) {
                daemon.stopServer(args);
            } else if (command.equals("configtest")) {
                daemon.load(args);
                if (null==daemon.getServer()) {
                    System.exit(1);
                }
                System.exit(0);
            } else {
                log.warn("Bootstrap: command \"" + command + "\" does not exist.");
            }
        } catch (Throwable t) {
            // Unwrap the Exception for clearer error reporting
            if (t instanceof InvocationTargetException &&
                    t.getCause() != null) {
                t = t.getCause();
            }
            handleThrowable(t);
            t.printStackTrace();
            System.exit(1);
        }

    }

main方法主要负责两件事:1.初始化catalina,2.通过反射调用catalina的方法。简单看下Bootstrap的init方法:

public void init()
        throws Exception
    {

        // Set Catalina path
        setCatalinaHome();
        setCatalinaBase();

        initClassLoaders();

        Thread.currentThread().setContextClassLoader(catalinaLoader);

        SecurityClassLoad.securityClassLoad(catalinaLoader);

        // Load our startup class and call its process() method
        if (log.isDebugEnabled())
            log.debug("Loading startup class");
        Class<?> startupClass =
            catalinaLoader.loadClass
            ("org.apache.catalina.startup.Catalina");
        Object startupInstance = startupClass.newInstance();

        // Set the shared extensions class loader
        if (log.isDebugEnabled())
            log.debug("Setting startup class properties");
        String methodName = "setParentClassLoader";
        Class<?> paramTypes[] = new Class[1];
        paramTypes[0] = Class.forName("java.lang.ClassLoader");
        Object paramValues[] = new Object[1];
        paramValues[0] = sharedLoader;
        Method method =
            startupInstance.getClass().getMethod(methodName, paramTypes);
        method.invoke(startupInstance, paramValues);

        catalinaDaemon = startupInstance;

    }

通过反射获取Catalina类,然后反射注入类加载器,main方法中后续对daemon对象调用的start,stop方法,其实都是通过反射调用Catalina类的相应方法:

public void start()
        throws Exception {
        if( catalinaDaemon==null ) init();

        Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
        method.invoke(catalinaDaemon, (Object [])null);

    }

所以我们接下来的重点要关注Catalina类。

2、Catalina

先看start方法:

public void start() {
           
        if (getServer() == null) {
            //初始化tomcat
            load();
        }

        if (getServer() == null) {
            log.fatal("Cannot start server. Server instance is not configured.");
            return;
        }

        long t1 = System.nanoTime();

        // Start the new server
        try {
            /
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值