1

Spring Cloud has enhanced the property source function of Environment,

In the spring-cloud-contenxt package, the PropertySourceLocator interface is provided to implement the extension of property file loading. We can extend our own externalized configuration loading through this interface. This interface is defined as follows

public interface PropertySourceLocator {

   /**
    * @param environment The current Environment.
    * @return A PropertySource, or null if there is none.
    * @throws IllegalStateException if there is a fail-fast condition.
    */
   PropertySource<?> locate(Environment environment);
}

The abstract method of locate needs to return a PropertySource object.

This PropertySource is the property source stored in the Environment. That is to say, if we want to implement custom externalized configuration loading, we only need to implement this interface and return PropertySource.

According to this idea, we follow the following steps to realize the custom loading of externalized configuration.

Custom PropertySource

Since PropertySourceLocator needs to return a PropertySource, we must define our own PropertySource to obtain configuration sources from outside.

GpDefineMapPropertySource represents a class that uses Map results as a property source.

/**
 * 咕泡教育,ToBeBetterMan
 * Mic老师微信: mic4096
 * 微信公众号: 跟着Mic学架构
 * https://ke.gupaoedu.cn
 * 使用Map作为属性来源。
 **/
public class GpDefineMapPropertySource extends MapPropertySource {
    /**
     * Create a new {@code MapPropertySource} with the given name and {@code Map}.
     *
     * @param name   the associated name
     * @param source the Map source (without {@code null} values in order to get
     *               consistent {@link #getProperty} and {@link #containsProperty} behavior)
     */
    public GpDefineMapPropertySource(String name, Map<String, Object> source) {
        super(name, source);
    }

    @Override
    public Object getProperty(String name) {
        return super.getProperty(name);
    }

    @Override
    public String[] getPropertyNames() {
        return super.getPropertyNames();
    }
}

Extends PropertySourceLocator

Extend PropertySourceLocator, override locate to provide property source.

The property source is loaded from the gupao.json file and saved to the custom property source GpDefineMapPropertySource.

public class GpJsonPropertySourceLocator implements PropertySourceLocator {

    //json数据来源
    private final static String DEFAULT_LOCATION="classpath:gupao.json";
    //资源加载器
    private final ResourceLoader resourceLoader=new DefaultResourceLoader(getClass().getClassLoader());

    @Override
    public PropertySource<?> locate(Environment environment) {
        //设置属性来源
        GpDefineMapPropertySource jsonPropertySource=new GpDefineMapPropertySource
                ("gpJsonConfig",mapPropertySource());
        return jsonPropertySource;
    }

    private Map<String,Object> mapPropertySource(){
        Resource resource=this.resourceLoader.getResource(DEFAULT_LOCATION);
        if(resource==null){
            return null;
        }
        Map<String,Object> result=new HashMap<>();
        JsonParser parser= JsonParserFactory.getJsonParser();
        Map<String,Object> fileMap=parser.parseMap(readFile(resource));
        processNestMap("",result,fileMap);
        return result;
    }
    //加载文件并解析
    private String readFile(Resource resource){
        FileInputStream fileInputStream=null;
        try {
            fileInputStream=new FileInputStream(resource.getFile());
            byte[] readByte=new byte[(int)resource.getFile().length()];
            fileInputStream.read(readByte);
            return new String(readByte,"UTF-8");
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(fileInputStream!=null){
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }
    //解析完整的url保存到result集合。 为了实现@Value注入
    private void processNestMap(String prefix,Map<String,Object> result,Map<String,Object> fileMap){
        if(prefix.length()>0){
            prefix+=".";
        }
        for (Map.Entry<String, Object> entrySet : fileMap.entrySet()) {
            if (entrySet.getValue() instanceof Map) {
                processNestMap(prefix + entrySet.getKey(), result, (Map<String, Object>) entrySet.getValue());
            } else {
                result.put(prefix + entrySet.getKey(), entrySet.getValue());
            }
        }
    }
}

Spring.factories

In the /META-INF/spring.factories file, add the following spi extension to allow Spring Cloud to scan this extension when it starts to load GpJsonPropertySourceLocator.

org.springframework.cloud.bootstrap.BootstrapConfiguration=\
  com.gupaoedu.env.GpJsonPropertySourceLocator

Write controller tests

@RestController
public class ConfigController {
    @Value("${custom.property.message}")
    private String name;
    
    @GetMapping("/")
    public String get(){
        String msg=String.format("配置值:%s",name);
        return msg;
    }
}

Staged summary

From the above cases, it can be found that based on the PropertySourceLocator extension mechanism provided by Spring Boot, the extension of custom configuration sources can be easily implemented.

Thus, two questions arise.

  1. Where is the PropertySourceLocator triggered?
  2. Since the data source can be loaded from gupao.json , can it be loaded from a remote server?

PropertySourceLocator loading principle

Let's explore the first question, the execution flow of PropertySourceLocator.

SpringApplication.run

When the spring boot project starts, there is a prepareContext method, which will call back all instances that implement ApplicationContextInitializer to do some initialization work.

ApplicationContextInitializer is the original thing of the Spring framework. Its main function is to allow us to further set and process the instance of ConfiurableApplicationContext before the ApplicationContext of the ConfigurableApplicationContext type (or subtype) is refreshed.

It can be used in web applications that require programmatic initialization of the application context, such as registering a propertySource based on the context, or a configuration file. And the requirements of this configuration center of Config just need such a mechanism to complete.

public ConfigurableApplicationContext run(String... args) {
   //省略代码...
        prepareContext(context, environment, listeners, applicationArguments, printedBanner);
   //省略代码
    return context;
}

PropertySourceBootstrapConfiguration.initialize

Among them, PropertySourceBootstrapConfiguration implements ApplicationContextInitializer , initialize method code is as follows.

@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
    List<PropertySource<?>> composite = new ArrayList<>();
    //对propertySourceLocators数组进行排序,根据默认的AnnotationAwareOrderComparator
    AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
    boolean empty = true;
    //获取运行的环境上下文
    ConfigurableEnvironment environment = applicationContext.getEnvironment();
    for (PropertySourceLocator locator : this.propertySourceLocators) {
        //回调所有实现PropertySourceLocator接口实例的locate方法,并收集到source这个集合中。
        Collection<PropertySource<?>> source = locator.locateCollection(environment);
        if (source == null || source.size() == 0) { //如果source为空,直接进入下一次循环
            continue;
        }
        //遍历source,把PropertySource包装成BootstrapPropertySource加入到sourceList中。
        List<PropertySource<?>> sourceList = new ArrayList<>();
        for (PropertySource<?> p : source) {
            sourceList.add(new BootstrapPropertySource<>(p));
        }
        logger.info("Located property source: " + sourceList);
        composite.addAll(sourceList);//将source添加到数组
        empty = false; //表示propertysource不为空
    }
     //只有propertysource不为空的情况,才会设置到environment中
    if (!empty) {
        //获取当前Environment中的所有PropertySources.
        MutablePropertySources propertySources = environment.getPropertySources();
        String logConfig = environment.resolvePlaceholders("${logging.config:}");
        LogFile logFile = LogFile.get(environment);
       // 遍历移除bootstrapProperty的相关属性
        for (PropertySource<?> p : environment.getPropertySources()) {
            
            if (p.getName().startsWith(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
                propertySources.remove(p.getName());
            }
        }
        //把前面获取到的PropertySource,插入到Environment中的PropertySources中。
        insertPropertySources(propertySources, composite);
        reinitializeLoggingSystem(environment, logConfig, logFile);
        setLogLevels(applicationContext, environment);
        handleIncludedProfiles(environment);
    }
}

The logic of the above code is described as follows.

  1. First of all, this.propertySourceLocators means all the implementation classes that implement the PropertySourceLocators interface, including the GpJsonPropertySourceLocator we customized earlier.
  2. Sorts the propertySourceLocators array according to the default AnnotationAwareOrderComparator collation.
  3. Get the running environment context ConfigurableEnvironment
  4. When traversing propertySourceLocators
  • Call the locate method, passing in the obtained context environment
  • Add source to the linked list of PropertySource
  • Set whether the source is an empty identifier scalar empty
  1. If the source is not empty, it will be set to the environment
  • Returns the variable form of Environment, which can be operated such as addFirst, addLast
  • Remove bootstrapProperties in propertySources
  • Set propertySources according to the rules overridden by config server
  • Handling configuration information for multiple active profiles
Note: The PropertySourceLocator in this.propertySourceLocators collection is injected through the automatic assembly mechanism, and the specific implementation is in the class BootstrapImportSelector .

Understanding and use of ApplicationContextInitializer

ApplicationContextInitializer is the original thing of the Spring framework. Its main function is to allow us to further set and process the instance of ConfiurableApplicationContext before the ApplicationContext of the ConfigurableApplicationContext type (or subtype) is refreshed.

It can be used in web applications that require programmatic initialization of the application context, such as registering a propertySource based on the context, or a configuration file. And the requirements of this configuration center of Config just need such a mechanism to complete.

Create a TestApplicationContextInitializer

public class TestApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext>{
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        ConfigurableEnvironment ce=applicationContext.getEnvironment();
        for(PropertySource<?> propertySource:ce.getPropertySources()){
            System.out.println(propertySource);
        }
        System.out.println("--------end");
    }
}

add spi loading

Create a file /resources/META-INF/spring.factories. Add the following

org.springframework.context.ApplicationContextInitializer= \
  com.gupaoedu.example.springcloudconfigserver9091.TestApplicationContextInitializer

You can see the output of the current PropertySource in the console.

ConfigurationPropertySourcesPropertySource {name='configurationProperties'}
StubPropertySource {name='servletConfigInitParams'}
StubPropertySource {name='servletContextInitParams'}
PropertiesPropertySource {name='systemProperties'}
OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}
RandomValuePropertySource {name='random'}
MapPropertySource {name='configServerClient'}
MapPropertySource {name='springCloudClientHostInfo'}
OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application.yml]'}
MapPropertySource {name='kafkaBinderDefaultProperties'}
MapPropertySource {name='defaultProperties'}
MapPropertySource {name='springCloudDefaultProperties'}
Copyright notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless otherwise stated. Reprint please indicate from Mic takes you to learn the architecture!
If this article is helpful to you, please help to follow and like, your persistence is the driving force for my continuous creation. Welcome to follow the WeChat public account of the same name to get more technical dry goods!

跟着Mic学架构
810 声望1.1k 粉丝

《Spring Cloud Alibaba 微服务原理与实战》、《Java并发编程深度理解及实战》作者。 咕泡教育联合创始人,12年开发架构经验,对分布式微服务、高并发领域有非常丰富的实战经验。