Tomcat 的架構 (也叫作 Catalina),是一個精密的層級結構系統。java
/conf/server.xmlweb
<?xml version="1.0" encoding="UTF-8"?> <!-- Apache Tomcat v9.0.30 配置文件 --> <!-- Server 表明了一個 Tomcat 實例。 className 是 Tomcat 實體類,必須使用 org.apache.catalina.Server 的子類,默認 org.apache.catalina.core.StandardServer; shutdown 表明關閉的指令; address 表明關閉指令能夠的來源,默認只有本地的關閉指令 Tomcat 纔會採用; port 是 Tomcat 關閉自身的指令接收端口,能夠經過設置爲 -1 來禁止 --> <Server port="8005" shutdown="SHUTDOWN" address="localhost" className="org.apache.catalina.core.StandardServer" > <!-- 監聽器配置,監聽類必須是 org.apache.catalina.LifecycleListener 的子類, 這裏配置的類會根據本身監聽的生命週期事件,被相關的 Lifecycle 的子類存入 list 中,當發生了相關事件就執行回調方法 --> <!-- 用於在服務啓動的時候打印日誌的監聽器 --> <Listener className="org.apache.catalina.startup.VersionLoggerListener" /> <!-- 默認開啓 apr 監聽器 --> <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" /> <!-- 內存不夠的時候調用此監聽器,執行一次 full gc --> <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" /> <!-- JNDI 全局變量管理監聽器 --> <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" /> <!-- 因爲線程引用了 threadLocal 中的變量致使內存不夠的時候調用此監聽器,殺掉線程 --> <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" /> <!-- JNDI 全局資源配置, 此處爲一個 UserDatabase 對象,用來存儲 tomcat-users.xml 文件中的信息 --> <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 表明了一個 Tomcat 服務,一個 Server 能夠有多個 Service, 同一個 Server 下的 Service 的 name 必須不一樣, className 必須使用 org.apache.catalina.Service 的子類,默認 org.apache.catalina.core.StandardService --> <Service name="Catalina" className="org.apache.catalina.core.StandardService"> <!-- 鏈接池配置,能夠不作配置,會有一個默認的鏈接池。 namePrefix 線程名稱; maxThreads 最大線程數,默認 200; maxQueueSize 任務隊列最大值,默認 Integer.MAX; minSpareThreads 最小的活躍線程,默認 25; maxIdleTime 線程被銷燬以前的存活時間,默認 60000 ms(1 min); deamon 池內的線程是不是守護線程,默認 true --> <Executor name="tomcatThreadPool" namePrefix="catalina-exec-" maxThreads="200" minSpareThreads="25" maxQueueSize="100000000" deamon="true" maxIdleTime="60000"/> <!-- Connector 是相應用戶請求的主體,一種 Connector 用於表明一種用戶請求, 通俗來講 Connector 是 Container 的前置工做對象,能夠通俗理解爲 Request 和 Response。 port 監控的端口; protocol 使用的網絡 io 鏈接器; connectionTimeout 鏈接超時時間,單位毫秒; executor 配置鏈接池; sslEnabled 是否容許 https; maxThreads 最大鏈接數; redirectPort 用戶用 http 請求某個資源,而該資源自己又被設置了必需要 https 方式訪問,會自動重定向到這個端口 --> <!-- protocol: org.apache.coyote.http11.Http11NioProtocol http1.1 nio 鏈接器 org.apache.coyote.http11.Http11Nio2Protocol http1.1 aio 鏈接器 org.apache.coyote.http11.Http11AprProtocol http1.1 apr 鏈接器,須要操做系統其它支持 HTTP/1.1 http1.1 協議的自動選擇選項,有 apr 就使用 apr,沒有就使用 nio AJP/1.3 ajp1.3 協議的自動選擇選項,有 apr 就使用 apr,沒有就使用 nio --> <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" SSLEnabled="false" executor="tomcatThreadPool" maxThreads="150"> <!-- http/2 配置,讓這個 Connector 能夠兼顧解析 http/2 --> <!-- <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol"/> --> <!-- 配置 https 證書 --> <!-- <SSLHostConfig> <Certificate certificateKeyFile="conf/localhost-rsa-key.pem" certificateFile="conf/localhost-rsa-cert.pem" certificateChainFile="conf/localhost-rsa-chain.pem" type="RSA" /> </SSLHostConfig> --> </Connector> <!-- Engine 是 Servlet Container 的最高層級,一個 Service 中只能有一個 Engine。 name 名稱; defaultHost 默認的 host 地址 --> <Engine name="Catalina" defaultHost="localhost"> <!-- Realm 提供用戶名密碼的映射,不多使用到,不作展開 --> <Realm className="org.apache.catalina.realm.LockOutRealm"> <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/> </Realm> <!-- Host 虛擬主機,用以管理一個站點,一個站點能夠有多個子項目構成。 name 名稱,同一個 Engine 下的 Host 不要重複便可, className 必須是 org.apache.catalina.Engine 的子類,默認 org.apache.catalina.core.StandardEngine, appBase 站點下屬全部項目配置的目錄; autoDeploy 是否自動掃描 appBase 目錄下的全部的項目,若是配置爲 true 的話,就能夠無需配置 <Context></Context> 了; unpackWars 自動解壓 war 包 --> <Host name="localhost" appBase="webapps" className="org.apache.catalina.core.StandardEngine" 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" /> <!-- Context 表明一個在 host appBase 路徑下的 web 應用, 通常狀況下能夠不配置,由於 host 會去掃描 appBase 下的全部文件夾找 web.xml。 此處將 /webapps/example 路徑下的項目配置成根路由下的項目, 將 /webapps/host-manager 路徑下的項目配置成 /manager 路由下的項目,以此類推。 --> <Context docBase="examples" path="/" /> <Context docBase="host-manager" path="/manager" /> <Context docBase="ROOT" path="/root" /> </Host> </Engine> </Service> </Server>
該部分的解析使用 tomcat-embed-core 進行源碼觀察。spring
org.apache.catalina.Lifecycle 是一個接口,org.apache.catalina.util.LifecycleBase 實現了 Lifecycle 接口,而 Server / Service / Engine / Host / Context / Wrapper 等組件都繼承了 LifecycleBase,也就是說這些組件在發生生命週期事件的時候均可以被監聽器監控。apache
event 是 Lifecycle 定義的可以被監控的生命週期事件。數組
// 組件初始化即將開始 public static final String BEFORE_INIT_EVENT = "before_init"; // 組件初始化完成以後 public static final String AFTER_INIT_EVENT = "after_init"; // 組件啓動中 public static final String START_EVENT = "start"; // 組件即將啓動 public static final String BEFORE_START_EVENT = "before_start"; // 組件啓動完成 public static final String AFTER_START_EVENT = "after_start"; // 組件中止中 public static final String STOP_EVENT = "stop"; // 組件即將中止 public static final String BEFORE_STOP_EVENT = "before_stop"; // 組件中止完成 public static final String AFTER_STOP_EVENT = "after_stop"; // 組件即將被摧毀 public static final String AFTER_DESTROY_EVENT = "after_destroy"; // 組件摧毀完成 public static final String BEFORE_DESTROY_EVENT = "before_destroy"; // 組件的週期性事件 public static final String PERIODIC_EVENT = "periodic"; // 組件開始根據配置文件配置自身,在 before_start 以後,start 以前 public static final String CONFIGURE_START_EVENT = "configure_start"; // 組件配置結束,在 stop 以後,after_stop 以前 public static final String CONFIGURE_STOP_EVENT = "configure_stop";
組件狀態被定義在 org.apache.catalina.LifecycleState 中,是一個枚舉類。tomcat
public enum LifecycleState { // 新建 NEW(false, null), // 正在初始化 INITIALIZING(false, Lifecycle.BEFORE_INIT_EVENT), // 初始化完成 INITIALIZED(false, Lifecycle.AFTER_INIT_EVENT), // 準備開始組件 STARTING_PREP(false, Lifecycle.BEFORE_START_EVENT), // 正在執行 STARTING(true, Lifecycle.START_EVENT), // 執行結束了 STARTED(true, Lifecycle.AFTER_START_EVENT), // 準備中止組件 STOPPING_PREP(true, Lifecycle.BEFORE_STOP_EVENT), // 正在中止中 STOPPING(false, Lifecycle.STOP_EVENT), // 中止了 STOPPED(false, Lifecycle.AFTER_STOP_EVENT), // 正在被銷燬 DESTROYING(false, Lifecycle.BEFORE_DESTROY_EVENT), // 被銷燬以後 DESTROYED(false, Lifecycle.AFTER_DESTROY_EVENT), // 失敗 FAILED(false, null); private final boolean available; // 是否可控制,若是爲 false private final String lifecycleEvent; // 此狀態綁定的監控事件 // ... }
代碼內容很簡潔。服務器
LifecycleBase 是 Lifecycle 的基本實現類,以 init() 方法舉例:網絡
// LifecycleBase.class @Override public final synchronized void init() throws LifecycleException { // 調用 init 方法須要確保這個組件的狀態是 new,若是不是的話會拋出錯誤 if (!state.equals(LifecycleState.NEW)) { invalidTransition(Lifecycle.BEFORE_INIT_EVENT); } try { // before_init 監聽通知 setStateInternal(LifecycleState.INITIALIZING, null, false); // 真正的初始化邏輯 initInternal(); // after_init 監聽通知 setStateInternal(LifecycleState.INITIALIZED, null, false); } catch (Throwable t) { handleSubClassException(t, "lifecycleBase.initFail", toString()); } }
與監聽器進行交互的核心方法是 addLifecycleListener(...) 與 setStateInternal(...):session
// LifecycleBase.class @Override public void addLifecycleListener(LifecycleListener listener) { // lifecycleListeners 是一個存放監聽器的列表 lifecycleListeners.add(listener); } // LifecycleBase.class // 此方法用於通知全部的監聽器相關邏輯 private synchronized void setStateInternal( LifecycleState state, Object data, boolean check) throws LifecycleException { // 記錄日誌 if (log.isDebugEnabled()) { log.debug(sm.getString("lifecycleBase.setState", this, state)); } // 是否啓用狀態判斷 if (check) { // 傳入的狀態值的非空判斷,非法操做,通常不會出現 if (state == null) { invalidTransition("null"); return; } // 若是狀態是以下幾種的話,會拋出錯誤 if (!(state == LifecycleState.FAILED || (this.state == LifecycleState.STARTING_PREP && state == LifecycleState.STARTING) || (this.state == LifecycleState.STOPPING_PREP && state == LifecycleState.STOPPING) || (this.state == LifecycleState.FAILED && state == LifecycleState.STOPPING)) ) { invalidTransition(state.name()); } } // 更新狀態 this.state = state; String lifecycleEvent = state.getLifecycleEvent(); if (lifecycleEvent != null) { // 此處會遍歷監聽器 fireLifecycleEvent(lifecycleEvent, data); } } // LifecycleBase.class protected void fireLifecycleEvent(String type, Object data) { // 遍歷監聽器列表,執行全部監聽器的 lifecycleEvent(...) 方法 LifecycleEvent event = new LifecycleEvent(this, type, data); for (LifecycleListener listener : lifecycleListeners) { listener.lifecycleEvent(event); } }
全部實現了 org.apache.catalina.LifecycleListener 接口的類均可以是監聽器:架構
public interface LifecycleListener { public void lifecycleEvent(LifecycleEvent event); }
以最簡單的 org.apache.catalina.startup.VersionLoggerListener 爲例子:
// VersionLoggerListener.class @Override public void lifecycleEvent(LifecycleEvent event) { // Tomcat 中的 Listener 通常都使用 if 判斷進行事件的篩選 // Tomcat 沒有在接口層面做出更增強制的監聽邏輯判斷 if (Lifecycle.BEFORE_INIT_EVENT.equals(event.getType())) { // 打印日誌 log(); } }
Spring boot 中與此相關的配置比較多,只列舉部分筆者在項目中經常使用的部分。
server: # 端口 port: 8080 # http 協議頭的大小 max-http-header-size: 8KB # servlet 設置 servlet: session: # session 失效時常,默認 30min timeout: 30m # 是否持久化 session,持久化以後 session 不會由於服務的重啓而丟失 persistent: false # 若是設置爲須要持久化,那麼能夠指定存儲的目錄 # store-dir: classpath:session # 配置 Tomcat 相關的參數 tomcat: # 最大線程數 max-threads: 2 # 最大鏈接數,默認 200 max-connections: 10 # encode uri-encoding: UTF-8 # 鏈接超時時間 connection-timeout: 2m # 最大鏈接排隊數 accept-count: # 提交表單的最大大小 max-http-form-post-size: 2MB # 是否支持 http2 http2.enabled: false spring: # Spring 網絡配置 http: encoding: # 編碼格式 charset: UTF-8 enabled: true force: true # servlet 配置 servlet: # 文件上傳配置 multipart: # 是否支持文件上傳 enabled: true # 上傳的文件的最大值 max-file-size: 100MB # request 的最大值 max-request-size: 100MB
Spring boot 對內嵌服務器的運用,會使用 ServletWebServerFactory :
// 接口 public interface ServletWebServerFactory { WebServer getWebServer(ServletContextInitializer... initializers); }
它的具體實現類有:
JettyServletWebServerFactory - eclipse jetty 內嵌服務對象的工廠類 TomcatServletWebServerFactory - apache tomcat 內嵌服務對象的工廠類 UndertowServletWebServerFactory - jboss undertow 內嵌服務對象的工廠類
本例中因爲默認內嵌 Tomcat 服務包,因此 Spring boot 會使用 TomcatServletWebServerFactory:
// step 1 // TomcatServletWebServerFactory.class @Override public WebServer getWebServer(ServletContextInitializer... initializers) { if (this.disableMBeanRegistry) { Registry.disableRegistry(); } Tomcat tomcat = new Tomcat(); File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat"); // Tomcat 配置 baseDir,若是不配置的話會建立一個臨時目錄,用來存放一些 log 文件等臨時文件 tomcat.setBaseDir(baseDir.getAbsolutePath()); // 建立 Connector,並存入 protocol Connector connector = new Connector(this.protocol); connector.setThrowOnFailure(true); // 在 Tomcat 的 service 中存入 connector,因爲是內嵌的 Tomcat,因此 service 只容許有一個 tomcat.getService().addConnector(connector); // 根據配置對 connector 進行配置 customizeConnector(connector); // 本質上這行代碼和上述 tomcat.getService().addConnector(connector) 的功能是同樣的,不太理解爲何又從新 set 了一遍 tomcat.setConnector(connector); // 關閉 host 的自動掃描 tomcat.getHost().setAutoDeploy(false); // 配置 engine configureEngine(tomcat.getEngine()); // 添加一些默認的 connector,通常是空的 for (Connector additionalConnector : this.additionalTomcatConnectors) { tomcat.getService().addConnector(additionalConnector); } // ServletContextInitializer 用於封裝 servlet 的配置信息,並將 servlet 註冊到 context 上 // 此處會建立一個 context 對象,並註冊到 host 中 // 因爲 spring mvc 內部實際上只有一個 dispatcher servlet,因此此處的數組通常只有一個 prepareContext(tomcat.getHost(), initializers); // 用一個 TomcatWebServer 對象包裝原生的 Tomcat 對象 return getTomcatWebServer(tomcat); } // step 2 // TomcatServletWebServerFactory.class protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) { return new TomcatWebServer(tomcat, getPort() >= 0); } // step 3 // TomcatWebServer.class public TomcatWebServer(Tomcat tomcat, boolean autoStart) { Assert.notNull(tomcat, "Tomcat Server must not be null"); this.tomcat = tomcat; this.autoStart = autoStart; initialize(); } // step 4 // TomcatWebServer.class private void initialize() throws WebServerException { // 記錄日誌 logger.info("Tomcat initialized with port(s): " + getPortsDescription(false)); synchronized (this.monitor) { try { addInstanceIdToEngineName(); // 給 context 增長生命週期監聽器 Context context = findContext(); context.addLifecycleListener((event) -> { if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) { removeServiceConnectors(); } }); // 此處啓動 tomcat this.tomcat.start(); // 此處檢測一下是否啓動成功,若是失敗了就直接拋出錯誤 rethrowDeferredStartupExceptions(); try { ContextBindings.bindClassLoader( context, context.getNamingToken(), getClass().getClassLoader()); } catch (NamingException ex) { } // 啓動一條主線程 // 因爲 Tomcat 的全部現場都默認是守護線程,因此須要一條非守護線程來確保項目不退出 startDaemonAwaitThread(); } catch (Exception ex) { stopSilently(); destroySilently(); throw new WebServerException("Unable to start embedded Tomcat", ex); } } }