环境:Springboot2.3.12.RELEASE + Spring Cloud Alibaba2.2.5.RELEASE + Spring Cloud Hoxton.SR12
应用的核心技术是:自定义PropertySourceLocator,然后配置spring.factories
在如下包中配置:
spring-cloud-context-xxx.jar中
org.springframework.cloud.bootstrap.BootstrapConfiguration=
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration
Nacos加载配置的时候默认会通过一下三个DataId加载数据:
核心类:
NacosPropertySourceLocator
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.cloud.nacos.config.enabled", matchIfMissing = true)
public class NacosConfigBootstrapConfiguration {
// Nacos Config相关的配置属性
@Bean
@ConditionalOnMissingBean
public NacosConfigProperties nacosConfigProperties() {
return new NacosConfigProperties();
}
// 管理Nacos Config相关的服务
@Bean
@ConditionalOnMissingBean
public NacosConfigManager nacosConfigManager(
NacosConfigProperties nacosConfigProperties) {
return new NacosConfigManager(nacosConfigProperties);
}
// 核心类自定义启动加载配置文件(Bootstraps)
@Bean
public NacosPropertySourceLocator nacosPropertySourceLocator(
NacosConfigManager nacosConfigManager) {
return new NacosPropertySourceLocator(nacosConfigManager);
}
}
org.springframework.cloud.bootstrap.BootstrapConfiguration=
com.alibaba.cloud.nacos.NacosConfigBootstrapConfiguration
public class NacosPropertySourceLocator implements PropertySourceLocator {
private NacosPropertySourceBuilder nacosPropertySourceBuilder;
private NacosConfigProperties nacosConfigProperties;
private NacosConfigManager nacosConfigManager;
public NacosPropertySourceLocator(NacosConfigManager nacosConfigManager) {
this.nacosConfigManager = nacosConfigManager;
this.nacosConfigProperties = nacosConfigManager.getNacosConfigProperties();
}
public PropertySource<?> locate(Environment env) {
nacosConfigProperties.setEnvironment(env);
// 关键通过NacosConfigManager获取ConfigService服务
ConfigService configService = nacosConfigManager.getConfigService();
// ...
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);// NACOS_PROPERTY_SOURCE_NAME = NACOS
// 加载共享配置
loadSharedConfiguration(composite);
// 加载扩展配置
loadExtConfiguration(composite);
// 加载应用程序配置(这里就以应用程序配置,深入查看)
loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
return composite;
}
}
ConfigService配置服务是获取配置信息的核心方法:
public interface ConfigService {
// 获取配置
String getConfig(String dataId, String group, long timeoutMs) throws NacosException;
// 获取配置并设置监听
String getConfigAndSignListener(String dataId, String group, long timeoutMs, Listener listener)
throws NacosException;
// 给配置添加监听
void addListener(String dataId, String group, Listener listener) throws NacosException;
// 发布配置
boolean publishConfig(String dataId, String group, String content) throws NacosException;
// 删除配置
boolean removeConfig(String dataId, String group) throws NacosException;
// 删除监听
void removeListener(String dataId, String group, Listener listener);
// 获取服务状态
String getServerStatus();
// 关闭资源服务
void shutDown() throws NacosException;
}
NacosConfigManager
public class NacosConfigManager {
private static ConfigService service = null;
private NacosConfigProperties nacosConfigProperties;
public NacosConfigManager(NacosConfigProperties nacosConfigProperties) {
this.nacosConfigProperties = nacosConfigProperties;
// 创建配置服务
createConfigService(nacosConfigProperties);
}
static ConfigService createConfigService(
NacosConfigProperties nacosConfigProperties) {
if (Objects.isNull(service)) {
synchronized (NacosConfigManager.class) {
try {
if (Objects.isNull(service)) {
// 通过工厂创建服务
// assembleConfigServiceProperties方法
// 就是收集关于Nacos所有配置信息,如:地址,端口,用户名,密码等
// 详细查看NacosConfigProperties#assembleConfigServiceProperties
service = NacosFactory.createConfigService(
nacosConfigProperties.assembleConfigServiceProperties());
}
}
// ...
}
}
return service;
}
}
// NacosFactory
public class NacosFactory {
public static ConfigService createConfigService(Properties properties) throws NacosException {
return ConfigFactory.createConfigService(properties);
}
}
public class ConfigFactory {
public static ConfigService createConfigService(Properties properties) throws NacosException {
try {
Class<?> driverImplClass = Class.forName("com.alibaba.nacos.client.config.NacosConfigService");
Constructor constructor = driverImplClass.getConstructor(Properties.class);
ConfigService vendorImpl = (ConfigService) constructor.newInstance(properties);
// 通过反射构造了NacosConfigservice,同时设置属性信息
// 这些属性信息就是一些Naocs服务的地址,端口,用户名,密码等信息
return vendorImpl;
} catch (Throwable e) {
throw new NacosException(NacosException.CLIENT_INVALID_PARAM, e);
}
}
}
到此就得到了一个ConfigService服务类NacosConfigService。
接着95.2获取到了ConfigService以后继续执行
public class NacosPropertySourceLocator implements PropertySourceLocator {
private NacosPropertySourceBuilder nacosPropertySourceBuilder;
public PropertySource<?> locate(Environment env) {
nacosConfigProperties.setEnvironment(env);
// 关键通过NacosConfigManager获取ConfigService服务
ConfigService configService = nacosConfigManager.getConfigService();
// ...
nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService,
timeout);
CompositePropertySource composite = new CompositePropertySource(
NACOS_PROPERTY_SOURCE_NAME);
loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
return composite;
}
private void loadApplicationConfiguration(
CompositePropertySource compositePropertySource, String dataIdPrefix,
NacosConfigProperties properties, Environment environment) {
// 获取文件扩展
String fileExtension = properties.getFileExtension();
// 获取分组
String nacosGroup = properties.getGroup();
// ...
// 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
// 这里会根据不同配置的profiles再次加载不同环境的配置
for (String profile : environment.getActiveProfiles()) {
String dataId = dataIdPrefix + SEP1 + profile + DOT + fileExtension;
loadNacosDataIfPresent(compositePropertySource, dataId, nacosGroup,
fileExtension, true);
}
}
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;
}
// 加载Nacos属性源
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) {
// ...
return nacosPropertySourceBuilder.build(dataId, group, fileExtension,
isRefreshable);
}
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 class NacosPropertySourceBuilder {
NacosPropertySource build(String dataId, String group, String fileExtension,
boolean isRefreshable) {
Map<String, Object> p = loadNacosData(dataId, group, fileExtension);
NacosPropertySource nacosPropertySource = new NacosPropertySource(group, dataId,
p, new Date(), isRefreshable);
NacosPropertySourceRepository.collectNacosPropertySource(nacosPropertySource);
return nacosPropertySource;
}
// 加载数据
private Map<String, Object> loadNacosData(String dataId, String group,
String fileExtension) {
String data = null;
try {
// 通过ConfigService加载配置内容(从远程服务获取)
data = configService.getConfig(dataId, group, timeout);
if (StringUtils.isEmpty(data)) {
return EMPTY_MAP;
}
Map<String, Object> dataMap = NacosDataParserHandler.getInstance()
.parseNacosData(data, fileExtension);
return dataMap == null ? EMPTY_MAP : dataMap;
}
// ...
return EMPTY_MAP;
}
}
public class NacosConfigService implements ConfigService {
public String getConfig(String dataId, String group, long timeoutMs) throws NacosException {
return getConfigInner(namespace, dataId, group, timeoutMs);
}
private String getConfigInner(String tenant, String dataId, String group, long timeoutMs) throws NacosException {
group = null2defaultGroup(group);
ParamUtils.checkKeyParam(dataId, group);
ConfigResponse cr = new ConfigResponse();
cr.setDataId(dataId);
cr.setTenant(tenant);
cr.setGroup(group);
// 这里会从缓存文件中获取,如果能获取就不会再从远程加载了
// 会从如下缓存目录下加载配置:
// System.getProperty("JM.SNAPSHOT.PATH",
// System.getProperty("user.home")) + File.separator + "nacos"
// + File.separator + "config"
String content = LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant);
if (content != null) {
cr.setContent(content);
configFilterChainManager.doFilter(null, cr);
content = cr.getContent();
return content;
}
try {
// 缓存总无法获取则从远程服务上拉取数据
String[] ct = worker.getServerConfig(dataId, group, tenant, timeoutMs);
cr.setContent(ct[0]);
configFilterChainManager.doFilter(null, cr);
content = cr.getContent();
return content;
} catch (NacosException ioe) {
if (NacosException.NO_RIGHT == ioe.getErrCode()) {
throw ioe;
}
}
content = LocalConfigInfoProcessor.getSnapshot(agent.getName(), dataId, group, tenant);
cr.setContent(content);
configFilterChainManager.doFilter(null, cr);
content = cr.getContent();
return content;
}
}
public class ClientWorker implements Closeable {
// 这里就是从远程服务拉取配置
public String[] getServerConfig(String dataId, String group, String tenant, long readTimeout)
throws NacosException {
String[] ct = new String[2];
if (StringUtils.isBlank(group)) {
group = Constants.DEFAULT_GROUP;
}
HttpRestResult<String> result = null;
try {
Map<String, String> params = new HashMap<String, String>(3);
if (StringUtils.isBlank(tenant)) {
params.put("dataId", dataId);
params.put("group", group);
} else {
params.put("dataId", dataId);
params.put("group", group);
params.put("tenant", tenant);
}
result = agent.httpGet(Constants.CONFIG_CONTROLLER_PATH, null, params, agent.getEncode(), readTimeout);
} catch (Exception ex) {
throw new NacosException(NacosException.SERVER_ERROR, ex);
}
switch (result.getCode()) {
case HttpURLConnection.HTTP_OK:
// 获取成功后会将数据保存到缓存目录下
LocalConfigInfoProcessor.saveSnapshot(agent.getName(), dataId, group, tenant, result.getData());
ct[0] = result.getData();
if (result.getHeader().getValue(CONFIG_TYPE) != null) {
ct[1] = result.getHeader().getValue(CONFIG_TYPE);
} else {
ct[1] = ConfigType.TEXT.getType();
}
return ct;
case HttpURLConnection.HTTP_NOT_FOUND:
LocalConfigInfoProcessor.saveSnapshot(agent.getName(), dataId, group, tenant, null);
return ct;
case HttpURLConnection.HTTP_CONFLICT: {
throw new NacosException(NacosException.CONFLICT,
"data being modified, dataId=" + dataId + ",group=" + group + ",tenant=" + tenant);
}
throw new NacosException(result.getCode(), result.getMessage());
}
default: {
throw new NacosException(result.getCode(),
"http error, code=" + result.getCode() + ",dataId=" + dataId + ",group=" + group + ",tenant="
+ tenant);
}
}
}
}
到此就完成了远程配置数据的加载
总结:Nacos Config先从本地缓存中获取数据,如果不能获取才从远程拉取(如果远程服务无法连接,那么会不断重试,前提是本地能够加载到数据,否则服务将无法正常启动)。
完毕!!!