參考github解決方案java
在解決Nacos
社區認領的issue——對接zipkin時,遇到了一個奇怪的問題,當採用github的解決方案,即採用eureka-client
時,zipkin-server可以自動註冊到Eureka Server
中,可是當採用nacos-discovery
時,卻怎麼也沒法實現zipkin-server
服務自動註冊到nacos-server
中,經過斷點調試以及參考Spring Cloud
相關源碼以及文檔,終於發現了問題所在git
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
對應着三種不一樣的類型,分別是AnnotationConfigServletWebServerApplicationContext
、AnnotationConfigReactiveWebServerApplicationContext
以及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);
語句,此時獲取到的Context
爲AnnotationConfigApplicationContext
,而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
便可