我认为SpringCloud中AbstractAutoServiceRegistration的“bug“

本文深入探讨了在使用Spring Cloud框架时,Zipkin-server与Nacos结合过程中遇到的服务注册问题。通过对比Eureka-client的顺利注册流程,揭示了在采用nacos-discovery时,Zipkin-server无法自动注册到Nacos-server的根本原因。文章详细分析了Spring Cloud服务注册机制,并提出了一种基于ApplicationReadyEvent的调整方案,成功解决了这一技术难题。
前话: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()实现服务的自动注册

@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()方法实现服务注册,但是这里就可能存在一个小小的问题了。

在创建SpringBoot Web Application时,有一段代码比较关键

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事件的下发

AnnotationConfigServletWebServerApplicationContext

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

public class ServletWebServerInitializedEvent extends WebServerInitializedEvent {
    ...
}
复制代码

AnnotationConfigReactiveWebServerApplicationContext

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

public class ReactiveWebServerInitializedEvent extends WebServerInitializedEvent {
    ...
}
复制代码

AnnotationConfigApplicationContext

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即可

原文

转载于:https://juejin.im/post/5cdabcda6fb9a032136fec3d

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值