springboot之WebServerInitializedEvent事件

前言
請先閒暇時間閱讀文章springboot-tomcat服務啓動

  1. 從鏈接文章中,我們可以發現tomcat啓動之後,會發佈一個ServletWebServerInitializedEvent事件。
  2. 熟悉spring事件機制的朋友都會知道,事件event,和監聽器Listener是共同存在的,通過觀察者模式來完成spring的事件機制。
  3. 今天,我們就來聊聊這個事件,能涉及到哪些我們想知道的事情

ServletWebServerInitializedEvent

  1. 看源碼得知,就是WebServerInitializedEvent的子類

  2. 並且在ServletWebServerApplicationContext中,tomcat啓動之後,發佈事件

    @Override
     protected void finishRefresh() {
     	super.finishRefresh();
     	WebServer webServer = startWebServer();
     	if (webServer != null) {
     		publishEvent(new ServletWebServerInitializedEvent(webServer, this));
     	}
     }
    
  3. 獲取到真實的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的子類在這裏插入圖片描述

  1. 從圖中我們可以看出,有zookeepernacos作爲註冊中心的子類實現。
  2. 但是有的同學可能會說,之前常用的Eureak註冊中心,不存在嗎?當然不是,看如下
    在這裏插入圖片描述
  3. 從上圖可以得出,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獲取到了,條件滿足,去執行了註冊。
      各位可以思考,這樣是否有意義,讓我們猜測下當時作者開發的思路?
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章