spring cloud alibaba整合 nacos config源码简析

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;
}
}

猜你喜欢

转载自blog.csdn.net/mapleleafforest/article/details/111933917