文章目录
1、官网下载Tomcat 的源码导入IDEA
一、地址:http://tomcat.apache.org/ 左侧 Download Tomcat 7,在网页最下面下载Tomcat源码;
二、下载完成后打开 , 选择File->Open->选择tomcat的源码目录(我下载的是apache-tomcat-7.0.106);
java
三、① 在项目配置中设置JDK和源码目录:File->Project Structure->project->project SDK
②设置 java包设置为Tomcat项目的Sources文件:File->Project Structure->Modules
web
2、Tomcat启动重要文件
startup源码分析
我以为要研究一个技术的源码要从它是怎么启动运行的开始,特别是很复杂的源码,全部我就从Tomcat的启动开始。
apache
在Tomcat的bin目录下有两个启动Tomcat的文件,一个是startup.bat,它用于windows环境下启动Tomcat;另外一个是startup.sh,它用于Linux环境下Tomcat的启动。大概看了下这两个文件中的实现思路差很少同样的,我就看了startup.bat(windows启动文件)bootstrap
如下是startup.bat文件,我加了一些注解:
经过以上阅读能够获得一个结论: startup.bat文件实际上就作了一件事情 -> 启动catalina.bat
这样我也明白了,为何在此以前我在windows下配置了catalina.bat就可使用catalina run 启动Tomcat了。因此未必必定要经过startup.bat来启动Tomcat,用catalina.bat start也是能够的。
windows
catalina.bat源码分析
既然在 startup.bat有关联到 catalina.bat ,那么就确定要看看这个文件是干吗的了。api
因为注解比代码多,我就梳理一下大概的执行逻辑tomcat
首先会直接跳到mainEntry代码段 -> 在肯定CATALINA_HOME下有catalina.bat后再把CATALINA_HOME赋给变量CATALINA_BASE -> 以后再去得到CLASSPATH(就是咱们配置的环境变量)-> 系统拿到classpath路径后把它和CATALINA_HOME拼接在一块儿,最终定位到一个叫bootstrap.jar的文件;
而后到 doStart
代码块(固然还有doDebug和doRun)并设定参数,最终跳转到execCmd
代码段;
app
经过以上阅读能够获得一个结论: catalina.bat最终执行了Bootstrap类中的main方法。
读完catalina.bat会发现,咱们能够经过设定不一样的参数让Tomcat以不一样的方式运行。好比说:在IDEA中能够选择debug等模式启动Tomcat的,也能够为其配置参数,在catalina.bat中咱们能够看到了启动Tomcat背后的运做流程。
eclipse
//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()方法,那么不妨这么设置一下来运行项目:
首先:新建 catalina-home和像下图同样新建第一个pom.xml文件,并在apache-tomcat-9.0.39-src文件里新建第二个pom.xml(这两个pom文件能够在IDEA新建,也能够去其余项目中引入)
将apache-tomcat-9.0.39-src中的conf文件复制到新建的catalina-home目录之下,再将如下内容复制到apache-tomcat-9.0.39-src中的pom.xml中
webapp
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.apache.tomcat</groupId> <artifactId>Tomcat9.0</artifactId> <name>Tomcat9</name> <version>9.0.20</version> <build> <finalName>Tomcat9</finalName> <sourceDirectory>java</sourceDirectory> <resources> <resource> <directory>java</directory> </resource> </resources> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.3</version> <configuration> <encoding>UTF-8</encoding> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>org.apache.ant</groupId> <artifactId>ant</artifactId> <version>1.10.9</version> </dependency> <dependency> <groupId>org.apache.ant</groupId> <artifactId>ant-apache-log4j</artifactId> <version>1.9.5</version> </dependency> <dependency> <groupId>org.apache.ant</groupId> <artifactId>ant-commons-logging</artifactId> <version>1.9.5</version> </dependency> <dependency> <groupId>javax.xml.rpc</groupId> <artifactId>javax.xml.rpc-api</artifactId> <version>1.1</version> </dependency> <dependency> <groupId>wsdl4j</groupId> <artifactId>wsdl4j</artifactId> <version>1.6.2</version> </dependency> <dependency> <groupId>org.eclipse.jdt.core.compiler</groupId> <artifactId>ecj</artifactId> <version>4.6.1</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.easymock</groupId> <artifactId>easymock</artifactId> <version>4.0.2</version> <scope>test</scope> </dependency> </dependencies> </project>
再将如下内容复制到外层pom.xml中:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.yang</groupId> <artifactId>apache-tomcat-study</artifactId> <name>Tomcat 9.0 Study</name> <version>1.0</version> <packaging>pom</packaging> <!--表示外层项目包含内层项目--> <modules> <module>apache-tomcat-9.0.39-src</module> </modules> </project>
而后:配置 Edit Configrations
Main class:
org.apache.catalina.startup.Bootstrap
vm options配置:
-Dcatalina.home="E:\Code\apache-tomcat-9.0.39-src\catalina-home" -Dfile.encoding=UTF8 -Duser.language=en -Duser.region=US
保存运行便可便可。
3、Tomcat启动流程分析
启动的分析思路就先看到这里,先来看看service.xml到底配置了什么,因而就延伸出来了如下知识。我先说一下Tomcat 部署项目的方式,而后再来引入Tomcat的四大Servlet容器。
一、部署项目的方式
背景:我把Tomcat源码这个项目跑在了IDEA中
有三种方式:这三种方式并非凭空产生,是有源码对应的。
(1)方式一:将web项目(应用)打包成 war 包,以后直接把这个war包放在webapps文件里
这种方式很好理解,就是把应用交给Tomcat去执行。
那么问题是Tomcat是怎么知道这个war包在webapps下,并它就是一个应用呢?
这个问题一会看到四大容器之一的Host就知道了。
(2)方式二:文件夹部署(只要有.class字节码便可运行项目)
第一种部署方式运行项目时,就是解压 war 包而且放到当前webapps目录下,此时是能够删除掉 war包的,只存在这个解压过来的文件也是能够独立运行项目的,这就引出了第二种部署方式,文件夹部署。
(3)方式二:配置应用指定的位置
把应用的位置指定在Host配置中。
二、Tomcat中Container管理四大Servlet容器
先来看看有哪一些,分别是
(1)Engine
表示整个Catalina Servlet引擎,是Container 组件的最高层,用来管理Host 或者Context的实现,是指定默认的Host为 localhost,名字指定为 Catalina,这就是为何咱们不指定Host也可使用localhost虚拟主机来访问到应用。
也就是说一个 Engine 对应一个
List<Host> hosts;
(2)Host
:我理解它是一个Engine管理下的一个虚拟主机,默认的虚拟主机是 localhost
,也就是使用这个虚拟的主机来告诉咱们要访问的 Tomcat 服务的位置,好比说使用 localhost://8080这个就是对应一个虚拟主机,而后再这个Host 里面来配置相应的应用,这样就是顺利访问到咱们指定要访问的应用了。
固然能够存在多个虚拟主机,虽然它们的都是对应同一个 Tomcat,可是对应的应用不同。一个虚拟主机里面也能够对应不一样的应用。说白了,多个Host就是来作一个多个应用的指定位置的。
这也解释了为何 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中的传递流程
好了,以上就是对 Container 接口的四大子接口的分析,它们分别对应四大 Servlet容器。
花了一个晚上搞清楚了这些接口和容器的关系了,直接看这个时序图:
三、分析启动时序图
从Bootstrap类的main方法开始,Tomcat会以链的方式逐级调用各个模块的init()方法进行初始化。待各个模块都初始化后,又会逐级调用各个模块的start()方法启动各个模块。
Bootstrap类首先会建立一个本类对象,而后调用init()方法进行初始化。
这里说一下,实例化是在堆空间中开辟相应的空间并赋默认值,初始化是调用 init() 方法赋实际值的一个过程。
假如是正常执行 start 的方式的话,能够看到在设置等待后,调用了本类对象的load()方法。查看load()方法的源码:
能够看到方法的最后经过反射的方式调用了成员变量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()方法的源码
能够看到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 里,你会发现有 Executor
和Connctor
。
此时也就是说能够走 Container 被继承的四个 Servlet 容器了。
到此也就能够画出简单的一个UML图。来清晰展现这些接口之间的大概关系。
实现与继承关系:
4、Request请求过来Tomcat在干吗
一、Pipeline
…