起動時のPropertySourceLocator呼び出しメカニズム
Spring BootアプリケーションはSpringApplication.run()実行中にprepareContext()を呼び出し、このタイミングで登録済みの全ApplicationContextInitializerがコールバックされる。
ConfigurableApplicationContext ctx = SpringApplication.run(DemoApp.class, args);
// prepareContext(...) 内部で初期化子が実行される
PropertySourceBootstrapConfiguration.initialize()
PropertySourceBootstrapConfigurationはApplicationContextInitializerを実装しており、起動直後に以下の処理を行う。
@Override
public void initialize(ConfigurableApplicationContext ctx) {
List<PropertySource<?>> aggregated = new ArrayList<>();
AnnotationAwareOrderComparator.sort(locators); // 1. ソート
ConfigurableEnvironment env = ctx.getEnvironment();
boolean found = false;
for (PropertySourceLocator locator : locators) {
Collection<PropertySource<?>> sources = locator.locateCollection(env);
if (sources == null || sources.isEmpty()) continue;
List<PropertySource<?>> wrapped = new ArrayList<>();
for (PropertySource<?> ps : sources) {
wrapped.add(
(ps instanceof EnumerablePropertySource)
? new BootstrapPropertySource<>((EnumerablePropertySource<?>) ps)
: new SimpleBootstrapPropertySource(ps)
);
}
aggregated.addAll(wrapped);
found = true;
}
if (found) {
MutablePropertySources propertySources = env.getPropertySources();
propertySources.stream()
.filter(p -> p.getName().startsWith(BOOTSTRAP_PROPERTY_SOURCE_NAME))
.forEach(p -> propertySources.remove(p.getName()));
insertPropertySources(propertySources, aggregated);
reinitializeLoggingSystem(env, ...);
}
}
処理概要
- 全
PropertySourceLocatorをOrderでソート - 各Locatorに対して
locateCollection()を呼び出し、PropertySourceを収集 - 収集したものを
BootstrapPropertySourceでラップ - 既存の
bootstrapPropertiesを除去し、新規リストを先頭に挿入 - ロギングシステムを再初期化
locatorsはBootstrapImportSelectorにより自動的にDIコンテナに登録される。
ApplicationContextInitializerの利用例
独自の初期化処理を追加したい場合はApplicationContextInitializerを実装する。
public class EnvDumpInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext ctx) {
ConfigurableEnvironment env = ctx.getEnvironment();
env.getPropertySources().forEach(System.out::println);
}
}
META-INF/spring.factoriesに登録しておくと起動時に自動で呼ばれる。
org.springframework.context.ApplicationContextInitializer=\
com.example.EnvDumpInitializer
NacosPropertySourceLocatorの実装
Nacosからリモート設定を取得するPropertySourceLocator実装がNacosPropertySourceLocatorである。
@Override
public PropertySource<?> locate(Environment env) {
nacosConfigProperties.setEnvironment(env);
ConfigService configService = nacosConfigManager.getConfigService();
if (configService == null) {
log.warn("ConfigService not available");
return null;
}
long timeout = nacosConfigProperties.getTimeout();
builder = new NacosPropertySourceBuilder(configService, timeout);
String appName = nacosConfigProperties.getName();
String prefix = StringUtils.hasText(nacosConfigProperties.getPrefix())
? nacosConfigProperties.getPrefix()
: StringUtils.hasText(appName)
? appName
: env.getProperty("spring.application.name");
CompositePropertySource composite = new CompositePropertySource("NACOS");
loadSharedConfiguration(composite);
loadExtConfiguration(composite);
loadApplicationConfiguration(composite, prefix, nacosConfigProperties, env);
return composite;
}
アプリケーション固有設定の読み込み
private void loadApplicationConfiguration(CompositePropertySource composite,
String prefix,
N NacosConfigProperties props,
Environment env) {
String ext = props.getFileExtension();
String grp = props.getGroup();
loadIfExists(composite, prefix, grp, ext, true);
loadIfExists(composite, prefix + "." + ext, grp, ext, true);
for (String profile : env.getActiveProfiles()) {
loadIfExists(composite,
prefix + "-" + profile + "." + ext,
grp, ext, true);
}
}
実際の取得処理
private NacosPropertySource load(String dataId, String group,
String ext, boolean refreshable) {
if (!refreshable && NacosContext.getRefreshCount() != 0) {
return NacosPropertySourceRepository.getCached(dataId, group);
}
return builder.build(dataId, group, ext, refreshable);
}
NacosPropertySourceBuilder.build()内部ではConfigService#getConfig()を呼び出し、取得した文字列をパースしてNacosPropertySourceとして返す。
NacosPropertySource build(String dataId, String group,
String ext, boolean refreshable) {
List<PropertySource<?>> sources = loadRemote(dataId, group, ext);
NacosPropertySource nps =
new NacosPropertySource(sources, group, dataId, new Date(), refreshable);
NacosPropertySourceRepository.cache(nps);
return nps;
}
private List<PropertySource<?>> loadRemote(String dataId, String group, String ext) {
String content = configService.getConfig(dataId, group, timeout);
if (StringUtils.isEmpty(content)) {
return Collections.emptyList();
}
return NacosDataParserHandler.getInstance().parse(dataId, content, ext);
}
以上により、Spring Boot起動時にNacos Serverから動的に設定値を取得し、Environmentに注入される。