欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

spring cloud alibaba整合 nacos config源码简析

程序员文章站 2022-03-19 16:12:13
在sprig boot 中,引入`nacos-config-spring-boot-starter`后,通常是使用`@NacosValue`注解来获取nacos server 中的配置以及自动刷新.在spring cloud alibaba中,引入`spring-cloud-starter-alibaba-nacos-config`则使用了更优雅的方式支持spring 原生注解@Value来获取参数,通过 Spring Cloud 原生注解 @RefreshScope 实现配置自动更新...

1. 前言

本文基于Spring Cloud Version:Spring Cloud Hoxton.SR8,Spring Boot Version:2.3.2.RELEASE,Nacos Version:1.3.3

在sprig boot 中,引入nacos-config-spring-boot-starter后,通常是使用@NacosValue注解来获取nacos server 中的配置以及自动刷新,官网中的实例如下:

@Controller
@RequestMapping("config")
public class ConfigController {

    @NacosValue(value = "${useLocalCache:false}", autoRefreshed = true)
    private boolean useLocalCache;

    @RequestMapping(value = "/get", method = GET)
    @ResponseBody
    public boolean get() {
        return useLocalCache;
    }
}

在spring cloud alibaba中,引入spring-cloud-starter-alibaba-nacos-config则使用了更优雅的方式支持spring 原生注解@Value来获取参数,通过 Spring Cloud 原生注解 @RefreshScope 实现配置自动更新,官网中的实例如下:

@RestController
@RequestMapping("/config")
@RefreshScope
public class ConfigController {

    @Value("${useLocalCache:false}")
    private boolean useLocalCache;

    @RequestMapping("/get")
    public boolean get() {
        return useLocalCache;
    }
}

spring-cloud-starter-alibaba-nacos-config 是把nacos server所有的配置都放在Environment中,下面简要分析一下源码看看是怎么整合

2. spring cloud alibaba 初始化将参数配置追加到Environment

spring cloud alibaba是借助PropertySourceBootstrapConfiguration这个ApplicationContextInitializer,通过自定义类实现PropertySourceLocator接口来扩展,其原理详见《spring cloud 中Environment自定义扩展配置源码简析和实例》
NacosConfigBootstrapConfiguration中创建了NacosConfigManagerNacosPropertySourceLocator两个关键类:

@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.cloud.nacos.config.enabled", matchIfMissing = true)
public class NacosConfigBootstrapConfiguration {

	@Bean
	@ConditionalOnMissingBean
	public NacosConfigProperties nacosConfigProperties() {
		return new NacosConfigProperties();
	}

	@Bean
	@ConditionalOnMissingBean
	public NacosConfigManager nacosConfigManager(
			NacosConfigProperties nacosConfigProperties) {
		return new NacosConfigManager(nacosConfigProperties);
	}

	@Bean
	public NacosPropertySourceLocator nacosPropertySourceLocator(
			NacosConfigManager nacosConfigManager) {
		return new NacosPropertySourceLocator(nacosConfigManager);
	}

}

jar中springfactory.properties的配置:

org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.alibaba.cloud.nacos.NacosConfigBootstrapConfiguration

NacosPropertySourceLocator的主要源码如下:

public class NacosPropertySourceLocator implements PropertySourceLocator {
@Override
	public PropertySource<?> locate(Environment env) {
		nacosConfigProperties.setEnvironment(env);
		ConfigService configService = nacosConfigManager.getConfigService();

		if (null == configService) {
			log.warn("no instance of config service found, can't load config from nacos");
			return null;
		}
		long timeout = nacosConfigProperties.getTimeout();
		nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService,
				timeout);
		String name = nacosConfigProperties.getName();

		String dataIdPrefix = nacosConfigProperties.getPrefix();
		if (StringUtils.isEmpty(dataIdPrefix)) {
			dataIdPrefix = name;
		}

		if (StringUtils.isEmpty(dataIdPrefix)) {
			dataIdPrefix = env.getProperty("spring.application.name");
		}

		CompositePropertySource composite = new CompositePropertySource(
				NACOS_PROPERTY_SOURCE_NAME);
        // 优先级由底到高
		loadSharedConfiguration(composite);//加载共享配置
		loadExtConfiguration(composite);//加载扩展配置
		loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);//加载本应用配置

		return composite;
	}
    
}

三类配置的加载源码可以自行看看,其主要逻辑就是使用ConfigService从nacos server获取参数配置,并将参数追加到PropertySource中。

3. nacos server配置修改动态更新至Environment中

spring cloud alibaba,通过NacosContextRefresher这个ApplicationListener来实现,分析详见代码和注解:

public class NacosContextRefresher
	implements ApplicationListener<ApplicationReadyEvent>, ApplicationContextAware {
	 public void onApplicationEvent(ApplicationReadyEvent event) {
	    // many Spring context
	    // 1.spring 上下文启动后触发
	    if (this.ready.compareAndSet(false, true)) {
		    this.registerNacosListenersForApplications();
	    }
    }
    private void registerNacosListenersForApplications() {
		if (isRefreshEnabled()) {
		    //2.遍历所有的NacosPropertySource,注册监听器
			for (NacosPropertySource propertySource : NacosPropertySourceRepository
					.getAll()) {
				if (!propertySource.isRefreshable()) {
					continue;
				}
				String dataId = propertySource.getDataId();
				registerNacosListener(propertySource.getGroup(), dataId);
			}
		}
    }
    private void registerNacosListener(final String groupKey, final String dataKey) {
		String key = NacosPropertySourceRepository.getMapKey(dataKey, groupKey);
		Listener listener = listenerMap.computeIfAbsent(key,
				lst -> new AbstractSharedListener() {
					@Override
					public void innerReceive(String dataId, String group,
							String configInfo) {
						refreshCountIncrement();
						nacosRefreshHistory.addRefreshRecord(dataId, group, configInfo);
						// todo feature: support single refresh for listening
						//3.当参数有变化时 分发RefreshEvent事件
						applicationContext.publishEvent(
								new RefreshEvent(this, null, "Refresh Nacos config"));
						if (log.isDebugEnabled()) {
							log.debug(String.format(
									"Refresh Nacos config group=%s,dataId=%s,configInfo=%s",
									group, dataId, configInfo));
						}
					}
				});
		try {
			configService.addListener(dataKey, groupKey, listener);
		}
		catch (NacosException e) {
			log.warn(String.format(
					"register fail for nacos listener ,dataId=[%s],group=[%s]", dataKey,
					groupKey), e);
		}
	}
	
}

如上注解,其核心逻辑如下:

1.在spring上下文启动完毕后,通过NacosContextRefresher这个ApplicationListener出发监听器注册逻辑

2.来给nacos config中的各个data-id配置增加监听器。

3.当有配置发生变化时,nacos的监听器分发一个会在applicationContext中分发一个RefreshEvent事件

4.RefreshEventListener中监听到RefreshEvent事件后,调用其refreshEnvironment方法,其核心逻辑就是:构建一个的SpringApplicationBuilder对象,用该对象build一个精简版的applicationContext,并传入当前上下文的Environment,以及BootstrapApplicationListenerConfigFileApplicationListener这两个关键创建Environment的类

5.最后调用这个精简版的applicationContext.run(),将evnrioment进行更新,其实就是又走了一遍章节 2. spring cloud alibaba 初始化将参数配置追加到Environment 中的逻辑。
RefreshEventListener源码分析:

public class RefreshEventListener implements SmartApplicationListener {
	@Override
	public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
		return ApplicationReadyEvent.class.isAssignableFrom(eventType)
				|| RefreshEvent.class.isAssignableFrom(eventType);
	}

	@Override
	public void onApplicationEvent(ApplicationEvent event) {
		if (event instanceof ApplicationReadyEvent) {
			handle((ApplicationReadyEvent) event);
		}
		else if (event instanceof RefreshEvent) {
			handle((RefreshEvent) event);
		}
	}

	public void handle(RefreshEvent event) {
		if (this.ready.get()) { // don't handle events before app is ready
			log.debug("Event received " + event.getEventDesc());
			//刷新入口
			Set<String> keys = this.refresh.refresh();
			log.info("Refresh keys changed: " + keys);
		}
	}

}

断点进入ContextRefresher:refresh方法

public class ContextRefresher {
public synchronized Set<String> refresh() {
	Set<String> keys = refreshEnvironment();
	this.scope.refreshAll();
	return keys;
}

public synchronized Set<String> refreshEnvironment() {
	Map<String, Object> before = extract(
			this.context.getEnvironment().getPropertySources());
	addConfigFilesToEnvironment();
	Set<String> keys = changes(before,
			extract(this.context.getEnvironment().getPropertySources())).keySet();
	this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
	return keys;
}

ConfigurableApplicationContext addConfigFilesToEnvironment() {
	ConfigurableApplicationContext capture = null;
	try {
        StandardEnvironment environment = copyEnvironment(
    			this.context.getEnvironment());
    	//4.构建一个的`SpringApplicationBuilder`对象
    	SpringApplicationBuilder builder = new SpringApplicationBuilder(Empty.class)
    			.bannerMode(Mode.OFF).web(WebApplicationType.NONE)
    			.environment(environment);
    	// Just the listeners that affect the environment (e.g. excluding logging
    	// listener because it has side effects)
    	//4.传入当前上下文的Environment,以及`BootstrapApplicationListener`,`ConfigFileApplicationListener`这两个关键创建Environment的类
    	builder.application()
    			.setListeners(Arrays.asList(new BootstrapApplicationListener(),
    					new ConfigFileApplicationListener()));
    	//5.最后调用这个精简版的applicationContext.run(),将evnrioment进行更新,其实就是又走了一遍章节 2. spring cloud alibaba 初始化将参数配置追加到Environment 中的逻辑。
    	capture = builder.run();
    	if (environment.getPropertySources().contains(REFRESH_ARGS_PROPERTY_SOURCE)) {
    		environment.getPropertySources().remove(REFRESH_ARGS_PROPERTY_SOURCE);
    	}
    	MutablePropertySources target = this.context.getEnvironment()
    			.getPropertySources();
    	String targetName = null;
    	for (PropertySource<?> source : environment.getPropertySources()) {
    		String name = source.getName();
    		if (target.contains(name)) {
    			targetName = name;
    		}
    		if (!this.standardSources.contains(name)) {
    			if (target.contains(name)) {
    				target.replace(name, source);
    			}
    			else {
    				if (targetName != null) {
    					target.addAfter(targetName, source);
    					// update targetName to preserve ordering
    					targetName = name;
    				}
    				else {
    					// targetName was null so we are at the start of the list
    					target.addFirst(source);
    					targetName = name;
    				}
    			}
    		}
    	}
	}
	return capture;
}
}

本文地址:https://blog.csdn.net/mapleleafforest/article/details/111933917