spring在run()方法启动时会调用prepareContext()准备上下文。在prepareEnvironment()方法准备好环境时会发布ApplicationEnvironmentPreparedEvent事件,BootstrapApplicationListener监听器对这个事件感兴趣,会调用onApplicationEvent()方法进行响应。此时会通过bootstrapServiceContext()来读取中BootstrapConfiguration配置的初始化器,PropertySourceBootstrapConfiguration会被读取并加载。然后在prepareContext()中会应用PropertySourceBootstrapConfiguration类中的initialize()方法进行初始化。PropertySourceBootstrapConfiguration中的属性propertySourceLocators使用了@Autowired注解,此时会到容器中获取实现了PropertySourceLocator接口的Bean。
initialize()这个方法会先创建PropertySource类的子类CompositePropertySource实例对象,使用"bootstrapProperties"表示这个属性源的名字,当获取到外部合成的属性源就会被添加这个实例对象中set集合中。
@Autowired(required = false)
private List<PropertySourceLocator> propertySourceLocators = new ArrayList<>();
Nacos的NacosPropertySourceLocator类实现了PropertySourceLocator,而且类在NacosConfigBootstrapConfiguration中被标注了@Bean注解,因此会被注入到propertySourceLocators属性中。当PropertySourceBootstrapConfiguration类中的initialize()方法调用时即到类中调用locate()方法进入Nacos的配置加载流程。
public class NacosPropertySourceLocator implements PropertySourceLocator {}
@Bean
public NacosPropertySourceLocator nacosPropertySourceLocator() {
return new NacosPropertySourceLocator();
}
- 调用NacosConfigProperties中的configServiceInstance()创建ConfigService实例,首先获取NacosConfigProperties中的属性,即以为开头的配置项的值。接下来使用获取到的属性以反射创建ConfigService实例。创建"NACOS"的CompositePropertySource实例,保存Nacos的配置源。
- 加载应用配置loadApplicationConfiguration():通过由配置项生成的dataId到Nacos服务端获取配置源loadNacosDataIfPresent()。
String dataId = dataIdPrefix + SEP1 + profile + DOT + fileExtension;
NacosPropertySource ps = nacosPropertySourceBuilder.build(dataId, group,
fileExtension, isRefreshable);
composite.addFirstPropertySource(ps);
- 从环境中获取所有的属性源,将Nacos合成的属性源添加到环境属性源集合(propertySource);
loadNacosDataIfPresent()中,第一次到服务器获取配置源loadNacosData(),之后就从缓存中获取配置源,最终到类中getConfigInner()方法从服务端获取配置源。
- 使用dataId、group创建ConfigResponse对象保存来自服务端的响应
- 优先使用本地配置:使用serverName(服务名)/dataId/group/tenant(默认命名空间public)构造配置文件,然后从配置文件中读取,如果本地配置文件不存在则从Nacos服务端读取
- 重头戏来了,我们本地没有配置,就通过类的getServerConfig()到服务端读取配置。
- 将"dataId"、“group”、"tenant"这三个参数加到List集合中保存下来
- 设置超时时间(默认3s),向服务端的/v1/cs/configs路径发get请求
- 以UTF-8编码方式对参数进行编码,得到query参数
static private String encodingParams(List<String> paramValues, String encoding) throws UnsupportedEncodingException { StringBuilder sb = new StringBuilder(); if (null == paramValues) { return null; } //遍历参数集合,第一个元素为key,然后加=,第二个元素为value for (Iterator<String> iter = paramValues.iterator(); iter.hasNext(); ) { sb.append(iter.next()).append("="); sb.append(URLEncoder.encode(iter.next(), encoding)); if (iter.hasNext()) { sb.append("&"); } } return sb.toString(); }
- 获取请求url的MD5值,使用谷歌RateLimiter框架通过MD5值进行请求次数的限制,默认1秒5次请求
- 使用Java原生的HttpURLConnection发送请求获取响应,将响应字节流编码成字符串,至此完成从服务端获取配置源
- 成功地获取到来自服务端的配置信息后会被保存在本地,使用serverName(服务名)/dataId/group/tenant(默认命名空间public)/group/dataId构造配置文件,然后将获取到的配置写到本地配置文件中保存下来
public static void writeStringToFile(File file, String data, String encoding)
throws IOException {
OutputStream os = null;
try {
os = new FileOutputStream(file);
os.write(data.getBytes(encoding));
} finally {
if (null != os) {
os.close();
}
}
}
- 将配置保存到ConfigResponse实例对象中
在从服务端获取到配置源后,将配置源封装成NacosPropertySource实例,再将NacosPropertySource配置源添加到CompositePropertySource(外部合成配置源实例),之后就是添加到spring的环境中,通过@Value注解获取了