前话:zipkin-server与nacos结合时的坑 参考github解决方案
在解决Nacos
的issue——对接zipkin时,遇到了一个奇怪的问题,当采用github的解决方案,即采用eureka-client
时,zipkin-server能够自动注册到Eureka Server
中,但是当采用nacos-discovery
时,却怎么也无法实现zipkin-server
服务自动注册到nacos-server
中,通过断点调试以及参考Spring Cloud
相关源码以及文档,终于发现了问题所在
Spring-Cloud认为服务自动注册的时机 SpringCloud
本身认为服务的注册时机,应该是WebServerInitializedEvent
事件发生后,进行服务的自动注册,因为在接收到此事件时,会下发bind(Event)
操作,由start()
函数内部调用register()
实现服务的自动注册
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 @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 ; } 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()
方法实现服务注册,但是这里就可能存在一个小小的问题了。
在创建SpringBoot Web Application
时,有一段代码比较关键
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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
事件的下发
AnnotationConfigServletWebServerApplicationContext
1 2 3 4 5 6 7 8 9 10 11 12 @Override protected void finishRefresh () { super .finishRefresh(); WebServer webServer = startWebServer(); if (webServer != null ) { publishEvent(new ServletWebServerInitializedEvent(webServer, this )); } } public class ServletWebServerInitializedEvent extends WebServerInitializedEvent { ... }
AnnotationConfigReactiveWebServerApplicationContext
1 2 3 4 5 6 7 8 9 10 11 12 @Override protected void finishRefresh () { super .finishRefresh(); WebServer webServer = startReactiveWebServer(); if (webServer != null ) { publishEvent(new ReactiveWebServerInitializedEvent(webServer, this )); } } public class ReactiveWebServerInitializedEvent extends WebServerInitializedEvent { ... }
AnnotationConfigApplicationContext
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 protected void finishRefresh () { clearResourceCaches(); initLifecycleProcessor(); getLifecycleProcessor().onRefresh(); publishEvent(new ContextRefreshedEvent(this )); 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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
1 2 3 4 5 @Override public void running (ConfigurableApplicationContext context) { context.publishEvent( new ApplicationReadyEvent(this .application, this .args, context)); }
因此,只需要改动如下代码即可
org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration
1 2 3 4 5 public abstract class AbstractAutoServiceRegistration <R extends Registration > implements AutoServiceRegistration , ApplicationContextAware , ApplicationListener <ApplicationReadyEvent > { ... }
更改事件监听类型为ApplicationReadyEvent
即可