Spring 插件扩展

Spring 插件扩展

本文汇总一些常见的扩展 Spring 的库的场景和扩展方法

自定义加载 Properties 文件

Apollo 是携程框架部门研发的分布式配置中心,它的 Java 客户端可以在 Spring 启动的时候将这些配置加载到本地,与 Spring 无缝整合,它的 Java 客户端使用文档请参考 Java 客户端使用指南。无论是支持注解方式 @Value("${someKeyFromApollo:someDefaultValue}") 引用,还是在文件中引用 Apollo 服务器上的配置 spring.datasource.url: ${someKeyFromApollo:someDefaultValue} 都是没有问题的。

它的实现原理如下:

public class ApolloApplicationContextInitializer implements
    ApplicationContextInitializer<ConfigurableApplicationContext> , EnvironmentPostProcessor, Ordered {

    @Override
    public void initialize(ConfigurableApplicationContext context) {
        ConfigurableEnvironment environment = context.getEnvironment();

        if (!environment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED, Boolean.class, false)) {
            logger.debug("Apollo bootstrap config is not enabled for context {}, see property: ${{}}", context, PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED);
            return;
        }
        logger.debug("Apollo bootstrap config is enabled for context {}", context);

        initialize(environment);
    }

    /**
    * Initialize Apollo Configurations Just after environment is ready.
    *
    * @param environment
    */
    protected void initialize(ConfigurableEnvironment environment) {

        if (environment.getPropertySources().contains(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
            //already initialized
            return;
        }

        String namespaces = environment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_NAMESPACES, ConfigConsts.NAMESPACE_APPLICATION);
        logger.debug("Apollo bootstrap namespaces: {}", namespaces);
        List<String> namespaceList = NAMESPACE_SPLITTER.splitToList(namespaces);

        CompositePropertySource composite = new CompositePropertySource(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME);
        for (String namespace : namespaceList) {
            Config config = ConfigService.getConfig(namespace);

            composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(namespace, config));
        }

        environment.getPropertySources().addFirst(composite);
    }

    /**
    * To fill system properties from environment config
    */
    void initializeSystemProperty(ConfigurableEnvironment environment) {
        for (String propertyName : APOLLO_SYSTEM_PROPERTIES) {
            fillSystemPropertyFromEnvironment(environment, propertyName);
        }
    }

    private void fillSystemPropertyFromEnvironment(ConfigurableEnvironment environment, String propertyName) {
        if (System.getProperty(propertyName) != null) {
            return;
        }

        String propertyValue = environment.getProperty(propertyName);

        if (Strings.isNullOrEmpty(propertyValue)) {
            return;
        }

        System.setProperty(propertyName, propertyValue);
    }

    /**
    *
    * In order to load Apollo configurations as early as even before Spring loading logging system phase,
    * this EnvironmentPostProcessor can be called Just After ConfigFileApplicationListener has succeeded.
    *
    * <br />
    * The processing sequence would be like this: <br />
    * Load Bootstrap properties and application properties -----> load Apollo configuration properties ----> Initialize Logging systems
    *
    * @param configurableEnvironment
    * @param springApplication
    */
    @Override
    public void postProcessEnvironment(ConfigurableEnvironment configurableEnvironment, SpringApplication springApplication) {

        // should always initialize system properties like app.id in the first place
        initializeSystemProperty(configurableEnvironment);

        Boolean eagerLoadEnabled = configurableEnvironment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_EAGER_LOAD_ENABLED, Boolean.class, false);

        //EnvironmentPostProcessor should not be triggered if you don't want Apollo Loading before Logging System Initialization
        if (!eagerLoadEnabled) {
            return;
        }

        Boolean bootstrapEnabled = configurableEnvironment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED, Boolean.class, false);

        if (bootstrapEnabled) {
            initialize(configurableEnvironment);
        }

    }

}

从上述代码可以看出,最主要的是在 initializepostProcessEnvironment 方法中,将从服务器获取好的 Config ,添加到 environment.getPropertySources() 中,并且添加的时候是用的 addFirst() 方法添加到了首位

而 SpringBoot 框架的 SpringApplication 在启动的时候,会扫描所有 JAR 包中的每一个 /META-INF/spring.factories 文件,下面是 apollo-client 的该文件内容:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.ctrip.framework.apollo.spring.boot.ApolloAutoConfiguration
org.springframework.context.ApplicationContextInitializer=\
com.ctrip.framework.apollo.spring.boot.ApolloApplicationContextInitializer
org.springframework.boot.env.EnvironmentPostProcessor=\
com.ctrip.framework.apollo.spring.boot.ApolloApplicationContextInitializer

SpringApplication构造器内部,会自动加载所有声明在 /META-INF/spring.factories 文件中且实现了 org.springframework.context.ApplicationContextInitializer 接口的类,然后根据 Ordered 接口的 getOrder() 方法将这些类进行排序,并在 applyInitializers 方法中,依次调用 initialize(context) 方法。