Tomcat源码分析----初始化与启动

简介: 在阅读tomcat源码前,我们一般都会有如下几个疑问: - web容器和servlet容器的区别是什么; - 在springMVC中的web.xml是什么时候加载到tomcat中的; - tomcat是怎么加载我们的web服务的; - tomcat是怎么实现的热部署; - 一个http请求

1.前言

1.1 问题思考

在阅读tomcat源码前,我们一般都会有如下几个疑问:

  • web容器和servlet容器的区别是什么;
  • 在springMVC中的web.xml是什么时候加载到tomcat中的;
  • tomcat是怎么加载我们的web服务的;
  • tomcat是怎么实现的热部署;
  • 一个http请求是怎么被tomcat监听到的,会有哪些处理;
  • 为什么请求可以有需要通过nginx的,也可以不需要nginx的直接请求到tomcat上?
    ……

如果你想知道答案,那么接下来的文章会告诉你。

1.2 基本姿势

问题先放在一边,我们都知道Tomcat是一种web容器,用来接收http请求,并将请求得到的结果返回。那么如果要我们设计一个web容器,该怎么做?

很自然的会想到,要有一个网络连接通信维护者和一个请求处理者。通信维护者能够监听到请求并返回数据;请求处理者能够将请求进行分解处理,得到请求应该返回的数据。如果你的思路是这样的,那么恭喜你,你看源码会简单很多,因为Tomcat的设计核心就是这样的,由两大组件组成:Connector和Container。Connector负责的是底层的网络通信的实现,而Container负责的是上层servlet业务的实现。

阅读源码前的准备工作:tomcat源码下载,http://tomcat.apache.org/download-70.cgi
下载好源码后导入eclipse。Tomcat7.0工程是用ant构建的,在导入eclipse的时候可以选择file-new-project-java project from existing ant buildfile导入即可。

本文用的是tomcat7.0,原因是因为公司的tomcat是7.0,当然你也可以下tomcat9的源码进行分析。

也许你会猴急,希望快点知道Connector和Container是怎么设计和实现的,不过我要掉你胃口了,先得给你讲讲Tomcat启动过程,因为只有知道了Tomcat启动过程,才能对Connector和Container是怎么初始化的了然于胸,知道了Connector和Container的初始化才能准确的把握其结构。

2.tomcat启动

2.1 从main看起

启动命令行:java [**] org.apache.catalina.startup.Boostrap start,从命令行中可知,调用的Boostrap类的main方法,main方法参数是start。

    public static void main(String args[]) {
        if (daemon == null) {
            Bootstrap bootstrap = new Bootstrap();
            try {
                bootstrap.init();
            }
            daemon = bootstrap;
        } else {
            Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
        }

        try {
            String command = "start";
            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);
            } 
        }

    }

从源码中可以看到,main方法可传入的参数有 startd、stopd、start、stop,startd和start的区别是设置了一个await的bool变量为true,这点在后面会继续提到。
通过运行tomcat参数的可以看到main方法主要做了3件事:

  1. 实例化Boostrap对象,并调用其init方法;
  2. 调用load方法
  3. 调用start方法;

2.2、实例化Boostrap对象:

调用Boostrap的init方法主要完成三件事:
-(1)初始化路径:设置HOME和Base路径,其中HOME是tomcat安装目录,Base是tomcat工作目录;如果我们想要运行Tomcat 的 多个实例,但是不想安装多个Tomcat软件副本。那么我们可以配置多个工作 目录,每个运行实例独占一个工作目录,但是共享同一个安装目录。
-(2)初始化类加载器:初始化Tomcat类加载器:commonLoader、catalinaLoader、sharedLoader
commonLoader无父加载器,catalinaLoader和sharedLoader的父加载器都是commonLoader,其中若tomcat的配置文件没有配置:server.loader则catalinaLoader=commonLoader,同理,没配置shared.loader……,这三种都是URLClassLoader,使用Java 中的安全模型。
Tomcat 的类加载器体系如下结构所示:
screenshot

-(3)初始化Boostrap的Catalina对象:通过反射生成Catalina对象,并通过反射调用setParentClassLoader方法设置其父 ClassLoader为sharedLoader。为什么要用反射,不直接在声明的时候生成对象?使用反射来生成实例的原因是因为在tomcat的发展历史中可以不止Catalina一种启动方式,现在看代码已经没必要了。
-(4)其他:主线程的classLoader设置为catalinaLoader,安全管理的classLoad设置为catalineLoader。

public void init()
        throws Exception
    {
        //设置系统变量CATALINA_HOME和CATALINA_BASE
        setCatalinaHome();
        setCatalinaBase();
        //初始化classLoader
        initClassLoaders();

        Thread.currentThread().setContextClassLoader(catalinaLoader);//主线程的classLoader

        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();

        //TODO:使用反射来生成实例的原因是因为在tomcat的发展历史中可以不止Catalina一种启动方式,现在看代码已经没必要了
        // 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);

        //TODO:设置catalina的父classLoader为sharedLoader        
        catalinaDaemon = startupInstance;

    }

2.3、Boostrap的load方法

调用Catalina的load方法。通过参数判断设置一些条件并判断是否立即加载,加载调用load方法主要做了4件事:
-(1)判断系统变量catalina设置HOME和Base路径是否设置,没有的话则设置一下(感觉像是烂代码啊,在Boostrap初始化的时候不是已经设置过一次吗?为什么不写在一起?)
-(2)初始化命名服务的基本配置
-(3)Digester类,默认将conf/server.xml解析成相应的对象,这个解析很重要,因为是他完成了Connector和Container的初始化工作。先mark下,接下来会详细的介绍Digester是怎么完成Connector和Container的初始化,现在我们只要知道是这里完成的就可以了。
-(4)Server调用init初始化声明周期,其父类LifecycleMBeanBase实现

//去掉了一些无用的代码
public void load() {
        
        long t1 = System.nanoTime();

        // CATALINA_BASE和CATALINA_HOME设置
        initDirs();

        // 初始化命名服务的基本配置
        initNaming();

        //Digester类,主要用于处理xml配置文件,将xml文件转换成对应的java对象(默认为conf/server.xml)  
        Digester digester = createStartDigester();

        InputSource inputSource = null;
        InputStream inputStream = null;
        File file = null;
        try {
            try {
                // 配置文件,由命令行参数-config指定,否则取默认值conf/server.xml  
                file = configFile();
                inputStream = new FileInputStream(file);
                inputSource = new InputSource(file.toURI().toURL().toString());
            } 
            
            try {
                inputSource.setByteStream(inputStream);
                //将cataline对象压栈,如果栈为空,则设置root为cataline对象
                digester.push(this);
                //遇到相应的应用对对象赋值,相应的调用则调用
                digester.parse(inputSource);
            }
        } 

        getServer().setCatalina(this);

        // Stream redirection
        initStreams();

        // Start the new server
        try {
            getServer().init();
        } 
        ……
    }

2.4、 Boostrap的start方法

调用Catalina的start方法:
-(1)Server加载:Server才是正真的tomcat服务执行者,包括Connector和Container。调用load方法;
-(2)调用Server的start方法,最终调用的是StandardServer的startInternal方法,调用自己所有的Service的start方法,启动connector和container、excutor的start方法,后文会继续扩展。
-(3)注册钩子

public void start() {
        try {
            //服务启动
            getServer().start();
        } 
        ……
        // Register shutdown hook
        if (useShutdownHook) {
            if (shutdownHook == null) {
                shutdownHook = new CatalinaShutdownHook();
            }
            Runtime.getRuntime().addShutdownHook(shutdownHook);
            LogManager logManager = LogManager.getLogManager();
            if (logManager instanceof ClassLoaderLogManager) {
                ((ClassLoaderLogManager) logManager).setUseShutdownHook(
                        false);
            }
        }

        if (await) {
            await();
            stop();
        }
    }

可以看到,Boostrap调用Catalina的方法时,全部都用的是反射,包括生成Catalina对象。而tomcat的启动说白了其实就是初始化并启动Connector和Container。

系列文章直达:

初始化与启动:https://yq.aliyun.com/articles/20169?spm=0.0.0.0.4yGfpo
容器:https://yq.aliyun.com/articles/20172?spm=0.0.0.0.2uPEZi
连接器:https://yq.aliyun.com/articles/20175?spm=0.0.0.0.2uPEZi
一个http请求的经历:https://yq.aliyun.com/articles/20177?spm=0.0.0.0.2uPEZi
重要的设计模式:https://yq.aliyun.com/articles/20179?spm=0.0.0.0.2uPEZi

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