我們可以通過Tomcat的/bin目錄下的腳本startup.sh來啟動(dòng)Tomcat,執(zhí)行了這個(gè)腳本會發(fā)生什么呢? 通過下面這張流程圖了解一下衫嵌。
- Tomcat本質(zhì)上是一個(gè)Java程序浸锨,因此startup.sh腳本會啟動(dòng)一個(gè)JVM來運(yùn)行Tomcat的啟動(dòng)類Bootstrap。
- Bootstrap的主要任務(wù)是初始化Tomcat的類加載器并創(chuàng)建Catalina介却。
- Catalina是一個(gè)啟動(dòng)類,它通過解析server.xml块茁、創(chuàng)建相應(yīng)的組件齿坷,并調(diào)用Server的start方法。
- Server組件的職責(zé)就是管理Service組件数焊,它會負(fù)責(zé)調(diào)用Service的start方法永淌。
- Service組件的職責(zé)就是管理連接器和頂層容器組件Engine,因此它會調(diào)用連接器和Engine的start方法佩耳。
Catalina
Catalina的主要任務(wù)就是創(chuàng)建Server遂蛀,需要解析出server.xml,把在server.xml里配置的各種組件一一創(chuàng)建出來干厚,接著調(diào)用Server組件的init方法和start方法李滴,這樣整個(gè)Tomcat就啟動(dòng)起來了。作為”管理者“蛮瞄,Catalina還需要處理各種異常情況所坯,比如我們通過”Ctrl + C“關(guān)閉Tomcat時(shí),Tomcat將如何優(yōu)雅的停止并且清理資源呢挂捅?因此Catalina在JVM中注冊了一個(gè)”關(guān)閉鉤子“芹助。
public void start() {
// 如果持有的Server實(shí)例為空,就解析server.xml創(chuàng)建一個(gè)
if (getServer() == null) {
load();
}
// 如果創(chuàng)建失敗 報(bào)錯(cuò)退出
if (getServer() == null) {
log.fatal("Cannot start server. Server instance is not configured.");
return;
}
long t1 = System.nanoTime();
// 啟動(dòng)Server
try {
getServer().start();
} catch (LifecycleException e) {
log.fatal(sm.getString("catalina.serverStartFail"), e);
try {
getServer().destroy();
} catch (LifecycleException e1) {
log.debug("destroy() failed for failed Server ", e1);
}
return;
}
long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
}
// 創(chuàng)建并注冊JVM關(guān)閉鉤子
if (useShutdownHook) {
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook);
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(
false);
}
}
// 用await方法監(jiān)聽停止請求
if (await) {
await();
stop();
}
}
那什么是”關(guān)閉鉤子“闲先,它又是做什么的呢状土?如果我們需要在JVM關(guān)閉時(shí)做一些清理工作,比如將緩存數(shù)據(jù)刷到磁盤上伺糠,或者清理一些臨時(shí)文件蒙谓,可以向JVM注冊一個(gè)”關(guān)閉鉤子“,”關(guān)閉鉤子“其實(shí)就是一個(gè)線程退盯,JVM在停止之前會嘗試執(zhí)行這個(gè)線程的run方法彼乌。下面是Tomcat的”關(guān)閉鉤子“CatalinaShutdownHook:
protected class CatalinaShutdownHook extends Thread {
@Override
public void run() {
try {
if (getServer() != null) {
Catalina.this.stop();
}
} catch (Throwable ex) {
ExceptionUtils.handleThrowable(ex);
log.error(sm.getString("catalina.shutdownHookFail"), ex);
} finally {
// If JULI is used, shut JULI down *after* the server shuts down
// so log messages aren't lost
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).shutdown();
}
}
}
}
可以看出,Tomcat的“關(guān)閉鉤子”實(shí)際上就是執(zhí)行了Server的stop方法渊迁,Server組件的stop方法會釋放和清理所有的資源慰照。
Server組件
Server組件的具體實(shí)現(xiàn)類是StandardServer,Server繼承了LifecycleBase琉朽,它的生命周期被統(tǒng)一管理毒租,并且它的子組件是Service,因此它還要管理Service的生命周期箱叁,也就是說在啟動(dòng)時(shí)調(diào)用Service組件的啟動(dòng)方法墅垮,在停止時(shí)調(diào)用它們的停止方法惕医。Server在內(nèi)部維護(hù)了若干Service組件,它是以數(shù)組來保存的算色,下面是Server添加一個(gè)Service到數(shù)組中的方法:
public void addService(Service service) {
service.setServer(this);
synchronized (servicesLock) {
// 創(chuàng)建一個(gè)長度加一的數(shù)組
Service results[] = new Service[services.length + 1];
// 將老的數(shù)據(jù)復(fù)制過去
System.arraycopy(services, 0, results, 0, services.length);
results[services.length] = service;
services = results;
// 啟動(dòng) Service 組件
if (getState().isAvailable()) {
try {
service.start();
} catch (LifecycleException e) {
// Ignore
}
}
// 觸發(fā)監(jiān)聽事件
// Report this property change to interested listeners
support.firePropertyChange("service", null, service);
}
}
除此之外抬伺,Server組件還有一個(gè)重要的任務(wù)是啟動(dòng)一個(gè)Socket類監(jiān)聽停止端口,這就是為什么你能通過shutdown命令來關(guān)閉Tomcat灾梦。上面Caralina的啟動(dòng)方法的最后一行代碼就是調(diào)用了Server的await方法峡钓。在await方法里會創(chuàng)建一個(gè)Socket監(jiān)聽8005端口,并在一個(gè)死循環(huán)里接收Socket上的連接請求若河,如果有新的連接到來就新建連接能岩,然后從Socket中讀取數(shù)據(jù);如果讀到的數(shù)據(jù)是停止命令”SUTDOWN“萧福,就退出循環(huán)拉鹃,進(jìn)入stop流程。
Service組件
Service組件的具體實(shí)現(xiàn)類是StandardService鲫忍,我們西拿來看看它的定義以及關(guān)鍵的成員變量膏燕。
public class StandardService extends LifecycleMBeanBase implements Service {
/**
* The name of this service.
* Service的名字
*/
private String name = null;
/**
* The <code>Server</code> that owns this Service, if any.
* Server實(shí)例
*/
private Server server = null;
/**
* The set of Connectors associated with this Service.
* 連接器數(shù)組
*/
protected Connector connectors[] = new Connector[0];
private final Object connectorsLock = new Object();
// 對應(yīng)的Engine容器
private Engine engine = null;
/**
* Mapper.
* 映射器
*/
protected final Mapper mapper = new Mapper();
/**
* Mapper listener.
* 映射器的監(jiān)聽器
*/
protected final MapperListener mapperListener = new MapperListener(this);
}
為什么要有一個(gè)MapperListener?這是因?yàn)門omcat支持熱部署饲窿,當(dāng)Web應(yīng)用的部署發(fā)生變化時(shí)煌寇,Mapper中的映射信息也要跟著變化焕蹄,MapperListener就是一個(gè)監(jiān)聽器逾雄,它監(jiān)聽容器的變化,并把信息更新到Mapper中腻脏,這是典型的觀察者模式鸦泳。
作為”管理“角色的組件,最重要的是維護(hù)其他組件的生命周期永品。此外在啟動(dòng)各種組件時(shí)做鹰,要注意它們的依賴關(guān)系,也就是說鼎姐,要注意啟動(dòng)的順序钾麸,Service的啟動(dòng)方法:
protected void startInternal() throws LifecycleException {
if(log.isInfoEnabled())
log.info(sm.getString("standardService.start.name", this.name));
// 觸發(fā)啟動(dòng)監(jiān)聽器
setState(LifecycleState.STARTING);
// 先啟動(dòng)engine, Engine會啟動(dòng)它的子容器
// Start our defined Container first
if (engine != null) {
synchronized (engine) {
engine.start();
}
}
synchronized (executors) {
for (Executor executor: executors) {
executor.start();
}
}
// 啟動(dòng)Mapper容器
mapperListener.start();
// 啟動(dòng)連接器炕桨,連接器會啟動(dòng)它的子組件 比如Endpoint
// Start our defined Connectors second
synchronized (connectorsLock) {
for (Connector connector: connectors) {
try {
// If it has already failed, don't try and start it
if (connector.getState() != LifecycleState.FAILED) {
connector.start();
}
} catch (Exception e) {
log.error(sm.getString(
"standardService.connector.startFailed",
connector), e);
}
}
}
}
從啟動(dòng)方法可以看到饭尝,Service先啟動(dòng)了Engine組件,再啟動(dòng)Mapper監(jiān)聽器献宫,最后才是啟動(dòng)連接器钥平,內(nèi)層組件啟動(dòng)好了才能對外提供服務(wù),才能啟動(dòng)外層的連接器組件姊途。而Mapper也依賴容器組件涉瘾,容器組件啟動(dòng)好了才能監(jiān)聽它們的變化知态,因此Mapper和MapperListener在容器組件之后啟動(dòng)。組件停止的順序和啟動(dòng)的順序正好相反的立叛,也是基于它們的依賴關(guān)系负敏。
Engine組件
再來看看頂層容器組件Engine是如何實(shí)現(xiàn)的,Engine本質(zhì)是一個(gè)容器秘蛇,因此它繼承了ContainerBase基類原在,并且實(shí)現(xiàn)了Engine接口。
public class StandardEngine extends ContainerBase implements Engine {
...
}
Engine的子容器是Host彤叉,所以它持有了一個(gè)Host容器的數(shù)組庶柿,在抽象類ContainerBase中,ContainerBase中有這樣一個(gè)數(shù)據(jù)結(jié)構(gòu):
protected final HashMap<String, Container> children = new HashMap<>();
ContainerBase用HashMap保存了它的子容器秽浇,并且ContainerBase還實(shí)現(xiàn)了子容器的”增刪改查“浮庐,甚至連子容器的啟動(dòng)和停止都提供了默認(rèn)實(shí)現(xiàn),比如ContainerBase會用專門的線程池來啟動(dòng)子容器柬焕。
for (int i = 0; i < children.length; i++) {
results.add(startStopExecutor.submit(new StartChild(children[i])));
}
所以Engine在啟動(dòng)Host子容器時(shí)就直接重用了這個(gè)方法审残。
我們知道容器最重要的功能是處理請求,而Engine容器對請求的”處理“斑举,其實(shí)就是把請求轉(zhuǎn)發(fā)給某一個(gè)Host子容器來處理搅轿,具體是通過Valve來實(shí)現(xiàn)的。
我們知道每一個(gè)容器組件都有一個(gè)Pipeline富玷,而Pipeline中有一個(gè)基礎(chǔ)閥(Basic Valve)璧坟,而Engine容器的基礎(chǔ)閥定義如下:
final class StandardEngineValve extends ValveBase {
public final void invoke(Request request, Response response)
throws IOException, ServletException {
// Select the Host to be used for this Request
// 拿到請求中的Host容器
Host host = request.getHost();
if (host == null) {
response.sendError
(HttpServletResponse.SC_BAD_REQUEST,
sm.getString("standardEngine.noHost",
request.getServerName()));
return;
}
if (request.isAsyncSupported()) {
request.setAsyncSupported(host.getPipeline().isAsyncSupported());
}
// Ask this Host to process this request
// 調(diào)用Host容器中的Pipeline中的第一個(gè)Valve
host.getPipeline().getFirst().invoke(request, response);
}
}
這個(gè)基礎(chǔ)閥實(shí)現(xiàn)非常簡單,就是把請求轉(zhuǎn)發(fā)到Host容器赎懦。我們可以看到處理請求的Host容器對象是從請求中拿到的雀鹃,請求對象中怎么會有Host容器呢?這是因?yàn)檎埱蟮竭_(dá)Engine容器之前励两,Mapper組件已經(jīng)對請求進(jìn)行了路由處理黎茎,Mapper組件通過請求的URL定位了相應(yīng)的容器,并且把容器對象保存到了請求對象中当悔。
Tomcat的啟動(dòng)過程傅瞻,具體是由啟動(dòng)類和”高層“組件來完成的,它們都承擔(dān)著”管理“的角色盲憎,負(fù)責(zé)將子組件創(chuàng)建出來嗅骄,并把它們拼裝在一起,同時(shí)也掌握子組件的”生殺大權(quán)“焙畔。