SpringCloud-Nacos配置中心——配置获取

王守钰 2021-11-11 15:11:41

spring.factories

org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.alibaba.cloud.nacos.NacosConfigBootstrapConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alibaba.cloud.nacos.NacosConfigAutoConfiguration,\
com.alibaba.cloud.nacos.endpoint.NacosConfigEndpointAutoConfiguration
org.springframework.boot.diagnostics.FailureAnalyzer=\
com.alibaba.cloud.nacos.diagnostics.analyzer.NacosConnectionFailureAnalyzer
org.springframework.boot.env.PropertySourceLoader=\
com.alibaba.cloud.nacos.parser.NacosJsonPropertySourceLoader,\
com.alibaba.cloud.nacos.parser.NacosXmlPropertySourceLoader
org.springframework.context.ApplicationListener=\
com.alibaba.cloud.nacos.logging.NacosLoggingListener

NacosConfigBootstrapConfiguration

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

}

NacosConfigBootstrapConfiguration判断配置${spring.cloud.nacos.config.enabled}是否为false,不为false的话会启动配置中心。实例化NacosConfigBootstrapConfiguration的时候,会进行创建出NacosConfigPropertiesNacosConfigManagerNacosPropertySourceLocator对象信息。

NacosConfigProperties

@ConfigurationProperties(NacosConfigProperties.PREFIX)
public class NacosConfigProperties {

	/**
	 * Prefix of {@link NacosConfigProperties}.
	 */
	public static final String PREFIX = "spring.cloud.nacos.config";

	/**
	 * COMMAS , .
	 */
	public static final String COMMAS = ",";

	/**
	 * SEPARATOR , .
	 */
	public static final String SEPARATOR = "[,]";

	private static final Pattern PATTERN = Pattern.compile("-(\\w)");

	private static final Logger log = LoggerFactory
			.getLogger(NacosConfigProperties.class);

	@Autowired
	@JsonIgnore
	private Environment environment;

	@PostConstruct
	public void init() {
		this.overrideFromEnv();
	}

	private void overrideFromEnv() {
		if (StringUtils.isEmpty(this.getServerAddr())) {
			String serverAddr = environment
					.resolvePlaceholders("${spring.cloud.nacos.config.server-addr:}");
			if (StringUtils.isEmpty(serverAddr)) {
				serverAddr = environment.resolvePlaceholders(
						"${spring.cloud.nacos.server-addr:localhost:8848}");
			}
			this.setServerAddr(serverAddr);
		}
		if (StringUtils.isEmpty(this.getUsername())) {
			this.setUsername(
					environment.resolvePlaceholders("${spring.cloud.nacos.username:}"));
		}
		if (StringUtils.isEmpty(this.getPassword())) {
			this.setPassword(
					environment.resolvePlaceholders("${spring.cloud.nacos.password:}"));
		}
	}

	/**
	 * nacos config server address.
	 */
	private String serverAddr;

	/**
	 * the nacos authentication username.
	 */
	private String username;

	/**
	 * the nacos authentication password.
	 */
	private String password;

	/**
	 * encode for nacos config content.
	 */
	private String encode;

	/**
	 * nacos config group, group is config data meta info.
	 */
	private String group = "DEFAULT_GROUP";

	/**
	 * nacos config dataId prefix.
	 */
	private String prefix;

	/**
	 * the suffix of nacos config dataId, also the file extension of config content.
	 */
	private String fileExtension = "properties";

	/**
	 * timeout for get config from nacos.
	 */
	private int timeout = 3000;

	/**
	 * nacos maximum number of tolerable server reconnection errors.
	 */
	private String maxRetry;

	/**
	 * nacos get config long poll timeout.
	 */
	private String configLongPollTimeout;

	/**
	 * nacos get config failure retry time.
	 */
	private String configRetryTime;

	/**
	 * If you want to pull it yourself when the program starts to get the configuration
	 * for the first time, and the registered Listener is used for future configuration
	 * updates, you can keep the original code unchanged, just add the system parameter:
	 * enableRemoteSyncConfig = "true" ( But there is network overhead); therefore we
	 * recommend that you use {@link ConfigService#getConfigAndSignListener} directly.
	 */
	private boolean enableRemoteSyncConfig = false;

	/**
	 * endpoint for Nacos, the domain name of a service, through which the server address
	 * can be dynamically obtained.
	 */
	private String endpoint;

	/**
	 * namespace, separation configuration of different environments.
	 */
	private String namespace;

	/**
	 * access key for namespace.
	 */
	private String accessKey;

	/**
	 * secret key for namespace.
	 */
	private String secretKey;

	/**
	 * access key for namespace.
	 */
	private String ramRoleName;

	/**
	 * context path for nacos config server.
	 */
	private String contextPath;

	/**
	 * nacos config cluster name.
	 */
	private String clusterName;

	/**
	 * nacos config dataId name.
	 */
	private String name;

	/**
	 * a set of shared configurations .e.g:
	 * spring.cloud.nacos.config.shared-configs[0]=xxx .
	 */
	private List<Config> sharedConfigs;

	/**
	 * a set of extensional configurations .e.g:
	 * spring.cloud.nacos.config.extension-configs[0]=xxx .
	 */
	private List<Config> extensionConfigs;

	/**
	 * the master switch for refresh configuration, it default opened(true).
	 */
	private boolean refreshEnabled = true;
	
	public static class Config {

		/**
		 * the data id of extended configuration.
		 */
		private String dataId;

		/**
		 * the group of extended configuration, the default value is DEFAULT_GROUP.
		 */
		private String group = "DEFAULT_GROUP";

		/**
		 * whether to support dynamic refresh, the default does not support .
		 */
		private boolean refresh = false;

	}

}

NacosConfigProperties只要配置nacos的配置中心的相关参数,包括请求地址,用户名,密码,sharedConfigsextensionConfigs等信息。

NacosConfigManager

public class NacosConfigManager {

	private static final Logger log = LoggerFactory.getLogger(NacosConfigManager.class);

	private static ConfigService service = null;

	private NacosConfigProperties nacosConfigProperties;

	public NacosConfigManager(NacosConfigProperties nacosConfigProperties) {
		this.nacosConfigProperties = nacosConfigProperties;
		// Compatible with older code in NacosConfigProperties,It will be deleted in the
		// future.
		createConfigService(nacosConfigProperties);
	}

	/**
	 * Compatible with old design,It will be perfected in the future.
	 */
	static ConfigService createConfigService(
			NacosConfigProperties nacosConfigProperties) {
		if (Objects.isNull(service)) {
			synchronized (NacosConfigManager.class) {
				try {
					if (Objects.isNull(service)) {
						service = NacosFactory.createConfigService(
								nacosConfigProperties.assembleConfigServiceProperties());
					}
				}
				catch (NacosException e) {
					log.error(e.getMessage());
					throw new NacosConnectionFailureException(
							nacosConfigProperties.getServerAddr(), e.getMessage(), e);
				}
			}
		}
		return service;
	}

	public ConfigService getConfigService() {
		if (Objects.isNull(service)) {
			createConfigService(this.nacosConfigProperties);
		}
		return service;
	}

	public NacosConfigProperties getNacosConfigProperties() {
		return nacosConfigProperties;
	}

}

NacosConfigManager主要提供通过NacosFactory创建,获取ConfigService信息。

NacosPropertySourceLocator

@Order(0)
public class NacosPropertySourceLocator implements PropertySourceLocator {

	private static final Logger log = LoggerFactory
			.getLogger(NacosPropertySourceLocator.class);

	private static final String NACOS_PROPERTY_SOURCE_NAME = "NACOS";

	private static final String SEP1 = "-";

	private static final String DOT = ".";

	private NacosPropertySourceBuilder nacosPropertySourceBuilder;

	private NacosConfigProperties nacosConfigProperties;

	private NacosConfigManager nacosConfigManager;

	/**
	 * recommend to use
	 * {@link NacosPropertySourceLocator#NacosPropertySourceLocator(com.alibaba.cloud.nacos.NacosConfigManager)}.
	 * @param nacosConfigProperties nacosConfigProperties
	 */
	@Deprecated
	public NacosPropertySourceLocator(NacosConfigProperties nacosConfigProperties) {
		this.nacosConfigProperties = nacosConfigProperties;
	}

	public NacosPropertySourceLocator(NacosConfigManager nacosConfigManager) {
		this.nacosConfigManager = nacosConfigManager;
		this.nacosConfigProperties = nacosConfigManager.getNacosConfigProperties();
	}

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

	/**
	 * load shared configuration.
	 */
	private void loadSharedConfiguration(
			CompositePropertySource compositePropertySource) {
		List<NacosConfigProperties.Config> sharedConfigs = nacosConfigProperties
				.getSharedConfigs();
		if (!CollectionUtils.isEmpty(sharedConfigs)) {
			checkConfiguration(sharedConfigs, "shared-configs");
			loadNacosConfiguration(compositePropertySource, sharedConfigs);
		}
	}

	/**
	 * load extensional configuration.
	 */
	private void loadExtConfiguration(CompositePropertySource compositePropertySource) {
		List<NacosConfigProperties.Config> extConfigs = nacosConfigProperties
				.getExtensionConfigs();
		if (!CollectionUtils.isEmpty(extConfigs)) {
			checkConfiguration(extConfigs, "extension-configs");
			loadNacosConfiguration(compositePropertySource, extConfigs);
		}
	}

	/**
	 * load configuration of application.
	 */
	private void loadApplicationConfiguration(
			CompositePropertySource compositePropertySource, String dataIdPrefix,
			NacosConfigProperties properties, Environment environment) {
		String fileExtension = properties.getFileExtension();
		String nacosGroup = properties.getGroup();
		// load directly once by default
		loadNacosDataIfPresent(compositePropertySource, dataIdPrefix, nacosGroup,
				fileExtension, true);
		// load with suffix, which have a higher priority than the default
		loadNacosDataIfPresent(compositePropertySource,
				dataIdPrefix + DOT + fileExtension, nacosGroup, fileExtension, true);
		// Loaded with profile, which have a higher priority than the suffix
		for (String profile : environment.getActiveProfiles()) {
			String dataId = dataIdPrefix + SEP1 + profile + DOT + fileExtension;
			loadNacosDataIfPresent(compositePropertySource, dataId, nacosGroup,
					fileExtension, true);
		}

	}

	private void loadNacosConfiguration(final CompositePropertySource composite,
			List<NacosConfigProperties.Config> configs) {
		for (NacosConfigProperties.Config config : configs) {
			loadNacosDataIfPresent(composite, config.getDataId(), config.getGroup(),
					NacosDataParserHandler.getInstance()
							.getFileExtension(config.getDataId()),
					config.isRefresh());
		}
	}

	private void checkConfiguration(List<NacosConfigProperties.Config> configs,
			String tips) {
		for (int i = 0; i < configs.size(); i++) {
			String dataId = configs.get(i).getDataId();
			if (dataId == null || dataId.trim().length() == 0) {
				throw new IllegalStateException(String.format(
						"the [ spring.cloud.nacos.config.%s[%s] ] must give a dataId",
						tips, i));
			}
		}
	}

	private void loadNacosDataIfPresent(final CompositePropertySource composite,
			final String dataId, final String group, String fileExtension,
			boolean isRefreshable) {
		if (null == dataId || dataId.trim().length() < 1) {
			return;
		}
		if (null == group || group.trim().length() < 1) {
			return;
		}
		NacosPropertySource propertySource = this.loadNacosPropertySource(dataId, group,
				fileExtension, isRefreshable);
		this.addFirstPropertySource(composite, propertySource, false);
	}

	private NacosPropertySource loadNacosPropertySource(final String dataId,
			final String group, String fileExtension, boolean isRefreshable) {
		if (NacosContextRefresher.getRefreshCount() != 0) {
			if (!isRefreshable) {
				return NacosPropertySourceRepository.getNacosPropertySource(dataId,
						group);
			}
		}
		return nacosPropertySourceBuilder.build(dataId, group, fileExtension,
				isRefreshable);
	}

	/**
	 * Add the nacos configuration to the first place and maybe ignore the empty
	 * configuration.
	 */
	private void addFirstPropertySource(final CompositePropertySource composite,
			NacosPropertySource nacosPropertySource, boolean ignoreEmpty) {
		if (null == nacosPropertySource || null == composite) {
			return;
		}
		if (ignoreEmpty && nacosPropertySource.getSource().isEmpty()) {
			return;
		}
		composite.addFirstPropertySource(nacosPropertySource);
	}

	public void setNacosConfigManager(NacosConfigManager nacosConfigManager) {
		this.nacosConfigManager = nacosConfigManager;
	}

}

NacosPropertySourceLocator实现PropertySourceLocator并设置排序优先级为0,实现PropertySourceLocatorlocate方法。通过loadSharedConfiguration进行获取共享配置信息,通过loadExtConfiguration方法进行获取拓展的配置信息,通过loadApplicationConfiguration方法获取加载应用的配置信息,优先级这里面也就自动出来啦。

NacosPropertySourceBuilder

public class NacosPropertySourceBuilder {

	private static final Logger log = LoggerFactory
			.getLogger(NacosPropertySourceBuilder.class);

	private ConfigService configService;

	private long timeout;

	public NacosPropertySourceBuilder(ConfigService configService, long timeout) {
		this.configService = configService;
		this.timeout = timeout;
	}

	public long getTimeout() {
		return timeout;
	}

	public void setTimeout(long timeout) {
		this.timeout = timeout;
	}

	public ConfigService getConfigService() {
		return configService;
	}

	public void setConfigService(ConfigService configService) {
		this.configService = configService;
	}

	/**
	 * @param dataId Nacos dataId
	 * @param group Nacos group
	 */
	NacosPropertySource build(String dataId, String group, String fileExtension,
			boolean isRefreshable) {
		List<PropertySource<?>> propertySources = loadNacosData(dataId, group,
				fileExtension);
		NacosPropertySource nacosPropertySource = new NacosPropertySource(propertySources,
				group, dataId, new Date(), isRefreshable);
		NacosPropertySourceRepository.collectNacosPropertySource(nacosPropertySource);
		return nacosPropertySource;
	}

	private List<PropertySource<?>> loadNacosData(String dataId, String group,
			String fileExtension) {
		String data = null;
		try {
			data = configService.getConfig(dataId, group, timeout);
			if (StringUtils.isEmpty(data)) {
				log.warn(
						"Ignore the empty nacos configuration and get it based on dataId[{}] & group[{}]",
						dataId, group);
				return Collections.emptyList();
			}
			if (log.isDebugEnabled()) {
				log.debug(String.format(
						"Loading nacos data, dataId: '%s', group: '%s', data: %s", dataId,
						group, data));
			}
			return NacosDataParserHandler.getInstance().parseNacosData(dataId, data,
					fileExtension);
		}
		catch (NacosException e) {
			log.error("get data from Nacos error,dataId:{} ", dataId, e);
		}
		catch (Exception e) {
			log.error("parse data from Nacos error,dataId:{},data:{}", dataId, data, e);
		}
		return Collections.emptyList();
	}

}

NacosPropertySourceBuilder中进行了从nacos读取数据,进行配置文件统一化,NacosDataParserHandler进行获取了instance来进行处理配置信息。

NacosDataParserHandler

public final class NacosDataParserHandler {

	/**
	 * default extension.
	 */
	private static final String DEFAULT_EXTENSION = "properties";

	private static List<PropertySourceLoader> propertySourceLoaders;

	private NacosDataParserHandler() {
		propertySourceLoaders = SpringFactoriesLoader
				.loadFactories(PropertySourceLoader.class, getClass().getClassLoader());
	}

	/**
	 * Parsing nacos configuration content.
	 * @param configName name of nacos-config
	 * @param configValue value from nacos-config
	 * @param extension identifies the type of configValue
	 * @return result of Map
	 * @throws IOException thrown if there is a problem parsing config.
	 */
	public List<PropertySource<?>> parseNacosData(String configName, String configValue,
			String extension) throws IOException {
		if (StringUtils.isEmpty(configValue)) {
			return Collections.emptyList();
		}
		if (StringUtils.isEmpty(extension)) {
			extension = this.getFileExtension(configName);
		}
		for (PropertySourceLoader propertySourceLoader : propertySourceLoaders) {
			if (!canLoadFileExtension(propertySourceLoader, extension)) {
				continue;
			}
			NacosByteArrayResource nacosByteArrayResource;
			if (propertySourceLoader instanceof PropertiesPropertySourceLoader) {
				// PropertiesPropertySourceLoader internal is to use the ISO_8859_1,
				// the Chinese will be garbled, needs to transform into unicode.
				nacosByteArrayResource = new NacosByteArrayResource(
						NacosConfigUtils.selectiveConvertUnicode(configValue).getBytes(),
						configName);
			}
			else {
				nacosByteArrayResource = new NacosByteArrayResource(
						configValue.getBytes(), configName);
			}
			nacosByteArrayResource.setFilename(getFileName(configName, extension));
			List<PropertySource<?>> propertySourceList = propertySourceLoader
					.load(configName, nacosByteArrayResource);
			if (CollectionUtils.isEmpty(propertySourceList)) {
				return Collections.emptyList();
			}
			return propertySourceList.stream().filter(Objects::nonNull)
					.map(propertySource -> {
						if (propertySource instanceof EnumerablePropertySource) {
							String[] propertyNames = ((EnumerablePropertySource) propertySource)
									.getPropertyNames();
							if (propertyNames != null && propertyNames.length > 0) {
								Map<String, Object> map = new LinkedHashMap<>();
								Arrays.stream(propertyNames).forEach(name -> {
									map.put(name, propertySource.getProperty(name));
								});
								return new OriginTrackedMapPropertySource(
										propertySource.getName(), map, true);
							}
						}
						return propertySource;
					}).collect(Collectors.toList());
		}
		return Collections.emptyList();
	}

	/**
	 * check the current extension can be processed.
	 * @param loader the propertySourceLoader
	 * @param extension file extension
	 * @return if can match extension
	 */
	private boolean canLoadFileExtension(PropertySourceLoader loader, String extension) {
		return Arrays.stream(loader.getFileExtensions())
				.anyMatch((fileExtension) -> StringUtils.endsWithIgnoreCase(extension,
						fileExtension));
	}

	/**
	 * @param name filename
	 * @return file extension, default {@code DEFAULT_EXTENSION} if don't get
	 */
	public String getFileExtension(String name) {
		if (StringUtils.isEmpty(name)) {
			return DEFAULT_EXTENSION;
		}
		int idx = name.lastIndexOf(DOT);
		if (idx > 0 && idx < name.length() - 1) {
			return name.substring(idx + 1);
		}
		return DEFAULT_EXTENSION;
	}

	private String getFileName(String name, String extension) {
		if (StringUtils.isEmpty(extension)) {
			return name;
		}
		if (StringUtils.isEmpty(name)) {
			return extension;
		}
		int idx = name.lastIndexOf(DOT);
		if (idx > 0 && idx < name.length() - 1) {
			String ext = name.substring(idx + 1);
			if (extension.equalsIgnoreCase(ext)) {
				return name;
			}
		}
		return name + DOT + extension;
	}

	public static NacosDataParserHandler getInstance() {
		return ParserHandler.HANDLER;
	}

	private static class ParserHandler {

		private static final NacosDataParserHandler HANDLER = new NacosDataParserHandler();

	}

}

进行instance实例获取时,通过SpringFactoriesLoader进行加载META-INF/spring.factories配置文件下的PropertySourceLoader。系统加载了PropertiesPropertySourceLoader,YamlPropertySourceLoader,NacosXmlPropertySourceLoader,NacosJsonPropertySourceLoader,spring boot中的loader配置中。
image

org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader

NacosConfigAutoConfiguration

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

	@Bean
	public NacosConfigProperties nacosConfigProperties(ApplicationContext context) {
		if (context.getParent() != null
				&& BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
						context.getParent(), NacosConfigProperties.class).length > 0) {
			return BeanFactoryUtils.beanOfTypeIncludingAncestors(context.getParent(),
					NacosConfigProperties.class);
		}
		return new NacosConfigProperties();
	}

	@Bean
	public NacosRefreshProperties nacosRefreshProperties() {
		return new NacosRefreshProperties();
	}

	@Bean
	public NacosRefreshHistory nacosRefreshHistory() {
		return new NacosRefreshHistory();
	}

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

	@Bean
	public NacosContextRefresher nacosContextRefresher(
			NacosConfigManager nacosConfigManager,
			NacosRefreshHistory nacosRefreshHistory) {
		// Consider that it is not necessary to be compatible with the previous
		// configuration
		// and use the new configuration if necessary.
		return new NacosContextRefresher(nacosConfigManager, nacosRefreshHistory);
	}

}

NacosConfigAutoConfiguration在配置为$不为false的情况下进行实例化,NacosRefreshHistory为配置刷新记录的存储,NacosContextRefresher做为刷新的处理器。

NacosRefreshHistory

public class NacosRefreshHistory {

	private final static Logger log = LoggerFactory.getLogger(NacosRefreshHistory.class);

	private static final int MAX_SIZE = 20;

	private final LinkedList<Record> records = new LinkedList<>();

	private final ThreadLocal<DateFormat> DATE_FORMAT = ThreadLocal
			.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

	private MessageDigest md;

	public NacosRefreshHistory() {
		try {
			md = MessageDigest.getInstance("MD5");
		}
		catch (NoSuchAlgorithmException e) {
			log.error("failed to initialize MessageDigest : ", e);
		}
	}

	/**
	 * recommend to use
	 * {@link NacosRefreshHistory#addRefreshRecord(java.lang.String, java.lang.String, java.lang.String)}.
	 * @param dataId dataId
	 * @param md5 md5
	 */
	@Deprecated
	public void add(String dataId, String md5) {
		records.addFirst(
				new Record(DATE_FORMAT.get().format(new Date()), dataId, "", md5, null));
		if (records.size() > MAX_SIZE) {
			records.removeLast();
		}
	}

	public void addRefreshRecord(String dataId, String group, String data) {
		records.addFirst(new Record(DATE_FORMAT.get().format(new Date()), dataId, group,
				md5(data), null));
		if (records.size() > MAX_SIZE) {
			records.removeLast();
		}
	}

	public LinkedList<Record> getRecords() {
		return records;
	}

	private String md5(String data) {
		if (StringUtils.isEmpty(data)) {
			return null;
		}
		if (null == md) {
			try {
				md = MessageDigest.getInstance("MD5");
			}
			catch (NoSuchAlgorithmException ignored) {
				return "unable to get md5";
			}
		}
		return new BigInteger(1, md.digest(data.getBytes(StandardCharsets.UTF_8)))
				.toString(16);
	}

	static class Record {

		private final String timestamp;

		private final String dataId;

		private final String group;

		private final String md5;
	}

}

NacosRefreshHistory将刷新的记录放到了内存中的LinkedList中。

NacosContextRefresher

public class NacosContextRefresher
		implements ApplicationListener<ApplicationReadyEvent>, ApplicationContextAware {

	private final static Logger log = LoggerFactory
			.getLogger(NacosContextRefresher.class);

	private static final AtomicLong REFRESH_COUNT = new AtomicLong(0);

	private NacosConfigProperties nacosConfigProperties;

	private final boolean isRefreshEnabled;

	private final NacosRefreshHistory nacosRefreshHistory;

	private final ConfigService configService;

	private ApplicationContext applicationContext;

	private AtomicBoolean ready = new AtomicBoolean(false);

	private Map<String, Listener> listenerMap = new ConcurrentHashMap<>(16);

	public NacosContextRefresher(NacosConfigManager nacosConfigManager,
			NacosRefreshHistory refreshHistory) {
		this.nacosConfigProperties = nacosConfigManager.getNacosConfigProperties();
		this.nacosRefreshHistory = refreshHistory;
		this.configService = nacosConfigManager.getConfigService();
		this.isRefreshEnabled = this.nacosConfigProperties.isRefreshEnabled();
	}

	/**
	 * recommend to use
	 * {@link NacosContextRefresher#NacosContextRefresher(NacosConfigManager, NacosRefreshHistory)}.
	 * @param refreshProperties refreshProperties
	 * @param refreshHistory refreshHistory
	 * @param configService configService
	 */
	@Deprecated
	public NacosContextRefresher(NacosRefreshProperties refreshProperties,
			NacosRefreshHistory refreshHistory, ConfigService configService) {
		this.isRefreshEnabled = refreshProperties.isEnabled();
		this.nacosRefreshHistory = refreshHistory;
		this.configService = configService;
	}

	@Override
	public void onApplicationEvent(ApplicationReadyEvent event) {
		// many Spring context
		if (this.ready.compareAndSet(false, true)) {
			this.registerNacosListenersForApplications();
		}
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) {
		this.applicationContext = applicationContext;
	}

	/**
	 * register Nacos Listeners.
	 */
	private void registerNacosListenersForApplications() {
		if (isRefreshEnabled()) {
			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
						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);
		}
	}

	public NacosConfigProperties getNacosConfigProperties() {
		return nacosConfigProperties;
	}

	public NacosContextRefresher setNacosConfigProperties(
			NacosConfigProperties nacosConfigProperties) {
		this.nacosConfigProperties = nacosConfigProperties;
		return this;
	}

	public boolean isRefreshEnabled() {
		if (null == nacosConfigProperties) {
			return isRefreshEnabled;
		}
		// Compatible with older configurations
		if (nacosConfigProperties.isRefreshEnabled() && !isRefreshEnabled) {
			return false;
		}
		return isRefreshEnabled;
	}

	public static long getRefreshCount() {
		return REFRESH_COUNT.get();
	}

	public static void refreshCountIncrement() {
		REFRESH_COUNT.incrementAndGet();
	}

}

NacosContextRefresher实现了ApplicationListener接口,监听了ApplicationReadyEvent事件,当接收到事件后进行调用registerNacosListenersForApplications方法进行注册监听器,当监听的配置发生了变化,向变更历史中进行插入数据,发布RefreshEvent事件。

NacosConfigEndpointAutoConfiguration

@ConditionalOnWebApplication
@ConditionalOnClass(Endpoint.class)
@ConditionalOnProperty(name = "spring.cloud.nacos.config.enabled", matchIfMissing = true)
public class NacosConfigEndpointAutoConfiguration {

	@Autowired
	private NacosConfigManager nacosConfigManager;

	@Autowired
	private NacosRefreshHistory nacosRefreshHistory;

	@ConditionalOnMissingBean
	@ConditionalOnAvailableEndpoint
	@Bean
	public NacosConfigEndpoint nacosConfigEndpoint() {
		return new NacosConfigEndpoint(nacosConfigManager.getNacosConfigProperties(),
				nacosRefreshHistory);
	}

	@Bean
	@ConditionalOnMissingBean
	@ConditionalOnEnabledHealthIndicator("nacos-config")
	public NacosConfigHealthIndicator nacosConfigHealthIndicator() {
		return new NacosConfigHealthIndicator(nacosConfigManager.getConfigService());
	}

}

NacosConfigEndpointAutoConfiguration提供暴露出配置中心的端点信息。

NacosConnectionFailureAnalyzer

public class NacosConnectionFailureAnalyzer
		extends AbstractFailureAnalyzer<NacosConnectionFailureException> {

	@Override
	protected FailureAnalysis analyze(Throwable rootFailure,
			NacosConnectionFailureException cause) {
		return new FailureAnalysis(
				"Application failed to connect to Nacos server: \""
						+ cause.getServerAddr() + "\"",
				"Please check your Nacos server config", cause);
	}

}

NacosConnectionFailureAnalyzer做为nacos连接失败的报警,抛出FailureAnalysis异常信息。