前言
請先閒暇時間閱讀文章springboot-tomcat服務啓動
- 從鏈接文章中,我們可以發現tomcat啓動之後,會發佈一個ServletWebServerInitializedEvent事件。
- 熟悉spring事件機制的朋友都會知道,事件event,和監聽器Listener是共同存在的,通過觀察者模式來完成spring的事件機制。
- 今天,我們就來聊聊這個事件,能涉及到哪些我們想知道的事情
ServletWebServerInitializedEvent
-
看源碼得知,就是WebServerInitializedEvent的子類
-
並且在ServletWebServerApplicationContext中,tomcat啓動之後,發佈事件
@Override protected void finishRefresh() { super.finishRefresh(); WebServer webServer = startWebServer(); if (webServer != null) { publishEvent(new ServletWebServerInitializedEvent(webServer, this)); } }
-
獲取到真實的tomcat端口
WebServer webServer; int port = webServer.getPort();
服務啓動之後的webServer對象,從該對象#getPort()可以直接獲取真實啓動的端口。
(可以從ServletWebServerInitializedEvent事件中獲取到該對象)
事件對應的監聽器
1. ServerPortInfoApplicationContextInitializer
是springboot2.0.0之後,添加一個類
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
applicationContext.addApplicationListener(this);
}
@Override
public void onApplicationEvent(WebServerInitializedEvent event) {
String propertyName = "local." + getName(event.getApplicationContext()) + ".port";
setPortProperty(event.getApplicationContext(), propertyName, event.getWebServer().getPort());
}
- 從類名可以推測出,該類是一個ApplicationContextInitializer上下文初始化器,(spring或者springboot在創建出AppcationContext對象之後,都會去獲取到容器中所有的ApplicationContextInitializer,然後進行初始化)
- 從該類實現邏輯看,就是將自身添加到applicationContext的監聽器中,然後監聽WebServerInitializedEvent 事件。並且將實際的tomcat端口存儲到local.server.port屬性中
2. AbstractAutoServiceRegistration
**可以看出該類,自身是一個WebServerInitializedEvent監聽器**
該類是spring-cloud-common中的一個類,作用是: 將自身服務註冊到註冊中心的Abstract模板類。
從以下代碼可以實現功能:就是獲取WebServer的端口,然後進行服務註冊
@Override
@SuppressWarnings("deprecation")
public void onApplicationEvent(WebServerInitializedEvent event) {
bind(event);
}
@Deprecated
public void bind(WebServerInitializedEvent event) {
ApplicationContext context = event.getApplicationContext();
if (context instanceof ConfigurableWebServerApplicationContext) {
if ("management".equals(((ConfigurableWebServerApplicationContext) context)
.getServerNamespace())) {
return;
}
}
this.port.compareAndSet(0, event.getWebServer().getPort());
this.start();
}
public void start() {
if (!isEnabled()) {
if (logger.isDebugEnabled()) {
logger.debug("Discovery Lifecycle disabled. Not starting");
}
return;
}
// only initialize if nonSecurePort is greater than 0 and it isn't already running
// because of containerPortInitializer below
if (!this.running.get()) {
this.context.publishEvent(
new InstancePreRegisteredEvent(this, getRegistration()));
register();
if (shouldRegisterManagement()) {
registerManagement();
}
this.context.publishEvent(
new InstanceRegisteredEvent<>(this, getConfiguration()));
this.running.compareAndSet(false, true);
}
}
AbstractAutoServiceRegistration的子類
- 從圖中我們可以看出,有zookeeper和nacos作爲註冊中心的子類實現。
- 但是有的同學可能會說,之前常用的Eureak註冊中心,不存在嗎?當然不是,看如下
- 從上圖可以得出,Eureka並沒有繼承spring-cloud-common的服務註冊的規範,而是自己搞了一遍。
熟悉spring的朋友都知道,上圖標記的兩個接口,SmartLifecycle和SmartApplicationListener都是spring中的擁有生命週期的接口
所以要麼是SmartLifecycle中的start進行註冊,還是在SmartApplicationListener監聽器中進行註冊。不過從EurekaAutoServiceRegistration中得出,其實該類是做了服務註冊兩次調用
@Override
public void start() {
// only set the port if the nonSecurePort or securePort is 0 and this.port != 0
if (this.port.get() != 0) {
if (this.registration.getNonSecurePort() == 0) {
this.registration.setNonSecurePort(this.port.get());
}
if (this.registration.getSecurePort() == 0 && this.registration.isSecure()) {
this.registration.setSecurePort(this.port.get());
}
}
// only initialize if nonSecurePort is greater than 0 and it isn't already running
// because of containerPortInitializer below
if (!this.running.get() && this.registration.getNonSecurePort() > 0) {
this.serviceRegistry.register(this.registration);
this.context.publishEvent(new InstanceRegisteredEvent<>(this,
this.registration.getInstanceConfig()));
this.running.set(true);
}
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof WebServerInitializedEvent) {
onApplicationEvent((WebServerInitializedEvent) event);
}
else if (event instanceof ContextClosedEvent) {
onApplicationEvent((ContextClosedEvent) event);
}
}
public void onApplicationEvent(WebServerInitializedEvent event) {
// TODO: take SSL into account
String contextName = event.getApplicationContext().getServerNamespace();
if (contextName == null || !contextName.equals("management")) {
int localPort = event.getWebServer().getPort();
if (this.port.get() == 0) {
log.info("Updating port to " + localPort);
this.port.compareAndSet(0, localPort);
start();
}
}
}
- 得出結論:
- EurekaAutoServiceRegistration所做之事和AbstractAutoServiceRegistration基本一模一樣,
到底是誰先,誰後,誰抄襲了誰,不得而知,留給你們自己思考? - 另一點就是EurekaAutoServiceRegistration服務註冊借用spring聲明週期調用兩次,
只不過第一次port爲空,條件不滿足。
第二次事件監聽去註冊,此時port獲取到了,條件滿足,去執行了註冊。
各位可以思考,這樣是否有意義,讓我們猜測下當時作者開發的思路?
- EurekaAutoServiceRegistration所做之事和AbstractAutoServiceRegistration基本一模一樣,