概述
搭建SpringCloudAlibabaNacos环境
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.study.nacos</groupId>
<artifactId>nacosStudy</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>nacosStudy</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.1.3.RELEASE</version>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
配置Nacos相关属性
spring.application.name=nacos-config-test
spring.cloud.nacos.config.server-addr=192.168.10.18:8858
启动类中添加测试代码
package com.study.nacos.nacosstudy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class NacosStudyApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(NacosStudyApplication.class, args);
String test = context.getEnvironment().getProperty("test");
System.out.println(test);
}
}
通过context.getEnvironment().getProperty(“test”);获取nacos管理的配置项。
nacos统一配置的作用
集中管理配置的CRUD
配置的动态更新
springcloud实现nacos配置初始化加载
启动类方法入口
@SpringBootApplication
public class NacosStudyApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(NacosStudyApplication.class, args);
}
}
SpringApplication.java的run方法
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}
我们继续跟踪new SpringApplication(primarySources).run(args);的run方法。 进入run方法,我们就看到的核心代码,真正创建应用的上下文的地方. 这里,我会只摘取对nacos配置加载和动态更新有用的入口代码。
public ConfigurableApplicationContext run(String... args) {
ConfigurableApplicationContext context = null;
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
return context;
}
nacos配置位置查找
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader())
.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
- 接下来发送ApplicationEnvironmentPreparedEvent事件
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
this.application, this.args, environment));
}
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
listener.onApplicationEvent(event);
}
...
}
- 找到事件接收者
我们在越多spring源码时会经常迷失,迷失的原因是spring中使用了大量的观察者模式,往往都是先根据被观察者的类型收集大量的观察观察者(比如Listener),然后循环执行观察者方法。 这里介绍一个技巧,找实现了ApplicationEnvironmentPreparedEvent事件的接口(观察者),因为预处理环境就发送的这个事件。 - 这样我们就来到了BootstrapApplicationListener.java的onApplicationEvent方法中
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
if ((Boolean)environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class, true)) {
if (!environment.getPropertySources().contains("bootstrap")) {
ConfigurableApplicationContext context = null;
....
if (context == null) {
context = this.bootstrapServiceContext(environment, event.getSpringApplication(), configName);
event.getSpringApplication().addListeners(new ApplicationListener[]{new BootstrapApplicationListener.CloseContextOnFailureApplicationListener(context)});
}
this.apply(context, event.getSpringApplication(), environment);
}
}
}
private ConfigurableApplicationContext bootstrapServiceContext(ConfigurableEnvironment environment, final SpringApplication application, String configName) {
StandardEnvironment bootstrapEnvironment = new StandardEnvironment();
MutablePropertySources bootstrapProperties = bootstrapEnvironment.getPropertySources();
...
builder.sources(new Class[]{BootstrapImportSelectorConfiguration.class});
ConfigurableApplicationContext context = builder.run(new String[0]);
context.setId("bootstrap");
this.addAncestorInitializer(application, context);
bootstrapProperties.remove("bootstrap");
this.mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
return context;
}
自动装配nacos配置类型
@Import({BootstrapImportSelector.class})
public class BootstrapImportSelectorConfiguration {
public BootstrapImportSelectorConfiguration() {
}
}
public String[] selectImports(AnnotationMetadata annotationMetadata) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
List<String> names = new ArrayList(SpringFactoriesLoader.loadFactoryNames(BootstrapConfiguration.class, classLoader));
..
return classNames;
}
- 可以找到两处加载org.springframework.cloud.bootstrap.BootstrapConfiguration的地方
spring-cloud-starter-alibaba-nacos-config-2.1.2.RELEASE.jar的META-INF/spring.factories中 com.alibaba.cloud.nacos.NacosConfigBootstrapConfiguration
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
spring-cloud-context-3.0.3.jar的META-INF/spring.factories中 org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,\
org.springframework.cloud.autoconfigure.LifecycleMvcEndpointAutoConfiguration,\
org.springframework.cloud.autoconfigure.RefreshAutoConfiguration,\
org.springframework.cloud.autoconfigure.RefreshEndpointAutoConfiguration,\
org.springframework.cloud.autoconfigure.WritableEnvironmentEndpointAutoConfiguration
org.springframework.context.ApplicationListener=\
org.springframework.cloud.bootstrap.BootstrapApplicationListener,\
org.springframework.cloud.bootstrap.LoggingSystemShutdownListener,\
org.springframework.cloud.context.restart.RestartListener
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration,\
org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration,\
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration
org.springframework.boot.BootstrapRegistryInitializer=\
org.springframework.cloud.bootstrap.RefreshBootstrapRegistryInitializer
org.springframework.boot.Bootstrapper=\
org.springframework.cloud.bootstrap.TextEncryptorConfigBootstrapper
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.cloud.bootstrap.encrypt.DecryptEnvironmentPostProcessor,\
org.springframework.cloud.util.random.CachedRandomPropertySourceEnvironmentPostProcessor
加载nacos客户端的配置信息
@Configuration
@ConditionalOnProperty(
name = {"spring.cloud.nacos.config.enabled"},
matchIfMissing = true
)
public class NacosConfigBootstrapConfiguration {
public 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 对象后,紧接着就是生成NacosConfigProperties对象,最后初始化nacos的配置信息。
public NacosConfigProperties() {
}
@PostConstruct
public void init() {
this.overrideFromEnv();
}
private void overrideFromEnv() {
if (StringUtils.isEmpty(this.getServerAddr())) {
String serverAddr = this.environment.resolvePlaceholders("${spring.cloud.nacos.config.server-addr:}");
if (StringUtils.isEmpty(serverAddr)) {
serverAddr = this.environment.resolvePlaceholders("${spring.cloud.nacos.server-addr:localhost:8848}");
}
this.setServerAddr(serverAddr);
}
if (StringUtils.isEmpty(this.getUsername())) {
this.setUsername(this.environment.resolvePlaceholders("${spring.cloud.nacos.username:}"));
}
if (StringUtils.isEmpty(this.getPassword())) {
this.setPassword(this.environment.resolvePlaceholders("${spring.cloud.nacos.password:}"));
}
}
上下文加载nacos中的配置信息
回到SpringApplication.java的run方法
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
我们关注的是上下文的初始化方法applyInitializers(context)方法
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
applyInitializers(context);
...
}
protected void applyInitializers(ConfigurableApplicationContext context) {
for (ApplicationContextInitializer initializer : getInitializers()) {
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
initializer.getClass(), ApplicationContextInitializer.class);
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
initializer.initialize(context);
}
}
我们找到之前spring-cloud-context-3.0.3.jar的META-INF/spring.factories中 org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration 对应的PropertySourceBootstrapConfiguration类。 在Initialize方法中
PropertySourceLocator locator = (PropertySourceLocator)var5.next();
source = locator.locateCollection(environment);
我们找到PropertySourceLocator的实现类NacosPropertySourceLocator, 我们查看locate方法
public PropertySource<?> locate(Environment env) {
this.nacosConfigProperties.setEnvironment(env);
ConfigService configService = this.nacosConfigManager.getConfigService();
if (null == configService) {
log.warn("no instance of config service found, can't load config from nacos");
return null;
} else {
long timeout = (long)this.nacosConfigProperties.getTimeout();
this.nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService, timeout);
String name = this.nacosConfigProperties.getName();
String dataIdPrefix = this.nacosConfigProperties.getPrefix();
if (StringUtils.isEmpty(dataIdPrefix)) {
dataIdPrefix = name;
}
if (StringUtils.isEmpty(dataIdPrefix)) {
dataIdPrefix = env.getProperty("spring.application.name");
}
CompositePropertySource composite = new CompositePropertySource("NACOS");
this.loadSharedConfiguration(composite);
this.loadExtConfiguration(composite);
this.loadApplicationConfiguration(composite, dataIdPrefix, this.nacosConfigProperties, env);
return composite;
}
}
private void loadApplicationConfiguration(CompositePropertySource compositePropertySource, String dataIdPrefix, NacosConfigProperties properties, Environment environment) {
String fileExtension = properties.getFileExtension();
String nacosGroup = properties.getGroup();
this.loadNacosDataIfPresent(compositePropertySource, dataIdPrefix, nacosGroup, fileExtension, true);
...
}
private NacosPropertySource loadNacosPropertySource(final String dataId, final String group, String fileExtension, boolean isRefreshable) {
return NacosContextRefresher.getRefreshCount() != 0L && !isRefreshable ? NacosPropertySourceRepository.getNacosPropertySource(dataId, group) : this.nacosPropertySourceBuilder.build(dataId, group, fileExtension, isRefreshable);
}
NacosPropertySource build(String dataId, String group, String fileExtension, boolean isRefreshable) {
Map<String, Object> p = this.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 {
data = this.configService.getConfig(dataId, group, this.timeout);
...
}
spring cloud实现nacos配置的动态更新
应用上下文加载成功后会发送一个ready事件,nacosRefresh实现了该事件作为观察者; 执行事件逻辑中,把对nacos配置感兴趣的listener注册到服务器端。 客户端通过长连接+pull的方式获取服务器端配置变化,一旦配置发送变化,会直接通知到客户端,客户端根据注册的listener发送refreshEvent事件。 RefreshEventListener监听到refreshEvent事件会把更新的配置拷贝到上下文的environment中。
|