参考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
便可