Spring Cloud NacosのPropertySourceLocator起動フロー解説

起動時のPropertySourceLocator呼び出しメカニズム

Spring BootアプリケーションはSpringApplication.run()実行中にprepareContext()を呼び出し、このタイミングで登録済みの全ApplicationContextInitializerがコールバックされる。

ConfigurableApplicationContext ctx = SpringApplication.run(DemoApp.class, args);
// prepareContext(...) 内部で初期化子が実行される

PropertySourceBootstrapConfiguration.initialize()

PropertySourceBootstrapConfigurationApplicationContextInitializerを実装しており、起動直後に以下の処理を行う。

@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, ...);
    }
}

処理概要

  1. PropertySourceLocatorをOrderでソート
  2. 各Locatorに対してlocateCollection()を呼び出し、PropertySourceを収集
  3. 収集したものをBootstrapPropertySourceでラップ
  4. 既存のbootstrapPropertiesを除去し、新規リストを先頭に挿入
  5. ロギングシステムを再初期化

locatorsBootstrapImportSelectorにより自動的に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に注入される。

タグ: Spring Cloud Nacos PropertySourceLocator ApplicationContextInitializer ConfigService

7月23日 01:26 投稿