nacos配置中心 服务启动的配置加载
代码语言:javascript复制@SpringBootApplication
public class SpringCloudNacosConfigApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context=
SpringApplication.run(SpringCloudNacosConfigApplication.class, args);
String info=context.getEnvironment().getProperty("info");
System.out.println(info);
}
}
Spring抽象了一个environment表示Spring应用程序环境配置,整合了各种各样的外部环境,并提供统一访问的方法getProperty()
spring启动时候,会把配置加载到Environment中,当创建一个Bean时可以从Environment中把一些属性值通过@Value的形式注入到业务代码中。
springcloud要实现统一配置管理并动态刷新配置需要解决两个问题
- 如何将远程服务器上的配置加载到Environment
- 配置变更时,如何将新的配置更新到Environment中,保证配置变更时可以进行属性值的动态刷新。
spring boot 启动 调用SpringApplication.run()
代码语言:javascript复制public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
prepareEnvironment 环境准备
prepareEnvironment中调用listeners.environmentPrepared方法,发布一个ApplicationEnvironmentPreparedEvent事件,所有对这个事件感兴趣的Listener都会监听到该事件。
代码语言:javascript复制private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
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;
}
进入listeners.environmentPrepared方法
代码语言:javascript复制@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
this.application, this.args, environment));
}
BootstrapApplicationListener实现了ApplicationListener<ApplicationEnvironmentPreparedEvent>
会收到该事件并进行处理
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;
String configName = environment.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
Iterator var5 = event.getSpringApplication().getInitializers().iterator();
while(var5.hasNext()) {
ApplicationContextInitializer<?> initializer = (ApplicationContextInitializer)var5.next();
if (initializer instanceof ParentContextApplicationContextInitializer) {
context = this.findBootstrapContext((ParentContextApplicationContextInitializer)initializer, configName);
}
}
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);
}
}
}
进入bootstrapServiceContext方法:
代码语言:javascript复制private ConfigurableApplicationContext bootstrapServiceContext(ConfigurableEnvironment environment, final SpringApplication application, String configName) {
StandardEnvironment bootstrapEnvironment = new StandardEnvironment();
MutablePropertySources bootstrapProperties = bootstrapEnvironment.getPropertySources();
Iterator var6 = bootstrapProperties.iterator();
while(var6.hasNext()) {
PropertySource<?> source = (PropertySource)var6.next();
bootstrapProperties.remove(source.getName());
}
String configLocation = environment.resolvePlaceholders("${spring.cloud.bootstrap.location:}");
Map<String, Object> bootstrapMap = new HashMap();
bootstrapMap.put("spring.config.name", configName);
bootstrapMap.put("spring.main.web-application-type", "none");
if (StringUtils.hasText(configLocation)) {
bootstrapMap.put("spring.config.location", configLocation);
}
bootstrapProperties.addFirst(new MapPropertySource("bootstrap", bootstrapMap));
Iterator var8 = environment.getPropertySources().iterator();
while(var8.hasNext()) {
PropertySource<?> source = (PropertySource)var8.next();
if (!(source instanceof StubPropertySource)) {
bootstrapProperties.addLast(source);
}
}
SpringApplicationBuilder builder = (new SpringApplicationBuilder(new Class[0])).profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF).environment(bootstrapEnvironment).registerShutdownHook(false).logStartupInfo(false).web(WebApplicationType.NONE);
SpringApplication builderApplication = builder.application();
if (builderApplication.getMainApplicationClass() == null) {
builder.main(application.getMainApplicationClass());
}
if (environment.getPropertySources().contains("refreshArgs")) {
builderApplication.setListeners(this.filterListeners(builderApplication.getListeners()));
}
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;
}
builder.sources(new Class[]{BootstrapImportSelectorConfiguration.class});
是关键
@Configuration
@Import(BootstrapImportSelector.class)
public class BootstrapImportSelectorConfiguration {
}
BootstrapImportSelectorConfiguration是一个配置类,该配置类用@Import导入BootstrapImportSelector来实现自动装配
代码语言:javascript复制@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
List<String> names = new ArrayList<>(SpringFactoriesLoader
.loadFactoryNames(BootstrapConfiguration.class, classLoader));
names.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(
this.environment.getProperty("spring.cloud.bootstrap.sources", ""))));
List<OrderedAnnotatedElement> elements = new ArrayList<>();
for (String name : names) {
try {
elements.add(
new OrderedAnnotatedElement(this.metadataReaderFactory, name));
}
catch (IOException e) {
continue;
}
}
AnnotationAwareOrderComparator.sort(elements);
String[] classNames = elements.stream().map(e -> e.name).toArray(String[]::new);
return classNames;
}
BootstrapImportSelector的selectImports方法中,利用Spring的SPI机制,可到classpath路径下查找META-INF/spring.factories预定义的一些扩展点,其中key就是BootstrapConfiguration。
分别在
spring-cloud-context.jar
代码语言:javascript复制# Bootstrap components
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
spring-cloud-alibaba-nacos-config.jar
代码语言:javascript复制org.springframework.cloud.bootstrap.BootstrapConfiguration=
com.alibaba.cloud.nacos.NacosConfigBootstrapConfiguration
其中重要的两个类是PropertySourceBootstrapConfiguration NacosConfigBootstrapConfiguration
prepareEnvironment 环境准备到此结束。
prepareContext
开始刷新应用上下文的准备阶段,
代码语言:javascript复制private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
applyInitializers(context);
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
}
接着调用applyInitializers方法:
代码语言:javascript复制@SuppressWarnings({ "rawtypes", "unchecked" })
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);
}
}
PropertySourceBootstrapConfiguration实现了ApplicationContextInitializer接口,所以initializer.initialize(context);调用PropertySourceBootstrapConfiguration中的initialize方法
代码语言:javascript复制@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
CompositePropertySource composite = new CompositePropertySource(
BOOTSTRAP_PROPERTY_SOURCE_NAME);
AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
boolean empty = true;
ConfigurableEnvironment environment = applicationContext.getEnvironment();
for (PropertySourceLocator locator : this.propertySourceLocators) {
PropertySource<?> source = null;
source = locator.locate(environment);
if (source == null) {
continue;
}
logger.info("Located property source: " source);
composite.addPropertySource(source);
empty = false;
}
if (!empty) {
MutablePropertySources propertySources = environment.getPropertySources();
String logConfig = environment.resolvePlaceholders("${logging.config:}");
LogFile logFile = LogFile.get(environment);
if (propertySources.contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
propertySources.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
}
insertPropertySources(propertySources, composite);
reinitializeLoggingSystem(environment, logConfig, logFile);
setLogLevels(applicationContext, environment);
handleIncludedProfiles(environment);
}
}
然后执行source = locator.locate(environment);
PropertySourceLocator接口的主要作用是实现外部化配置可动态加载
NacosPropertySourceLocator实现了locate方法,把存放在服务端中的配置信息读取出来,然后把结果存到CompositePropertySource中
NacosPropertySourceLocator的locate方法是核心方法,作用是
- 初始化ConfigService对象,这是Nacos客户端提供的用于访问实现配置中心基本操作的类
- 按顺序加载共享配置,扩展配置,应用名称对应的配置。
@Override
public PropertySource<?> locate(Environment env) {
ConfigService configService = nacosConfigProperties.configServiceInstance();
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;
}
进入loadApplicationConfiguration方法
代码语言:javascript复制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);
}
}
进入loadNacosDataIfPresent方法:
代码语言:javascript复制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);
}
进入loadNacosPropertySource方法
代码语言:javascript复制private NacosPropertySource loadNacosPropertySource(final String dataId,
final String group, String fileExtension, boolean isRefreshable) {
if (NacosContextRefresher.getRefreshCount() != 0) {
if (!isRefreshable) {
return NacosPropertySourceRepository.getNacosPropertySource(dataId);
}
}
return nacosPropertySourceBuilder.build(dataId, group, fileExtension,
isRefreshable);
}
进入nacosPropertySourceBuilder.build方法
代码语言:javascript复制NacosPropertySource build(String dataId, String group, String fileExtension,
boolean isRefreshable) {
Properties p = loadNacosData(dataId, group, fileExtension);
NacosPropertySource nacosPropertySource = new NacosPropertySource(group, dataId,
propertiesToMap(p), new Date(), isRefreshable);
NacosPropertySourceRepository.collectNacosPropertySources(nacosPropertySource);
return nacosPropertySource;
}
进入loadNacosData方法:
代码语言:javascript复制private Properties 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 EMPTY_PROPERTIES;
}
log.info(String.format(
"Loading nacos data, dataId: '%s', group: '%s', data: %s", dataId,
group, data));
Properties properties = NacosDataParserHandler.getInstance()
.parseNacosData(data, fileExtension);
return properties == null ? EMPTY_PROPERTIES : properties;
}
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 EMPTY_PROPERTIES;
}
最终通过configService.getConfig方法从nacos配置中心中加载配置进行填充。