我认为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便可

原文

相关文章
相关标签/搜索