我認爲SpringCloud中AbstractAutoServiceRegistration的「bug「

前話:zipkin-server與nacos結合時的坑

參考github解決方案java

在解決Nacos社區認領的issue——對接zipkin時,遇到了一個奇怪的問題,當採用github的解決方案,即採用eureka-client時,zipkin-server可以自動註冊到Eureka Server中,可是當採用nacos-discovery時,卻怎麼也沒法實現zipkin-server服務自動註冊到nacos-server中,經過斷點調試以及參考Spring Cloud相關源碼以及文檔,終於發現了問題所在git

Spring-Cloud認爲服務自動註冊的時機

SpringCloud自己認爲服務的註冊時機,應該是WebServerInitializedEvent事件發生後,進行服務的自動註冊,由於在接收到此事件時,會下發bind(Event)操做,由start()函數內部調用register()實現服務的自動註冊github

@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);
	}
}
複製代碼

存在的問題

由上面的代碼可知,SpringCloud自己對於服務註冊的時機,是在發生WebServerInitializedEvent事件以後,纔會去調用相應的register()方法實現服務註冊,可是這裏就可能存在一個小小的問題了。web

在建立SpringBoot Web Application時,有一段代碼比較關鍵spring

protected ConfigurableApplicationContext createApplicationContext() {
	Class<?> contextClass = this.applicationContextClass;
	if (contextClass == null) {
		try {
			switch (this.webApplicationType) {
			case SERVLET:
				contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
				break;
			case REACTIVE:
				contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
				break;
			default:
				contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
			}
		}
		catch (ClassNotFoundException ex) {
			throw new IllegalStateException(
					"Unable create a default ApplicationContext, "
							+ "please specify an ApplicationContextClass",
					ex);
		}
	}
	return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
複製代碼

所以,這裏的contextClass對應着三種不一樣的類型,分別是AnnotationConfigServletWebServerApplicationContextAnnotationConfigReactiveWebServerApplicationContext以及AnnotationConfigApplicationContext,而這三個中,只有前面兩個Context會觸發WebServerInitializedEvent事件的下發apache

AnnotationConfigServletWebServerApplicationContextjava-web

@Override
protected void finishRefresh() {
	super.finishRefresh();
	WebServer webServer = startWebServer();
	if (webServer != null) {
		publishEvent(new ServletWebServerInitializedEvent(webServer, this));
	}
}

public class ServletWebServerInitializedEvent extends WebServerInitializedEvent {
    ...
}
複製代碼

AnnotationConfigReactiveWebServerApplicationContextapp

@Override
protected void finishRefresh() {
	super.finishRefresh();
	WebServer webServer = startReactiveWebServer();
	if (webServer != null) {
		publishEvent(new ReactiveWebServerInitializedEvent(webServer, this));
	}
}

public class ReactiveWebServerInitializedEvent extends WebServerInitializedEvent {
    ...
}
複製代碼

AnnotationConfigApplicationContextide

protected void finishRefresh() {
	// Clear context-level resource caches (such as ASM metadata from scanning).
	clearResourceCaches();

	// Initialize lifecycle processor for this context.
	initLifecycleProcessor();

	// Propagate refresh to lifecycle processor first.
	getLifecycleProcessor().onRefresh();

	// Publish the final event.
	publishEvent(new ContextRefreshedEvent(this));

	// Participate in LiveBeansView MBean, if active.
	LiveBeansView.registerApplicationContext(this);
}

public class ContextRefreshedEvent extends ApplicationContextEvent {
    ...
}
複製代碼

所以,若是設置了spring.main.web-application-type=none時,createApplicationContext代碼中的switch分支就會執行contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);語句,此時獲取到的ContextAnnotationConfigApplicationContext,而AnnotationConfigApplicationContext調用的finishRefresh方法所下發的事件ContextRefreshedEvent是沒有繼承WebServerInitializedEvent的,在這種狀況下,SpringCloud就沒法執行服務自動註冊函數

進行調整

調整的方法很簡單,因爲SpringBoot採用事件機制,所以當整個SpringBoot Application程序初始化完畢,執行listeners.running(context)時,會觸發ApplicationReadyEvent事件

SpringApplication

public ConfigurableApplicationContext run(String... args) {
	StopWatch stopWatch = new StopWatch();
	stopWatch.start();
	...

	try {
		listeners.running(context);
	}
	catch (Throwable ex) {
		handleRunFailure(context, ex, exceptionReporters, null);
		throw new IllegalStateException(ex);
	}
	return context;
}
複製代碼

EventPublishingRunListener

@Override
public void running(ConfigurableApplicationContext context) {
	context.publishEvent(
				new ApplicationReadyEvent(this.application, this.args, context));
}
複製代碼

所以,只須要改動以下代碼便可

org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration

public abstract class AbstractAutoServiceRegistration<R extends Registration> implements AutoServiceRegistration, ApplicationContextAware, ApplicationListener<ApplicationReadyEvent> {
    ...
}
複製代碼

更改事件監聽類型爲ApplicationReadyEvent便可

原文

相關文章
相關標籤/搜索