欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Tomcat学习记录

程序员文章站 2022-04-15 19:06:23
一、官网下载Tomcat 的源码:1、地址:http://tomcat.apache.org/ 左侧 Download Tomcat 7,在网页最下面下载Tomcat源码;2、下载完成后打开 , 选择File->Open->选择tomcat的源码目录...

一、官网下载Tomcat 的源码并运行在 IDEA

1、地址:http://tomcat.apache.org/ 左侧 Download Tomcat 7,在网页最下面下载Tomcat源码;
Tomcat学习记录
2、下载完成后打开 , 选择File->Open->选择tomcat的源码目录(我下载的是apache-tomcat-7.0.106);

3、① 在项目配置中设置JDK和源码目录:File->Project Structure->project->project SDK
      ②设置 java和test包为Tomcat项目的Sources文件:File->Project Structure->Modules
Tomcat学习记录

二、Tomcat启动重要文件

startup源码分析

我觉得要研究一个技术的源码要从它是怎么启动运行的开始,特别是很复杂的源码,所有我就从Tomcat的启动开始。
Tomcat学习记录

在Tomcat的bin目录下有两个启动Tomcat的文件,一个是startup.bat,它用于windows环境下启动Tomcat;另一个是startup.sh,它用于Linux环境下Tomcat的启动。大概看了下这两个文件中的实现思路差不多一样的,我就看了startup.bat(windows启动文件)

以下是startup.bat文件,我加了一些注解:
Tomcat学习记录
Tomcat学习记录
通过以上阅读可以得到一个结论: startup.bat文件实际上就做了一件事情 -> 启动catalina.bat
这样我也明白了,为什么在此之前我在windows下配置了catalina.bat就可以使用catalina run 启动Tomcat了。所以未必一定要通过startup.bat来启动Tomcat,用catalina.bat start也是可以的。

catalina.bat源码分析

既然在 startup.bat有关联到 catalina.bat ,那么就肯定要看看这个文件是干嘛的了。

由于注解比代码多,我就梳理一下大概的执行逻辑

首先会直接跳到mainEntry代码段 -> 在确定CATALINA_HOME下有catalina.bat后再把CATALINA_HOME赋给变量CATALINA_BASE -> 之后再去获得CLASSPATH(就是我们配置的环境变量)-> 系统拿到classpath路径后把它和CATALINA_HOME拼接在一起,最终定位到一个叫bootstrap.jar的文件;
Tomcat学习记录
然后到 doStart代码块(当然还有doDebug和doRun)并设定参数,最终跳转到execCmd代码段;

通过以上阅读可以得到一个结论: catalina.bat最终执行了Bootstrap类中的main方法
读完catalina.bat会发现,我们可以通过设定不同的参数让Tomcat以不同的方式运行。比如说:在IDEA中可以选择debug等模式启动Tomcat的,也可以为其配置参数,在catalina.bat中我们可以看到了启动Tomcat背后的运作流程。

 //public final class Bootstrap Bootstrap类的main方法
 public static void main(String args[]) {

        synchronized (daemonLock) {
            if (daemon == null) {
                // Don't set daemon until init() has completed
                Bootstrap bootstrap = new Bootstrap();
                try {
                    bootstrap.init();  //注意这个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);
            }
        }

刚刚说了,既然启动是靠Bootstrap的main()方法,那么不妨这么设置一下来运行项目:

org.apache.catalina.startup.Bootstrap
-Duser.language=""

Tomcat学习记录
保存即可。

三、Tomcat启动流程分析

启动的分析思路就先看到这里,先来看看service.xml到底配置了什么,于是就延伸出来了以下知识。我先说一下Tomcat 部署项目的方式,然后再来引入Tomcat的四大Servlet容器

1、部署项目的方式

背景:我把Tomcat源码这个项目跑在了IDEA中

有三种方式:这三种方式并不是凭空产生,是有源码对应的。
Tomcat学习记录
(1)方式一:将web项目(应用)打包成 war 包,之后直接把这个war包放在webapps文件里
这种方式很好理解,就是把应用交给Tomcat去执行。
那么问题是Tomcat是怎么知道这个war包在webapps下,并它就是一个应用呢?
这个问题一会看到四大容器之一的Host就知道了。
Tomcat学习记录
Tomcat学习记录

(2)方式二:文件夹部署(只要有.class字节码即可运行项目)
第一种部署方式运行项目时,就是解压 war 包并且放到当前webapps目录下,此时是可以删除掉 war包的,只存在这个解压过来的文件也是可以独立运行项目的,这就引出了第二种部署方式,文件夹部署。

(3)方式二:配置应用指定的位置
把应用的位置指定在Host配置中。
Tomcat学习记录

2、Tomcat中Container管理四大Servlet容器

先来看看有哪一些,分别是
Tomcat学习记录

(1)Engine 表示整个Catalina Servlet引擎,是Container 组件的最高层,用来管理Host 或者Context的实现,是指定默认的Host为 localhost,名字指定为 Catalina,这就是为什么我们不指定Host也可以使用localhost虚拟主机来访问到应用。
Tomcat学习记录
也就是说一个 Engine 对应一个

List<Host> hosts;

(2)Host:我理解它是一个Engine管理下的一个虚拟主机,默认的虚拟主机是 localhost,也就是使用这个虚拟的主机来告诉我们要访问的 Tomcat 服务的位置,比如说使用 localhost://8080这个就是对应一个虚拟主机,然后再这个Host 里面来配置相应的应用,这样就是顺利访问到我们指定要访问的应用了。

当然可以存在多个虚拟主机,虽然它们的都是对应同一个 Tomcat,但是对应的应用不一样。一个虚拟主机里面也可以对应不同的应用。说白了,多个Host就是来做一个多个应用的指定位置的
Tomcat学习记录
这也解释了为什么 Tomcat 会去webapps里找应用。
也就是说一个 Host 对应一个

List<Context> contexts;

(3)Context:直接理解的话,是上下文,但是我理解它是一个应用,或者是一个应用的配置,使用文件描述符配置文件的话,就会使用到 Context 容器。一个Context可以对应一个应用。
也就是说一个 Context 对应一个

List<Servlet> servlets;

(4)Wrapper:我理解它就是来对我们同一个 Servlet 的不同实例来进行分类管理的,也就是多次请求Tomcat 中同一个 Servlet 资源就会产生不同的 Servlet 实例,然而这些实例不可能任意在容器中,这样就不要管理,会造成混乱,所以就用 Weapper 来对同一类Servlet 的不同实例进行分类。Wrapper 还用来管理一个 Servlet 的生命周期。
也就是一个 Wrapper 对应一个 Servlet 类型。

List<Servlet> servlets; 

而一个Context 就对应了一个Wrapper了

List<Wrapper> wrappers;

Http请求在Container中的传递流程
Tomcat学习记录

好了,以上就是对 Container 接口的四大子接口的分析,它们分别对应四大 Servlet容器。

花了一个晚上搞清楚了这些接口和容器的关系了,直接看这个时序图:
Tomcat学习记录

3、分析启动时序图

从Bootstrap类的main方法开始,Tomcat会以链的方式逐级调用各个模块的init()方法进行初始化。待各个模块都初始化后,又会逐级调用各个模块的start()方法启动各个模块。
Tomcat学习记录
Bootstrap类首先会创建一个本类对象,然后调用init()方法进行初始化。

这里说一下,实例化是在堆空间中开辟相应的空间并赋默认值,初始化是调用 init() 方法赋实际值的一个过程。
Tomcat学习记录
假如是正常执行 start 的方式的话,可以看到在设置等待后,调用了本类对象的load()方法。查看load()方法的源码:
Tomcat学习记录
可以看到方法的最后通过反射的方式调用了成员变量catalinaDaemon的load()方法。通过跟踪源码可以看到catalinaDaemon是Bootstrap类的一个私有成员变量。

public final class Bootstrap {
    private static final Log log = LogFactory.getLog(Bootstrap.class);
    /**
     * Daemon object used by main.di
     */
    private static final Object daemonLock = new Object();
    private static volatile Bootstrap daemon = null;
    /**
     * Daemon reference.
     */
     //在init()方法中使用反射机制创建catalina赋给catalinaDaemon
    private Object catalinaDaemon = null;

它会在Bootstrap的init()方法中通过反射的方式完成初始化。下面我们回过头来看init()方法的源码

Tomcat学习记录
可以看到init()方法创建了一个Catalina对象, 并把该对象赋给了catalinaDaemon。

之后再执行 getServer 方法来创建 Server 容器。

public interface Service extends Lifecycle {
    /**
     * @return the <code>Container</code> that handles requests for all
     * <code>Connectors</code> associated with this Service.
     */
    public Container getContainer();

来到了 Service 里,你会看到第一句就定义一个获取 Container 的方法,也就是可以获取一个唯一的Container。 在这个 Service 里,你会发现有 ExecutorConnctor

此时也就是说可以走 Container 被继承的四个 Servlet 容器了。

到此也就可以画出简单的一个UML图。来清晰展示这些接口之间的大概关系。

Tomcat学习记录
实现与继承关系:
Tomcat学习记录

四、Request请求过来Tomcat在干嘛

1、Pipeline

本文地址:https://blog.csdn.net/qq_43012792/article/details/109629906