好了,今天我們繼續分析 tomcat 源碼, 這是第六篇了, 上一篇我們一邊 debug 一邊研究了 tomcat 的類加載體系, 我覺得效果還不錯, 樓主感覺對 tomcat 的類加載體系的理解又加深了一點. 所以, 我們今天還是按照之前的方式來繼續看源碼, 一邊 debug, 一邊看, 今天我們分析的是tomcat 中2個非常重要的組件-------生命周期和容器. tomcat 龐大的架構, 他是如何管理每個對象的呢? 我們在深入理解 Tomcat (二) 從宏觀上理解 Tomcat 組件及架構中說過一段:
基于JMX Tomcat會為每個組件進行注冊過程,通過Registry管理起來,而Registry是基于JMX來實現的,因此在看組件的init和start過程實際上就是初始化MBean和觸發MBean的start方法,會大量看到形如: Registry.getRegistry(null, null).invoke(mbeans, "init", false); Registry.getRegistry(null, null).invoke(mbeans, "start", false); 這樣的代碼,這實際上就是通過JMX管理各種組件的行為和生命期。
當時大家可能還不是很理解這句話, 覺得這是在扯淡, 聽不懂. 好吧, 今天我們就用代碼說話, 看看 JMX 到底怎么管理 tomcat 的 組件.
1. 什么是 JMX?
我們之前說過:
JMX 即 Java Management Extensions(JMX 規范), 是用來對 tomcat 進行管理的. tomcat 中的實現是 commons modeler 庫, Catalina 使用這個庫來編寫托管 Bean 的工作. 托管 Bean 就是用來管理 Catalina 中其他對象的 Bean.
簡單來說: 就是一個可以為Java應用程序或系統植入遠程管理功能的框架。
既然是框架, 肯定要有架構圖:
這里對上圖中三個分層進行介紹:
Probe Level:負責資源的檢測(獲取信息),包含MBeans,通常也叫做Instrumentation Level。MX管理構件(MBean)分為四種形式,分別是標準管理構件(Standard MBean)、動態管理構件(Dynamic MBean)、開放管理構件(Open Mbean)和模型管理構件(Model MBean)。
Agent Level:即MBeanServer,是JMX的核心,負責連接Mbeans和應用程序。
Remote Management Level:通過connectors和adaptors來遠程操作MBeanServer,常用的控制臺,例如JConsole、VisualVM(等會我們就要用這個)等。
2. 我們看看生命周期組件接口是如何設計的:
這是一張 IDEA 生成的簡單的 StandardHost(Host 容器的標準實現) 的 UML類圖, 基本上, tomcat 的容器類都是這樣的繼承結構.
因此我們就可以直接看下面這張圖:
這里對上圖中涉及的主要類作個簡單介紹:
Lifecycle:定義了容器生命周期、容器狀態轉換及容器狀態遷移事件的監聽器注冊和移除等主要接口;
LifecycleBase:作為Lifecycle接口的抽象實現類,運用抽象模板模式將所有容器的生命周期及狀態轉換銜接起來,此外還提供了生成LifecycleEvent事件的接口;
LifecycleSupport:提供有關LifecycleEvent事件的監聽器注冊、移除,并且使用經典的監聽器模式,實現事件生成后觸達監聽器的實現;
MBeanRegistration:Java JMX框架提供的注冊MBean的接口,引入此接口是為了便于使用JMX提供的管理功能;
LifecycleMBeanBase:Tomcat提供的對MBeanRegistration的抽象實現類,運用抽象模板模式將所有容器統一注冊到JMX;
此外,ContainerBase、StandardServer、StandardService、WebappLoader、Connector、StandardContext、StandardEngine、StandardHost、StandardWrapper等容器都繼承了LifecycleMBeanBase,因此這些容器都具有了同樣的生命周期并可以通過JMX進行管理。
3. 再看看我們的容器結構
我們之前說, 如果從宏觀上講容器, 畫畫圖, 講講就好了, 就可以在腦海里形成一個映象, 今天, 我們要好好的講講容器, 從代碼層面去理解他們. 這樣一來, 也順便把我們的容器組件也講了, 等于又講了生命周期組件, 還有容器組件. 一舉兩得. 哈哈哈. 好吧, 不扯了, 回來, 我們繼續講容器. 還是先來一張圖吧:
從上圖中我們可以看到: StandardServer、StandardService、Connector、StandardContext這些容器,彼此之間都有父子關系,每個容器都可能包含零個或者多個子容器,這些子容器可能存在不同類型或者相同類型的多個. 所以他們都包含的關系, 如果讓你來設計這些容器的生命周期, 你會用什么設計模式呢?
4. 容器初始化, 開始 Debug
首先我們啟動 main 方法:
public static void main(String args[]) {
try {
// 命令
String command = "start";
// 如果命令行中輸入了參數
if (args.length > 0) {
// 命令 = 最后一個命令
command = args[args.length - 1];
}
// 如果命令是啟動
if (command.equals("startd")) {
args[args.length - 1] = "start";
daemon.load(args);
daemon.start();
}
// 如果命令是停止了
else if (command.equals("stopd")) {
args[args.length - 1] = "stop";
daemon.stop();
}
// 如果命令是啟動
else if (command.equals("start")) {
daemon.setAwait(true);// bootstrap 和 Catalina 一脈相連, 這里設置, 方法內部設置 Catalina 實例setAwait方法
daemon.load(args);// args 為 空,方法內部調用 Catalina 的 load 方法.
daemon.start();// 相同, 反射調用 Catalina 的 start 方法 ,至此,啟動結束
} else if (command.equals("stop")) {
daemon.stopServer(args);
} else if (command.equals("configtest")) {
daemon.load(args);
if (null==daemon.getServer()) {
System.exit(1);
}
System.exit(0);
} else {
log.warn("Bootstrap: command \"" + command + "\" does not exist.");
}
} catch (Throwable t) {
// Unwrap the Exception for clearer error reporting
if (t instanceof InvocationTargetException &&
t.getCause() != null) {
t = t.getCause();
}
handleThrowable(t);
t.printStackTrace();
System.exit(1);
}
}
熟悉這個方法或者看過我們上篇文章的同學都知道, 我已經把類加載那部分代碼去除了, 因為我們今天不研究類加載. 所以 ,我們看邏輯, 首先, 判斷命令是什么, 我們現在的命令肯定是 start 啊, 所以進入 else if 塊, 調用 load 方法 , 進入 load 方法, 可以看到, 該方法實際上就是 Catalina 類的 load 方法, 那么我們進入 Catalina 類的 load 方法看看(方法很長, 樓主去除了和今天的模塊無關的代碼):
public void load() {
// Start the new server
try {
getServer().init();
} catch (LifecycleException e) {
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
throw new java.lang.Error(e);
} else {
log.error("Catalina.start", e);
}
}
}
可以看到, 這里有一個我們今天感興趣的方法, getServer.init(), 這個方法看名字是啟動 Server 的初始化, 而 Server 是我們上面圖中最外層的容器. 因此, 我們去看看該方法, 也就是LifecycleBase.init() 方法. 該方法是一個模板方法, 只是定義了一個算法的骨架, 將一些細節算法延遲到了子類中. 看, 我們又學到了一個設計模式. 我們看看該方法:
@Override
public final synchronized void init() throws LifecycleException {
// 1
if (!state.equals(LifecycleState.NEW)) {
invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
}
// 2
setStateInternal(LifecycleState.INITIALIZING, null, false);
try {
// 模板方法
/**
* 采用模板方法模式來對所有支持生命周期管理的組件的生命周期各個階段進行了總體管理,
* 每個需要生命周期管理的組件只需要繼承這個基類,
* 然后覆蓋對應的鉤子方法即可完成相應的聲明周期階段的管理工作
*/
initInternal();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
setStateInternal(LifecycleState.FAILED, null, false);
throw new LifecycleException(
sm.getString("lifecycleBase.initFail",toString()), t);
}
// 3
setStateInternal(LifecycleState.INITIALIZED, null, false);
}
我們看看該方法, 這應該就是容器啟動的邏輯了, 先前我們定義了那么多狀態, 現在用上了. 首先判斷該方法的狀態, 如果不是 NEW, 則拋出異常, 否則則設置狀態為 INITIALIZING, 然后調用一個抽象方法 initInternal , 該方法由子類具體實現. 執行完則修改狀態為 INITIALIZED. 這里應該是使用了狀態模式. 依賴狀態時,同步該方法, 防止并發錯誤. tomcat 可以的.
5. 那么我們來看看 StandardServer 是如何實現 initInternal 方法的:
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
// Register global String cache
// Note although the cache is global, if there are multiple Servers
// present in the JVM (may happen when embedding) then the same cache
// will be registered under multiple names
onameStringCache = register(new StringCache(), "type=StringCache");
// Register the MBeanFactory
MBeanFactory factory = new MBeanFactory();
factory.setContainer(this);
onameMBeanFactory = register(factory, "type=MBeanFactory");
// Register the naming resources
globalNamingResources.init();
// Populate the extension validator with JARs from common and shared
// class loaders
if (getCatalina() != null) {
ClassLoader cl = getCatalina().getParentClassLoader();
// Walk the class loader hierarchy. Stop at the system class loader.
// This will add the shared (if present) and common class loaders
while (cl != null && cl != ClassLoader.getSystemClassLoader()) {
if (cl instanceof URLClassLoader) {
URL[] urls = ((URLClassLoader) cl).getURLs();
for (URL url : urls) {
if (url.getProtocol().equals("file")) {
try {
File f = new File (url.toURI());
if (f.isFile() &&
f.getName().endsWith(".jar")) {
ExtensionValidator.addSystemResource(f);
}
} catch (URISyntaxException e) {
// Ignore
} catch (IOException e) {
// Ignore
}
}
}
}
cl = cl.getParent();
}
}
// Initialize our defined Services
for (int i = 0; i < services.length; i++) {
services[i].init();
}
}
6. LifecycleMBeanBase.initInternal() 實現
首先調用父類的 super.initInternal() 方法,此initInternal方法用于將容器托管到JMX,便于運維管理:
@Override
protected void initInternal() throws LifecycleException {
// If oname is not null then registration has already happened via
// preRegister().
if (oname == null) {
mserver = Registry.getRegistry(null, null).getMBeanServer();
oname = register(this, getObjectNameKeyProperties());
}
}
7. LifecycleMBeanBase.register 方法實現
LifecycleMBeanBase 會調用自身的 register 方法, 該方法會將容器注冊到 MBeanServer:
protected final ObjectName register(Object obj,
String objectNameKeyProperties) {
// Construct an object name with the right domain
StringBuilder name = new StringBuilder(getDomain());
name.append(':');
name.append(objectNameKeyProperties);
ObjectName on = null;
try {
on = new ObjectName(name.toString());
// 核心實現:registerComponent
Registry.getRegistry(null, null).registerComponent(obj, on, null);
} catch (MalformedObjectNameException e) {
log.warn(sm.getString("lifecycleMBeanBase.registerFail", obj, name),
e);
} catch (Exception e) {
log.warn(sm.getString("lifecycleMBeanBase.registerFail", obj, name),
e);
}
return on;
}
該方法內部核心方法是 Registry. registerComponent, 在org.apache.catalina.util 包下, 我們看看該方法實現。
8. Registry.registerComponent 方法實現
public void registerComponent(Object bean, ObjectName oname, String type)
throws Exception
{
if( log.isDebugEnabled() ) {
log.debug( "Managed= "+ oname);
}
if( bean ==null ) {
log.error("Null component " + oname );
return;
}
try {
if( type==null ) {
type=bean.getClass().getName();
}
ManagedBean managed = findManagedBean(bean.getClass(), type);
// The real mbean is created and registered
DynamicMBean mbean = managed.createMBean(bean);
if( getMBeanServer().isRegistered( oname )) {
if( log.isDebugEnabled()) {
log.debug("Unregistering existing component " + oname );
}
getMBeanServer().unregisterMBean( oname );
}
getMBeanServer().registerMBean( mbean, oname);
} catch( Exception ex) {
log.error("Error registering " + oname, ex );
throw ex;
}
}
該方法會為當前容器創建一個 DynamicMBean , 并且注冊到MBeanServer。調用 MBeanServer.registerMBean() 方法。而 MBeanServer 在 javax.management, 也就是 rt.jar 中,該包由 java 的 BootStrap 啟動類加載器加載。
注冊進MBeanServer 的 key 是什么呢? 相信細心的同學會注意到 LifecycleMBeanBase.getObjectNameKeyProperties 和 LifecycleMBeanBase.getDomain 方法 和
LifecycleMBeanBase.getDomainInternal 方法, 這三個方法由具體子類實現,會生成一個專屬于容器的key。格式為:Catalina:type=Server
, 這是 Server 容器的 key, debug 可以看出來:
9. JMX 如何管理 組件?
至此, 我們已經知道 Tomcat 是如何將容器注冊到 MBeanServer 中的。 那么注冊到 MBeanServer 中后是什么樣子呢?我們看圖:
這是 JDK 自帶的 JvisualVM 工具, 添加了 MBeans 插件, 就可以遠程操作容器中的 組件了, 可以看到 Service 容器暴漏了很多接口, 用于運維人員管理容器和組件。
10. 回到 StandardServer.initInternal 方法
好了, 我們回到 StandardServer.initInternal 方法, 回到我們夢最開始的地方,super.initInternal
方法就是將容器注冊到 JMX 中。 那下面的邏輯是做什么的呢? 在執行完父類的 super.initInternal 的方法后, 該方法又注冊個兩個 JMX 。然后尋啟動子容器的 init 方法:
// Initialize our defined Services
for (int i = 0; i < services.length; i++) {
services[i].init();
}
而子容器的 init 方法和 Server 的 init 方法的邏輯基本一致,所以不再贅述。
11. 執行完 getServer().init()
方法后做什么------容器啟動
Bootstrap 的 load 方法調用了 Catalina 的 load 方法 ,該方法調用了Server 的init方法,執行完初始化過程,當然就是要執行 start 方法了, 那么如何執行呢?
Bootstrap 調用了 Catalina 的 start 方法,該方法也同樣執行了 Server 的 start 方法, 該方法的具體實現也在LifecycleBase 中:
@Override
public final synchronized void start() throws LifecycleException {
if (LifecycleState.STARTING_PREP.equals(state) ||
LifecycleState.STARTING.equals(state) ||
LifecycleState.STARTED.equals(state)) {
if (log.isDebugEnabled()) {
Exception e = new LifecycleException();
log.debug(sm.getString("lifecycleBase.alreadyStarted",
toString()), e);
} else if (log.isInfoEnabled()) {
log.info(sm.getString("lifecycleBase.alreadyStarted",
toString()));
}
return;
}
if (state.equals(LifecycleState.NEW)) {
init();
} else if (state.equals(LifecycleState.FAILED)){
stop();
} else if (!state.equals(LifecycleState.INITIALIZED) &&
!state.equals(LifecycleState.STOPPED)) {
invalidTransition(Lifecycle.BEFORE_START_EVENT);
}
setStateInternal(LifecycleState.STARTING_PREP, null, false);
try {
startInternal();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
setStateInternal(LifecycleState.FAILED, null, false);
throw new LifecycleException(
sm.getString("lifecycleBase.startFail",toString()), t);
}
if (state.equals(LifecycleState.FAILED) ||
state.equals(LifecycleState.MUST_STOP)) {
stop();
} else {
// Shouldn't be necessary but acts as a check that sub-classes are
// doing what they are supposed to.
if (!state.equals(LifecycleState.STARTING)) {
invalidTransition(Lifecycle.AFTER_START_EVENT);
}
setStateInternal(LifecycleState.STARTED, null, false);
}
}
12. StandardServer.startInternal 啟動容器方法實現
可以看到該方法對狀態的判斷特別多,我們感興趣的是 try 塊中的 startInternal() 方法, 同樣, 該方法也是個抽象方法,需要子類去具體實現自己的啟動邏輯。我們看看Server 的啟動邏輯:
@Override
protected void startInternal() throws LifecycleException {
fireLifecycleEvent(CONFIGURE_START_EVENT, null);
setState(LifecycleState.STARTING);//將自身狀態更改為LifecycleState.STARTING;
globalNamingResources.start();
// Start our defined Services
synchronized (services) {
for (int i = 0; i < services.length; i++) {
services[i].start();// 啟動所有子容器
}
}
}
13. LifecycleSupport.fireLifecycleEvent()方法實現
該方法首先執行自己的fireLifecycleEvent
方法, 該方法內部是LifecycleSupport.fireLifecycleEvent()方法, 我們進入該方法看個究竟:
public void fireLifecycleEvent(String type, Object data) {
// 事件監聽,觀察者模式的另一種方式
LifecycleEvent event = new LifecycleEvent(lifecycle, type, data);
LifecycleListener interested[] = listeners;// 監聽器數組 關注 事件(啟動或者關閉事件)
// 循環通知所有生命周期時間偵聽器????
for (int i = 0; i < interested.length; i++)
// 每個監聽器都有自己的邏輯
interested[i].lifecycleEvent(event);
}
該方法很簡單, 樓主沒有刪一行代碼, 首先, 創建一個事件對象, 然通知所有的監聽器發生了該事件.并做響應.那么 Server 有哪些監聽器呢?
這些監聽器將根據這個事件的類型做出響應.
14. 我們回到 startInternal 方法, 啟動所有容器
事件監聽結束之后, 調用 setState(LifecycleState.STARTING);
表明狀態時開始中, 并且循環啟動子容器, 這里的 Server 啟動的是Service 數組, 循環啟動他們的 start 方法. 以此類推. 啟動所有的容器:
synchronized (services) {
for (int i = 0; i < services.length; i++) {
services[i].start();// 啟動所有子容器
}
}
現在我們關注的是Server 容器, 因此, Server 會啟動 services 數組中的所有 Service 組件。該方法就完成了通知所有監聽器發送了啟動事件,然后使用觀察者模式,啟動所有子容器,然后子容器繼續遞歸啟動。最后修改自己的狀態并告訴監聽器。
15. 總結
其實樓主在啃代碼最深的感觸就是設計模式, 真的很牛逼,不知道同學們發現了幾個設計模式,樓主在本篇文章中發現了狀態模式, 觀察者模式,模板方法, 事件監聽,代理模式。真的收益良多。不枉樓主每天看代碼。
還有就是對 Tomcat 生命周期組件的總結。我們再看看我們的類圖:
tomcat 的主要容器都繼承了 LifecycleMBeanBase 抽象類,該類中關于 init 和 start 兩個模板方法。定義了主要算法骨架,而方法中又都有抽象方法,需要子類自己去實現。而 LifecycleBase 中又定義了如何實現事件監聽代理,LifecycleBase 依賴 LifecycleSupport 去完成真正的事件監聽。對了,監聽器是如何添加進 LifecycleSupport 的呢?LifecycleSupport 中含有
addLifecycleListener 方法。該方法也是被LifecycleBase代理的。而每個容器下面的子容器也是使用相同的邏輯完成初始化和啟動。父容器和子容器使用了聚合的方式設計。
可以說, tomcat的容器的生命周期組件設計是非常牛逼的。我們閱讀源碼不僅能了解他的設計原理,也能同大師交流,學會更多。
好了, 今天的深入理解 Tomcat(六)源碼剖析Tomcat 啟動過程----生命周期和容器組件就到這里,謝謝大家的耐心,再這個世界,耐心和堅持是無比珍貴的。尤其是程序員。
good luck !!!!