【源码解析】SpringBoot使用Nacos配置中心和使用 @NacosValue 进行热更新

SpringBoot使用Nacos

引入依赖

<dependency>
	<groupId>com.alibaba.boot</groupId>
	<artifactId>nacos-config-spring-boot-starter</artifactId>
	<version>0.2.12</version>
</dependency>

增加本地配置

nacos:
  config:
    server-addr: 127.0.0.1:8848
    bootstrap:
      enable: true
      log:
        enable: true
    data-id: cls-service
    type: yaml
    auto-refresh: true # 开启自动刷新

增加远程配置

cls:
  cname: chars11

使用@NacosValue

@RestController
@RequestMapping("test")
public class NacosValueController {

    @NacosValue(value = "${cls.cname}", autoRefreshed = true)
    private String userName;

    @GetMapping("t1")
    public String getUserName() {
        return userName;
    }

}

NacosValue原理解析

NacosConfigEnvironmentProcessor

EnvironmentPostProcessorApplicationListener实现了SmartApplicationListener,系统启动的时候,会执行EnvironmentPostProcessorApplicationListener#onApplicationEvent。获取容器中所有的EnvironmentPostProcessor,执行对应的postProcessEnvironment

    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ApplicationEnvironmentPreparedEvent) {
            this.onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent)event);
        }

        if (event instanceof ApplicationPreparedEvent) {
            this.onApplicationPreparedEvent();
        }

        if (event instanceof ApplicationFailedEvent) {
            this.onApplicationFailedEvent();
        }

    }

    private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
        ConfigurableEnvironment environment = event.getEnvironment();
        SpringApplication application = event.getSpringApplication();
        Iterator var4 = this.getEnvironmentPostProcessors(application.getResourceLoader(), event.getBootstrapContext()).iterator();

        while(var4.hasNext()) {
            EnvironmentPostProcessor postProcessor = (EnvironmentPostProcessor)var4.next();
            postProcessor.postProcessEnvironment(environment, application);
        }

    }

NacosConfigEnvironmentProcessor#postProcessEnvironment,如果nacos.config.bootstrap.logEnable=true,开启预加载。系统添加了NacosConfigApplicationContextInitializer初始化器。

	public void postProcessEnvironment(ConfigurableEnvironment environment,
			SpringApplication application) {
		application.addInitializers(new NacosConfigApplicationContextInitializer(this));
		nacosConfigProperties = NacosConfigPropertiesUtils
				.buildNacosConfigProperties(environment);
		if (enable()) {
			System.out.println(
					"[Nacos Config Boot] : The preload log configuration is enabled");
			loadConfig(environment);
			NacosConfigLoader nacosConfigLoader = NacosConfigLoaderFactory.getSingleton(nacosConfigProperties, environment, builder);
			LogAutoFreshProcess.build(environment, nacosConfigProperties, nacosConfigLoader, builder).process();
		}
	}

	boolean enable() {
		return nacosConfigProperties != null
				&& nacosConfigProperties.getBootstrap().isLogEnable();
	}

NacosConfigEnvironmentProcessor#loadConfig,调用NacosConfigLoader进行加载。

	private void loadConfig(ConfigurableEnvironment environment) {
		NacosConfigLoader configLoader = new NacosConfigLoader(nacosConfigProperties,
				environment, builder);
		configLoader.loadConfig();
		// set defer NacosPropertySource
		deferPropertySources.addAll(configLoader.getNacosPropertySources());
	}

NacosConfigLoader#loadConfig,调用了NacosUtils.getContent来获取远程配置的内容。

	public void loadConfig() {
		MutablePropertySources mutablePropertySources = environment.getPropertySources();
		List<NacosPropertySource> sources = reqGlobalNacosConfig(globalProperties,
				nacosConfigProperties.getType());
		for (NacosConfigProperties.Config config : nacosConfigProperties.getExtConfig()) {
			List<NacosPropertySource> elements = reqSubNacosConfig(config,
					globalProperties, config.getType());
			sources.addAll(elements);
		}
		if (nacosConfigProperties.isRemoteFirst()) {
			for (ListIterator<NacosPropertySource> itr = sources.listIterator(sources.size()); itr.hasPrevious();) {
				mutablePropertySources.addAfter(
						StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, itr.previous());
			}
		} else {
			for (NacosPropertySource propertySource : sources) {
				mutablePropertySources.addLast(propertySource);
			}
		}
	}

	private List<NacosPropertySource> reqGlobalNacosConfig(Properties globalProperties,
			ConfigType type) {
		List<String> dataIds = new ArrayList<>();
		// Loads all data-id information into the list in the list
		if (!StringUtils.hasLength(nacosConfigProperties.getDataId())) {
			final String ids = environment
					.resolvePlaceholders(nacosConfigProperties.getDataIds());
			dataIds.addAll(Arrays.asList(ids.split(",")));
		}
		else {
			dataIds.add(nacosConfigProperties.getDataId());
		}
		final String groupName = environment
				.resolvePlaceholders(nacosConfigProperties.getGroup());
		final boolean isAutoRefresh = nacosConfigProperties.isAutoRefresh();
		return new ArrayList<>(Arrays.asList(reqNacosConfig(globalProperties,
				dataIds.toArray(new String[0]), groupName, type, isAutoRefresh)));
	}

	private NacosPropertySource[] reqNacosConfig(Properties configProperties,
			String[] dataIds, String groupId, ConfigType type, boolean isAutoRefresh) {
		final NacosPropertySource[] propertySources = new NacosPropertySource[dataIds.length];
		for (int i = 0; i < dataIds.length; i++) {
			if (!StringUtils.hasLength(dataIds[i])) {
				continue;
			}
			// Remove excess Spaces
			final String dataId = environment.resolvePlaceholders(dataIds[i].trim());
			final String config = NacosUtils.getContent(builder.apply(configProperties),
					dataId, groupId);
			final NacosPropertySource nacosPropertySource = new NacosPropertySource(
					dataId, groupId,
					buildDefaultPropertySourceName(dataId, groupId, configProperties),
					config, type.getType());
			nacosPropertySource.setDataId(dataId);
			nacosPropertySource.setType(type.getType());
			nacosPropertySource.setGroupId(groupId);
			nacosPropertySource.setAutoRefreshed(isAutoRefresh);
			logger.info("load config from nacos, data-id is : {}, group is : {}",
					nacosPropertySource.getDataId(), nacosPropertySource.getGroupId());
			propertySources[i] = nacosPropertySource;
			DeferNacosPropertySource defer = new DeferNacosPropertySource(
					nacosPropertySource, configProperties, environment);
			nacosPropertySources.add(defer);
		}
		return propertySources;
	}

NacosUtils#getContent,获取远程配置,调用核心类ConfigService

	public static String getContent(ConfigService configService, String dataId,
			String groupId) {
		String content = null;
		try {
			content = configService.getConfig(dataId, groupId, DEFAULT_TIMEOUT);
		}
		catch (NacosException e) {
			if (logger.isErrorEnabled()) {
				logger.error("Can't get content from dataId : " + dataId + " , groupId : "
						+ groupId, e);
			}
		}
		return content;
	}

NacosConfigApplicationContextInitializer

NacosConfigApplicationContextInitializer初始化,会添加自动刷新的监听器。

public class NacosConfigApplicationContextInitializer
		implements ApplicationContextInitializer<ConfigurableApplicationContext> {
	// ...

	@Override
	public void initialize(ConfigurableApplicationContext context) {
		singleton.setApplicationContext(context);
		environment = context.getEnvironment();
		nacosConfigProperties = NacosConfigPropertiesUtils
				.buildNacosConfigProperties(environment);
		final NacosConfigLoader configLoader = NacosConfigLoaderFactory.getSingleton(
				nacosConfigProperties, environment, builder);
		if (!enable()) {
			logger.info("[Nacos Config Boot] : The preload configuration is not enabled");
		}
		else {

			// If it opens the log level loading directly will cache
			// DeferNacosPropertySource release

			if (processor.enable()) {
				processor.publishDeferService(context);
				configLoader
						.addListenerIfAutoRefreshed(processor.getDeferPropertySources());
			}
			else {
				configLoader.loadConfig();
				configLoader.addListenerIfAutoRefreshed();
			}
		}

		final ConfigurableListableBeanFactory factory = context.getBeanFactory();
		if (!factory
				.containsSingleton(NacosBeanUtils.GLOBAL_NACOS_PROPERTIES_BEAN_NAME)) {
			factory.registerSingleton(NacosBeanUtils.GLOBAL_NACOS_PROPERTIES_BEAN_NAME,
					configLoader.getGlobalProperties());
		}
	}

	private boolean enable() {
		return processor.enable() || nacosConfigProperties.getBootstrap().isEnable();
	}

}

NacosConfigLoader#addListenerIfAutoRefreshed(List<NacosConfigLoader.DeferNacosPropertySource>),调用NacosPropertySourcePostProcessor添加监听器。

	public void addListenerIfAutoRefreshed(
			final List<DeferNacosPropertySource> deferNacosPropertySources) {
		for (DeferNacosPropertySource deferNacosPropertySource : deferNacosPropertySources) {
			NacosPropertySourcePostProcessor.addListenerIfAutoRefreshed(
					deferNacosPropertySource.getNacosPropertySource(),
					deferNacosPropertySource.getProperties(),
					deferNacosPropertySource.getEnvironment());
		}
	}

NacosPropertySourcePostProcessor#addListenerIfAutoRefreshed,当监听到配置变化,直接替换env的配置数据。

	public static void addListenerIfAutoRefreshed(
			final NacosPropertySource nacosPropertySource, final Properties properties,
			final ConfigurableEnvironment environment) {

		if (!nacosPropertySource.isAutoRefreshed()) { // Disable Auto-Refreshed
			return;
		}

		final String dataId = nacosPropertySource.getDataId();
		final String groupId = nacosPropertySource.getGroupId();
		final String type = nacosPropertySource.getType();
		final NacosServiceFactory nacosServiceFactory = getNacosServiceFactoryBean(
				beanFactory);

		try {

			ConfigService configService = nacosServiceFactory
					.createConfigService(properties);

			Listener listener = new AbstractListener() {

				@Override
				public void receiveConfigInfo(String config) {
					String name = nacosPropertySource.getName();
					NacosPropertySource newNacosPropertySource = new NacosPropertySource(
							dataId, groupId, name, config, type);
					newNacosPropertySource.copy(nacosPropertySource);
					MutablePropertySources propertySources = environment
							.getPropertySources();
					// replace NacosPropertySource
					propertySources.replace(name, newNacosPropertySource);
				}
			};

			if (configService instanceof EventPublishingConfigService) {
				((EventPublishingConfigService) configService).addListener(dataId,
						groupId, type, listener);
			}
			else {
				configService.addListener(dataId, groupId, listener);
			}

		}
		catch (NacosException e) {
			throw new RuntimeException(
					"ConfigService can't add Listener with properties : " + properties,
					e);
		}
	}

使用DelegatingEventPublishingListener对监听器进行包装。

	public void addListener(String dataId, String group, String type, Listener listener)
			throws NacosException {
		Listener listenerAdapter = new DelegatingEventPublishingListener(configService,
				dataId, group, type, applicationEventPublisher, executor, listener);
		addListener(dataId, group, listenerAdapter);
	}

DelegatingEventPublishingListener#receiveConfigInfo,当接收到事件后,会调用内置的监听器处理,以及发布NacosConfigReceivedEvent事件。

	@Override
	public void receiveConfigInfo(String content) {
		onReceived(content);
		publishEvent(content);
	}

	private void publishEvent(String content) {
		NacosConfigReceivedEvent event = new NacosConfigReceivedEvent(configService,
				dataId, groupId, content, configType);
		applicationEventPublisher.publishEvent(event);
	}

	private void onReceived(String content) {
		delegate.receiveConfigInfo(content);
	}

NacosValueAnnotationBeanPostProcessor

NacosValueAnnotationBeanPostProcessor实现了BeanPostProcessor,启动的时候执行NacosValueAnnotationBeanPostProcessor#postProcessBeforeInitialization。将带有@NacosValue注解的属性和方法加入到一个Map中。

	@Override
	public Object postProcessBeforeInitialization(Object bean, final String beanName)
			throws BeansException {

		doWithFields(bean, beanName);

		doWithMethods(bean, beanName);

		return super.postProcessBeforeInitialization(bean, beanName);
	}

	private void doWithFields(final Object bean, final String beanName) {
		ReflectionUtils.doWithFields(bean.getClass(),
				new ReflectionUtils.FieldCallback() {
					@Override
					public void doWith(Field field) throws IllegalArgumentException {
						NacosValue annotation = getAnnotation(field, NacosValue.class);
						doWithAnnotation(beanName, bean, annotation, field.getModifiers(),
								null, field);
					}
				});
	}

	private void doWithMethods(final Object bean, final String beanName) {
		ReflectionUtils.doWithMethods(bean.getClass(),
				new ReflectionUtils.MethodCallback() {
					@Override
					public void doWith(Method method) throws IllegalArgumentException {
						NacosValue annotation = getAnnotation(method, NacosValue.class);
						doWithAnnotation(beanName, bean, annotation,
								method.getModifiers(), method, null);
					}
				});
	}

	private void doWithAnnotation(String beanName, Object bean, NacosValue annotation,
			int modifiers, Method method, Field field) {
		if (annotation != null) {
			if (Modifier.isStatic(modifiers)) {
				return;
			}

			if (annotation.autoRefreshed()) {
				String placeholder = resolvePlaceholder(annotation.value());

				if (placeholder == null) {
					return;
				}

				NacosValueTarget nacosValueTarget = new NacosValueTarget(bean, beanName,
						method, field, annotation.value());
				put2ListMap(placeholderNacosValueTargetMap, placeholder,
						nacosValueTarget);
			}
		}
	}

	private <K, V> void put2ListMap(Map<K, List<V>> map, K key, V value) {
		List<V> valueList = map.get(key);
		if (valueList == null) {
			valueList = new ArrayList<V>();
		}
		valueList.add(value);
		map.put(key, valueList);
	}

该类同样还实现了ApplicationListener<NacosConfigReceivedEvent>,当监听到NacosConfigReceivedEvent,通过反射用新的值替换Bean中的属性值。

	@Override
	public void onApplicationEvent(NacosConfigReceivedEvent event) {
		// In to this event receiver, the environment has been updated the
		// latest configuration information, pull directly from the environment
		// fix issue #142
		for (Map.Entry<String, List<NacosValueTarget>> entry : placeholderNacosValueTargetMap
				.entrySet()) {
			String key = environment.resolvePlaceholders(entry.getKey());
			String newValue = environment.getProperty(key);

			if (newValue == null) {
				continue;
			}
			List<NacosValueTarget> beanPropertyList = entry.getValue();
			for (NacosValueTarget target : beanPropertyList) {
				String md5String = MD5Utils.md5Hex(newValue, "UTF-8");
				boolean isUpdate = !target.lastMD5.equals(md5String);
				if (isUpdate) {
					target.updateLastMD5(md5String);
					Object evaluatedValue = resolveNotifyValue(target.nacosValueExpr, key, newValue);
					if (target.method == null) {
						setField(target, evaluatedValue);
					}
					else {
						setMethod(target, evaluatedValue);
					}
				}
			}
		}
	}


	private void setMethod(NacosValueTarget nacosValueTarget, Object propertyValue) {
		Method method = nacosValueTarget.method;
		ReflectionUtils.makeAccessible(method);
		try {
			method.invoke(nacosValueTarget.bean,
					convertIfNecessary(method, propertyValue));

			if (logger.isDebugEnabled()) {
				logger.debug("Update value with {} (method) in {} (bean) with {}",
						method.getName(), nacosValueTarget.beanName, propertyValue);
			}
		}
		catch (Throwable e) {
			if (logger.isErrorEnabled()) {
				logger.error("Can't update value with " + method.getName()
						+ " (method) in " + nacosValueTarget.beanName + " (bean)", e);
			}
		}
	}

	private void setField(final NacosValueTarget nacosValueTarget,
			final Object propertyValue) {
		final Object bean = nacosValueTarget.bean;

		Field field = nacosValueTarget.field;

		String fieldName = field.getName();

		try {
			ReflectionUtils.makeAccessible(field);
			field.set(bean, convertIfNecessary(field, propertyValue));

			if (logger.isDebugEnabled()) {
				logger.debug("Update value of the {}" + " (field) in {} (bean) with {}",
						fieldName, nacosValueTarget.beanName, propertyValue);
			}
		}
		catch (Throwable e) {
			if (logger.isErrorEnabled()) {
				logger.error("Can't update value of the " + fieldName + " (field) in "
						+ nacosValueTarget.beanName + " (bean)", e);
			}
		}
	}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值