TOMCAT 源碼分析 – 啓動
前語
Tomcat源碼版本爲官網下載的9.0.35版本。
構建環境參考https://blog.csdn.net/a17816876003/article/details/106586805
配置文件
Tomcat啓動的配置文件爲server.xml
,啓動過程也全都圍繞它進行,Tomcat的模塊結構也可以在其中一覽無餘
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<GlobalNamingResources>
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<Engine name="Catalina" defaultHost="localhost">
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
</Engine>
</Service>
</Server>
TOMCAT模塊結構
Tomcat最重要的模塊是Container容器,它層層包裹,像“套娃”一樣一層套一層,便於管理內層的生命週期。其結構於配置文件server.xml
中的XML標籤可以看得出來。首先記下理解這張圖的模塊結構,對下面源碼啓動的順序理解幫助十分大。
生命週期
Tomcat中每個容器的生命週期都實現了一個接口Lifecycle
,其重要的方法有init
、start
、stop
、destroy
、getState
等。從它的繼承樹(僅展示部分)可以觀察到,容器都實現了它:
啓動類
其啓動類爲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();
} catch (Throwable t) {
}
daemon = bootstrap;
} else {
Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
}
}
try {
String command = "start";
// ...
} else if (command.equals("start")) {
daemon.setAwait(true);
// 最重要的兩步 -- daemon爲Bootstrap類本身
// 第一步加載 -- 加載完會綁定監聽socket端口(Tomcat 8開始使用NIO)
daemon.load(args);
// 第二步啓動 -- 啓動完纔會去accept()處理請求
daemon.start();
if (null == daemon.getServer()) {
System.exit(1);
}
}
} catch (Throwable t) {
}
}
加載-load
加載過程,通過反射調用了Catalina
類的load
方法
/**
* Load daemon.
*/
private void load(String[] arguments) throws Exception {
// Call the load() method
String methodName = "load";
Object param[];
Class<?> paramTypes[];
if (arguments==null || arguments.length==0) {
paramTypes = null;
param = null;
} else {
paramTypes = new Class[1];
paramTypes[0] = arguments.getClass();
param = new Object[1];
param[0] = arguments;
}
// 通過反射調用了`Catalina`類的`load`方法
Method method =
catalinaDaemon.getClass().getMethod(methodName, paramTypes);
if (log.isDebugEnabled()) {
log.debug("Calling startup class " + method);
}
method.invoke(catalinaDaemon, param);
}
其加載步驟按順序有以下幾步:
1. 讀取配置文件
讀取%home%/conf/server.xml配置文件,並用Digester
進行解析,將配置文件的容器等配置按照層次遞歸的放入Server
中,實際爲StandardServer
這個類中。
// Set configuration source
ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(Bootstrap.getCatalinaBaseFile(), getConfigFile()));
File file = configFile();
// Create and execute our Digester
Digester digester = createStartDigester();
// ...
digester.push(this);
digester.parse(inputSource);
2. 對server
對象進行初始化
getServer().init();
- 2.1 發現其初始化都是用超類
LifecycleBase
的final方法進行初始化的(Engine
、Host
、Context
、Wrapper
等都間接繼承於它)。
public abstract class LifecycleBase implements Lifecycle {
@Override
public final synchronized void init() throws LifecycleException {
// ...
try {
// 設置狀態
setStateInternal(LifecycleState.INITIALIZING, null, false);
// 真正執行初始化的方法
initInternal();
setStateInternal(LifecycleState.INITIALIZED, null, false);
} catch (Throwable t) {
handleSubClassException(t, "lifecycleBase.initFail", toString());
}
}
}
查看initInternal
方法的定義,發現他將這個方法交由子類進行具體實現,這裏就提現了Java多態的模板方法的好處,繼續看下去會發現,所有Containner
組件的初始化都經過init()
方法,最終由自己實現initInternal
方法,管理自己內部容器的初始化操作。
// LifecycleBase.java
protected abstract void initInternal() throws LifecycleException;
繼續進入initInternal
方法執行,進入了Server
的實現類StandardServer
中
// StandardServer.java
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
// Initialize utility executor
reconfigureUtilityExecutor(getUtilityThreadsInternal(utilityThreads));
// Initialize our defined Services
// Server管理自己的套娃Service,最終也借用超類的init()方法對Service進行初始化
for (Service service : services) {
// 對service進行init
service.init();
}
}
3. 對service
進行初始化
在第二步的末尾的循環中又進入了2.1
中的init
方法,再調用service
實現類StandardService
的initInternal
方法
// StandardService.java
@Override
protected void initInternal() throws LifecycleException {
// 初始化Engine
if (engine != null) {
engine.init();
}
// 初始化執行器
for (Executor executor : findExecutors()) {
if (executor instanceof JmxEnabled) {
((JmxEnabled) executor).setDomain(getDomain());
}
executor.init();
}
// 初始化監聽器
mapperListener.init();
// 初始化 Connectors (可以有多個連接器)
synchronized (connectorsLock) {
for (Connector connector : connectors) {
connector.init();
}
}
}
4. 初始化引擎、執行器、監聽器、連接器
-
初始化引擎
重要的仍是其實現父類的
initInternal
方法,對Realm
(領域)的配置,實際獲取了LockOutRealm
。// StandardEngine.java @Override protected void initInternal() throws LifecycleException { getRealm(); // 這一步中還會有`ContainerBase`中創建`startStopExecuter`線程池供`start`啓動階段使用。 super.initInternal(); } // ContainerBase.java private void reconfigureStartStopExecutor(int threads) { if (threads == 1) { // Use a fake executor -- 虛假的線程池 if (!(startStopExecutor instanceof InlineExecutorService)) { startStopExecutor = new InlineExecutorService(); } } else { // Delegate utility execution to the Service Server server = Container.getService(this).getServer(); server.setUtilityThreads(threads); startStopExecutor = server.getUtilityExecutor(); } }
-
初始化執行器
源碼中帶的
server.xml
中沒有定義他,debug直接跳過了。 -
初始化監聽器
// LifecycleMBeanBase.java // MapperListener.java 中並無實現initInternal方法,則一直調用到超類的額該方法 @Override protected void initInternal() throws LifecycleException { if (oname == null) { mserver = Registry.getRegistry(null, null).getMBeanServer(); // 將oname 賦值爲Catalina:type=Mapper oname = register(this, getObjectNameKeyProperties()); } }
-
初始化連接器
@Override protected void initInternal() throws LifecycleException { // Initialize adapter adapter = new CoyoteAdapter(this); protocolHandler.setAdapter(adapter); if (service != null) { protocolHandler.setUtilityExecutor(service.getServer().getUtilityExecutor()); } try { // 對協議處理器進行初始化 protocolHandler.init(); } catch (Exception e) { throw new LifecycleException( sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e); } }
- 初始化協議處理器
// AbstractProtocol.java (Http11NioProtocol.java)
@Override
public void init() throws Exception {
// 初始化終點(端點)
String endpointName = getName();
endpoint.setName(endpointName.substring(1, endpointName.length()-1));
endpoint.setDomain(domain);
endpoint.init();
}
// 終(端)點初始化 --這一步,連接器就要讓終點去綁定端口了
// AbstractEndpoint.java
public final void init() throws Exception {
if (bindOnInit) {
bindWithCleanup();
bindState = BindState.BOUND_ON_INIT;
}
}
// NioEndpoint.java
protected void initServerSocket() throws Exception {
if (!getUseInheritedChannel()) {
serverSock = ServerSocketChannel.open();
socketProperties.setProperties(serverSock.socket());
InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
// 最終這一步去綁定了8080端口
serverSock.socket().bind(addr,getAcceptCount());
} else {
}
serverSock.configureBlocking(true); //mimic APR behavior
}
5. 迴歸Bootstrap
接着單步執行會發現它一層一層往外走,最終回到了Bootstrap
中,進入下一步啓動-start()
。
啓動-start
啓動過程通過反射調用了Catalina
的start
方法。
// Bootstrap.java
public void start() throws Exception {
Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
method.invoke(catalinaDaemon, (Object [])null);
}
// Catalina.java
public void start() {
// 獲取不到Server則調用之前的加載,到這裏他已經加載過了,不會進這個分支
if (getServer() == null) {
load();
}
// 正式調用Server的啓動生命週期
try {
getServer().start();
} catch (LifecycleException e) {
}
}
啓動步驟按順序分以下幾個:
1. Server
的啓動
可以發現它也是交由超類的final
start
進行實現,可想而知,所有容器的啓動,也都會進這個start
方法。
並且與load()
方法相似startInternal
都交由子類具體實現其內部邏輯
// LifecycleBase.java
@Override
public final synchronized void start() throws LifecycleException {
// ...
try {
setStateInternal(LifecycleState.STARTING_PREP, null, false);
// startInternal交由子類具體實現內部邏輯
startInternal();
} catch (Throwable t) {
}
}
具體進入startInternal
看實現,可以看到實現在StandardServer
中
// org.apache.catalina.core.StandardServer#startInternal
@Override
protected void startInternal() throws LifecycleException {
// 觸發生命週期的事件 -- START
fireLifecycleEvent(CONFIGURE_START_EVENT, null);
setState(LifecycleState.STARTING);
globalNamingResources.start();
// 對定義的Service進行start
synchronized (servicesLock) {
for (Service service : services) {
service.start();
}
}
}
2. Service
啓動
同理,service.start();
進入了超類的start()
方法,且最終調用自身StandardService
的startInternal
方法進行實現(下同之處自動省略)。
// org.apache.catalina.core.StandardService#startInternal
@Override
protected void startInternal() throws LifecycleException {
// 第一步啓動引擎
if (engine != null) {
synchronized (engine) {
engine.start();
}
}
// 第二步啓動執行器
synchronized (executors) {
for (Executor executor: executors) {
executor.start();
}
}
// 第三步啓動監聽器
mapperListener.start();
// 第四步啓動已經成功加載的連接器
// 那麼爲什麼會有失敗的呢,最直接的就是端口被佔用,無法綁定bind()
synchronized (connectorsLock) {
for (Connector connector: connectors) {
// If it has already failed, don't try and start it
if (connector.getState() != LifecycleState.FAILED) {
connector.start();
}
}
}
}
3. 啓動引擎、執行器、監聽器、連接器
-
啓動引擎
// org.apache.catalina.core.StandardEngine#startInternal @Override protected synchronized void startInternal() throws LifecycleException { // Standard container startup // 調用了超類[ContainerBase]中的startInternal super.startInternal(); } // org.apache.catalina.core.ContainerBase#startInternal @Override protected synchronized void startInternal() throws LifecycleException { // debug 發現進來爲null Cluster cluster = getClusterInternal(); if (cluster instanceof Lifecycle) { logger.info(String.format("cluster[%s] 開始執行生命週期之start()", cluster.getClusterName())); ((Lifecycle) cluster).start(); } // 會獲取到Load方法加載進來的`LockOutRealm` Realm realm = getRealmInternal(); if (realm instanceof Lifecycle) { logger.info(String.format("realm-屬於container[%s] 開始執行生命週期之start()", realm.getContainer().getName())); // 最終執行的爲org.apache.catalina.realm.CombinedRealm#startInternal ((Lifecycle) realm).start(); } // Start our child containers, if any // 最終找到了Engine下定義的Host [StandardHost] Container children[] = findChildren(); List<Future<Void>> results = new ArrayList<>(); for (Container child : children) { // 使用了load()時初始化的線程池 logger.info("開始使用線程池提交多線程去調用子Container[%s]的call方法初始化"); results.add(startStopExecutor.submit(new StartChild(child))); } MultiThrowable multiThrowable = null; // 使用Future#get進行阻塞,獲取Host的初始化結果 for (Future<Void> result : results) { try { result.get(); } catch (Throwable e) { log.error(sm.getString("containerBase.threadedStartFailed"), e); if (multiThrowable == null) { multiThrowable = new MultiThrowable(); } multiThrowable.add(e); } } if (multiThrowable != null) { throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"), multiThrowable.getThrowable()); } // Start the Valves in our pipeline (including the basic), if any if (pipeline instanceof Lifecycle) { logger.debug(String.format("開始管道pipeline[%s]的生命週期之start()", pipeline.getContainer().getName())); ((Lifecycle) pipeline).start(); } // 聲明週期改成啓動中 setState(LifecycleState.STARTING); // Start our thread if (backgroundProcessorDelay > 0) { monitorFuture = Container.getService(ContainerBase.this).getServer() .getUtilityExecutor().scheduleWithFixedDelay( new ContainerBackgroundProcessorMonitor(), 0, 60, TimeUnit.SECONDS); } }
-
啓動
Cluster
獲得對象爲null,無需啓動
-
啓動
Realm
有對象,跳過
-
啓動子容器
Host
根據上述代碼可知,它會被提交到線程池,進行多線程啓動,將會調用到線程的
call
方法// org.apache.catalina.core.ContainerBase.StartChild // 超類中的靜態內部類 private static class StartChild implements Callable<Void> { @Override public Void call() throws LifecycleException { // 調用子容器的start方法,在這步中,子容器爲`StandardHost` child.start(); //DEBUG: child: "StandardEngine[Catalina].StandardHost[localhost]" return null; } }
接着通過
StandardHost
的start
生命週期又進入startInternal
方法// org.apache.catalina.core.StandardHost#startInternal @Override protected synchronized void startInternal() throws LifecycleException { // 檢查管道中有沒有報錯 // 真正的啓動交由超類實現 super.startInternal(); } // 接着又交由到ContainerBase這個熟悉的超類進行實現,在啓動-start章節的3.1中已經貼出過,這裏不再詳細展示,主要就是多線程去啓動器子容器 -- 套娃模式的好處 // org.apache.catalina.core.ContainerBase#startInternal @Override protected synchronized void startInternal() throws LifecycleException { // Start our child containers, if any Container children[] = findChildren(); // 由於在server.xml中沒有在<Host>節點下再定義<Context>容器,所以這次children數組是空數組 List<Future<Void>> results = new ArrayList<>(); for (Container child : children) { logger.info("開始使用線程池提交多線程去調用子Container[%s]的call方法初始化"); results.add(startStopExecutor.submit(new StartChild(child))); } }
-
啓動管道
pipeline
跳過
-
-
啓動執行器
在執行完多線程對
Host
的啓動後,一路點擊Step Out
跳回到StandardService
中的startInternal
方法。若已經分不清層次可以在
StandardService
類中搜索下面這段代碼並打上斷點。// org.apache.catalina.core.StandardService#startInternal synchronized (executors) { for (Executor executor: executors) { executor.start(); } }
不過
executors
數組長度爲0,在這兒就跳過了。 -
啓動監聽器
// org.apache.catalina.mapper.MapperListener#startInternal @Override public void startInternal() throws LifecycleException { // 獲取引擎 Engine engine = service.getContainer(); // 添加監聽器進引擎 addListeners(engine); Container[] conHosts = engine.findChildren(); for (Container conHost : conHosts) { Host host = (Host) conHost; if (!LifecycleState.NEW.equals(host.getState())) { // Registering the host will register the context and wrappers registerHost(host); } } } // org.apache.catalina.mapper.MapperListener#addListeners private void addListeners(Container container) { // 把監聽器[回調接口]註冊進容器、生命週期、並遞歸對子容器進行註冊 container.addContainerListener(this); container.addLifecycleListener(this); for (Container child : container.findChildren()) { // 通過一共5層遞歸,觀察到層次如下 // StandardEngine[Catalina].StandardHost[localhost].StandardContext[].StandardWrapper[default] addListeners(child); } }
可以觀察到套娃一樣對子容器進行遞歸添加監聽器層次爲:
StandardEngine[Catalina].StandardHost[localhost].StandardContext[].StandardWrapper[default]
。 -
啓動連接器
可以在
org.apache.catalina.core.StandardService#startInternal
中搜索for (Connector connector: connectors)
打上斷點,快速從上一步的遞歸中出來,再進入connector.start();
。 ps: 這時候在windows平臺上遇到了端口被佔用,如何殺死佔用了8080端口的進程呢?
<!-- 先找出佔用8080端口的進程 --> netstat -ano| findstr "8080" <!-- 殺死佔用8080端口的進程[此處剛好爲7400] --> taskkill /f /pid 7400
// org.apache.catalina.connector.Connector#startInternal @Override protected void startInternal() throws LifecycleException { try { // 啓動協議處理器 protocolHandler.start(); } catch (Exception e) { } } // org.apache.coyote.AbstractProtocol#start @Override public void start() throws Exception { // 啓動端點 endpoint.start(); } // org.apache.tomcat.util.net.AbstractEndpoint#start public final void start() throws Exception { startInternal(); } // org.apache.tomcat.util.net.NioEndpoint#startInternal @Override public void startInternal() throws Exception { if (!running) { // 讀取配置 // Create worker collection if (getExecutor() == null) { // 初始化線程池,任務隊列 createExecutor(); } // 創建限制鎖 initializeConnectionLatch(); // 創建NIO的poller線程 poller = new Poller(); Thread pollerThread = new Thread(poller, getName() + "-ClientPoller"); pollerThread.setPriority(threadPriority); pollerThread.setDaemon(true); pollerThread.start(); // 真正開始接收NIO端口的請求 startAcceptorThread(); } } // org.apache.tomcat.util.net.AbstractEndpoint#startAcceptorThread protected void startAcceptorThread() { acceptor = new Acceptor<>(this); String threadName = getName() + "-Acceptor"; // http-nio-8080-Acceptor acceptor.setThreadName(threadName); Thread t = new Thread(acceptor, threadName); t.setPriority(getAcceptorThreadPriority()); t.setDaemon(getDaemon()); t.start(); // Thread[http-nio-8080-Acceptor,5,main] }
-
迴歸
BootStrap
一直
Step Out
回到BootStrap
中,完成啓動。
總結
- 加載(load)過程主要完成 配置讀取-實例化容器等組件、創建線程池、佔用監控端口。
- 啓動(start)過程主要完成 順序/多線程啓動各層容器、開始接收端口的請求數據。
收穫
- 組件模塊化,在超類中定義基本操作,在子類中定義具體的一部分的實現。架構脈絡清晰,層次分明,節省大量代碼量。
- 加載與啓動整體解耦分離,就不必在啓動過程中在意所需組件是否加載。有點像Spring的懶加載,讀取了Bean的定義,但不必去初始化,用到的時候纔去啓動(描述的不貼切)。