平常開發中隻需要把開發好的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()執行完成之後,一個項目的基本啟動流程算是執行完了。
分析的不是很細,建議大家下載下傳源碼自己跟一下啟動過程。