tomcat源码解析---web项目在tomcat中的启动过程分析

xiaoxiao2021-04-15  40

平常开发中只需要把开发好的war包上传到服务器,启动服务器,web项目就跟着启动运行了,这是为什么?服务器都做了哪些事情?下面我们通过跟踪调试tomcat源码,分析一下web项目的启动过程。 源码下载地址: http://mirrors.hust.edu.cn/apache/tomcat/tomcat-8/v8.5.34/src/apache-tomcat-8.5.34-src.zip 下载完成解压后删除webapps目录下的examples目录,不删除启动可能会报错(我的机器上是这样的),然后在根目录下创建pom.xml的maven依赖文件,内容如下:

<?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>cn.linxdcn</groupId> <artifactId>Tomcat8.0</artifactId> <name>Tomcat8</name> <version>8.0</version> <build> <finalName>Tomcat8</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.9.5</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.4</version> </dependency> </dependencies> </project>

把文件保存好之后,就可以导入源码到开发工具中查看了。 查看源码之前,首先我们看一下tomcat的结构模型

Server:代表整个服务器,一个服务器中可以有多个Service;Service: 由一个或者多个Connector组成,以及一个Engine,负责处理所有Connector所获得的客户请求;Connector: 一个Connector将在某个指定端口上侦听客户请求,并将获得的请求交给Engine来处理,从Engine处获得回应并返回客户;Engine:Engine下可以配置多个虚拟主机Virtual Host,每个虚拟主机都有一个域名 当Engine获得一个请求时,它把该请求匹配到某个Host上,然后把该请求交给该Host来处理,Engine有一个默认虚拟主机,当请求无法匹配到任何一个Host上的时候,将交给该默认Host来处理Host: 代表一个Virtual Host,虚拟主机,每个虚拟主机和某个网络域名Domain Name相匹配,每个虚拟主机下都可以部署(deploy)一个或者多个Web App,每个Web App对应于一个Context,有一个Context path当Host获得一个请求时,将把该请求匹配到某个Context上,然后把该请求交给该Context来处理,匹配的方法是“最长匹配”,所以一个path==”"的Context将成为该Host的默认Context,所有无法和其它Context的路径名匹配的请求都将最终和该默认Context匹配Context:一个Context对应于一个Web Application,一个Web Application由一个或者多个Servlet组成,Context在创建的时候将根据配置文件CATALINA_HOME/conf/web.xml和$WEBAPP_HOME/WEB-INF/web.xml载入Servlet类,当Context获得请求时,将在自己的映射表(mapping table)中寻找相匹配的Servlet类,如果找到,则执行该类,获得请求的回应,并返回

在源码中与这6个组件对应的类如下: Connector :org.apache.catalina.connector.Connector.java Server: org.apache.catalina.core.StandardServer.java Service: org.apache.catalina.core.StandardService.java Engine: org.apache.catalina.core.StandardEngine.java Host: org.apache.catalina.core.StandardHost.java Context: org.apache.catalina.core.StandardContext.java

组件的生命周期:Connector、StandardServer、StandardService、StandardEngine、StandardHost、StandardContext这6个组件类通过继承它们的父类都间接实现了生命周期接口Lifecycle。该接口定义组件在初始化、启动、停止、销毁的时候所要做的处理。初始化调用接口的init()方法,启动调用接口的start()方法,停止调用接口的stop()方法,销毁调用接口的destroy()方法。调用这些方法的前后通常会伴随着组件状态(LifecycleState)的改变,当组件状态改变的时候会发布相应的事件(LifecycleEvent),该事件将由接口注册的事件监听器(LifecycleListener)来处理。 Lifecycle接口部分内容: 源码跟踪: tomcat启动入口在Bootstrap类下的main()函数 源码中的类路径:org.apache.catalina.startup.Bootstrap.java main函数如下(代码过长的以后只列出核心代码) 进入Bootstrap的init()方法: init()方法的核心是创建Catalina类的一个实例catalinaDaemon,回到main()函数中接着往下走 进入Bootstrap的load()方法: 在daemon.load(args)方法中反射调用了catalinaDaemon.load(args)方法,进入Catalina.load()方法中 digester这个对象中定义了在接下来的启动过程中要创建的对象以及创建对象的规则 file是读取conf/server.xml生成的文件 在Catalina.load()方法中接着往下走: 调用digester.parse()之后,在之前我们在createStartDigester()函数中添加的对象都会按照给定的规则实例化。 (Connector、StandardServer、StandardService、StandardEngine、StandardHost就是在这一步实例化的,StandardContext的实例化不在这个过程中,后面讲) 创建的过程参见Digester.startElement()方法和Rule.begin()方法。

在Catalina.load()方法中接着往下走: getServer().init()执行的是StandardServer.init(),StandardServer中的init()方法来自于它父类的父类LifecycleBase,该类是一个实现生命周期接口Lifecycle的抽象类,LifecycleBase.init()方法的实现如下: StandardServer的初始化主要是在StandardServer.initInternal()中完成的,其他几个组件跟这类似,初始化也是主要在自己的initInternal()方法中完成。

进入到StandardServer.initInternal() StandardServer.initInternal()最后用循环调用StandardService.init()方法。init()方法的执行流程都是类似的,不再进入查看了。StandardService的初始化方法中又调用了StandardEngine的初始化方法。StandardEngine初始化完成后,整个的初始化过程完成(StandardHost和StandardContext的初始化不在这个过程中)。 getServer.init()方法执行完,load()方法执行完之后,接着执行Bootstrap的start()方法 进入Bootstrap的start()方法中: Bootstrap.start()方法中反射调用了Catalina.start()方法,进入该方法中: getServer().start()执行的是StandardServer.start()方法,与StandardServer.init()类似,StandardServer.start()最终执行的是StandardServer.startInternal()方法,进入该方法: StandardService.start()最终执行的是 StandardService.startInternal()方法,进入该方法: StandardEngine、StandardHost、StandardContext都继承了ContainerBase类,ContainerBase实现了容器接口Container。StandardHost是StandardEngine的子容器(child),StandardContext是StandardHost的子容器。 StandardEngine.start()方法最终调用的是ContainerBase.startInternal(),进入该方法中: 该方法的核心是自己启动的同时找到自己的子容器,并以多线程的方式启动它们。StandardEngine启动之后,会接着启动自己的子容器StandardHost。

StandardHost在启动的时候会发布一个启动事件给自己的监听器来处理,StandardHost的事件监听器的实现类是HostConfig,进入它的事件处理函数: 进入start()方法: deployApps()就是部署Host中项目的操作,进入该函数: 这个函数就是根据不同的情况来部署我们的项目的,其中deployWARs()就是用来部署war包的,进入该函数: 该函数的核心是利用多线程来部署每一个war包,最总的部署代码是HostConfig.deployWAR(),进入该方法中: 该方法的核心是创建StandardContext的实例(每部署一个项目都会生成一个StandardContext的实例),并将其作为子容器添加到StandardHost中。StandardHost启动完成后启动它的子容器StandardContext。进入StandardContext.startInternal()方法中: 在该方法执行过程中发布了一个事件,进入StandardContext的事件监听器ContextConfig的事件处理函数中查看对该事件的处理: 进入configureStart()函数: 进入webConfig()函数: 这个函数的核心是对我们项目中web.xml文件的解析处理,在这个函数中接着往下走: 进入configureContext()中: 从web.xml中解析出Filter、Listener、Servlet定义的代码。 从web.xml中解析的所有内容都会添加到对应项目的StandardContext实例中。解析完成之后继续回到 StandardContext.startInternal()中往下执行: 如果我们的项目全部是基于注解开发的,没有web.xml文件,这一步的作用十分重要,想要了解的请去看 org.springframework.web.SpringServletContainerInitializer.class这个类。如果我们的项目是基于xml配置开发的 这一步貌似没什么卵用。接着往下执行: 进入listenerStart()函数: listener是我们在web.xml中配置的,用于加载spring配置文件的,有兴趣的可以去看下源码contextInitialized()的实现,常见配置如下: listener.contextInitialized()执行之后,我们web项目中的spring容器才真正启动。 回到StandardContext.startInternal()中往下执行: loadOnStartup()方法的作用是初始化servlet,我们知道servlet有个init()方法,这个地方就是调用init()方法。 DispatcherServlet就是在这儿初始化的,它父类的初始化方法如下,有兴趣的可以去看下完整的初始化过程:

当StandardContext.start()执行完成之后,一个项目的基本启动流程算是执行完了。 分析的不是很细,建议大家下载源码自己跟一下启动过程。

转载请注明原文地址: https://www.6miu.com/read-4817809.html

最新回复(0)