Spring Cloud has enhanced the attribute source function of Environment,
In the spring cloud context package, the PropertySourceLocator interface is provided to implement the extension of property file loading. We can extend our external 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 locate needs to return a PropertySource object.
This PropertySource is the property source stored in the Environment. In other words, 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 the PropertySourceLocator needs to return a PropertySource, we must define our own PropertySource to obtain the configuration source from the outside.
GpDefineMapPropertySource represents a class that takes the Map result as the property source.
/** * Gobo education, ToBeBetterMan * Mic Teacher wechat: mic4096 * WeChat official account: following Mic learning framework * https://ke.gupaoedu.cn * Use Map as the attribute source. **/ 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(); } }
Extend PropertySourceLocator
Extend PropertySourceLocator and override locate to provide property source.
The attribute source is from Gupao The JSON file is loaded and saved into the custom property source GpDefineMapPropertySource.
public class GpJsonPropertySourceLocator implements PropertySourceLocator { //json data source private final static String DEFAULT_LOCATION="classpath:gupao.json"; //Resource loader private final ResourceLoader resourceLoader=new DefaultResourceLoader(getClass().getClassLoader()); @Override public PropertySource<?> locate(Environment environment) { //Set attribute source 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; } //Load file and parse 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; } //Save the parsed url to the complete set of result s. To implement @ Value injection 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
At / meta-inf / spring In the factories file, add the following spi extension to enable Spring Cloud to scan this extension when it starts, so as to load GpJsonPropertySourceLocator.
org.springframework.cloud.bootstrap.BootstrapConfiguration=\ com.gupaoedu.env.GpJsonPropertySourceLocator
Write controller test
@RestController public class ConfigController { @Value("${custom.property.message}") private String name; @GetMapping("/") public String get(){ String msg=String.format("Configuration value:%s",name); return msg; } }
Phased summary
Through the above cases, it can be found that the extension of custom configuration source can be easily realized based on the PropertySourceLocator extension mechanism provided by Spring Boot.
Thus, two questions arise.
- Where was the PropertySourceLocator triggered?
- Since we can get from Gupao If the data source is loaded in JSON, can it be loaded from the remote server?
Loading principle of PropertySourceLocator
Let's explore the first problem, the execution process 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.
ApplicationContextInitializer is the original thing of Spring framework. Its main function is to allow us to further set and process the instance of ConfigurableApplicationContext before the ApplicationContext of ConfigurableApplicationContext type (or subtype) is refresh ed.
It can be used in web applications that need to programmatically initialize the application context, such as registering a propertySource or a configuration file according to the context. The requirements of the configuration center of Config just need such a mechanism to complete.
public ConfigurableApplicationContext run(String... args) { //Omit code prepareContext(context, environment, listeners, applicationArguments, printedBanner); //Omit code return context; }
PropertySourceBootstrapConfiguration.initialize
Among them, PropertySourceBootstrapConfiguration implements applicationcontext initialize r, and the initialization method code is as follows.
@Override public void initialize(ConfigurableApplicationContext applicationContext) { List<PropertySource<?>> composite = new ArrayList<>(); //Sort the propertySourceLocators array according to the default AnnotationAwareOrderComparator AnnotationAwareOrderComparator.sort(this.propertySourceLocators); boolean empty = true; //Gets the context of the running environment ConfigurableEnvironment environment = applicationContext.getEnvironment(); for (PropertySourceLocator locator : this.propertySourceLocators) { //Call back all the locate methods that implement the PropertySourceLocator interface instance and collect them into the source collection. Collection<PropertySource<?>> source = locator.locateCollection(environment); if (source == null || source.size() == 0) { //If source is empty, go directly to the next cycle continue; } //Traverse the source, wrap the PropertySource as bootstrap PropertySource and add it to the sourceList. List<PropertySource<?>> sourceList = new ArrayList<>(); for (PropertySource<?> p : source) { sourceList.add(new BootstrapPropertySource<>(p)); } logger.info("Located property source: " + sourceList); composite.addAll(sourceList);//Add source to array empty = false; //Indicates that propertysource is not empty } //Only when propertysource is not empty can it be set in the environment if (!empty) { //Get all PropertySources in the current Environment MutablePropertySources propertySources = environment.getPropertySources(); String logConfig = environment.resolvePlaceholders("${logging.config:}"); LogFile logFile = LogFile.get(environment); // Traverse and remove the relevant properties of bootstrap property for (PropertySource<?> p : environment.getPropertySources()) { if (p.getName().startsWith(BOOTSTRAP_PROPERTY_SOURCE_NAME)) { propertySources.remove(p.getName()); } } //Insert the previously obtained PropertySource into PropertySources in the Environment. insertPropertySources(propertySources, composite); reinitializeLoggingSystem(environment, logConfig, logFile); setLogLevels(applicationContext, environment); handleIncludedProfiles(environment); } }
The logic of the above code is described as follows.
- First, this PropertySourceLocators refers to all implementation classes that implement the PropertySourceLocators interface, including the GpJsonPropertySourceLocator we customized earlier.
- Sort the propertySourceLocators array according to the default AnnotationAwareOrderComparator collation.
- Gets the running environment context ConfigurableEnvironment
- Error traversing propertySourceLocators
- Call the locate method and pass in the obtained context environment
- Add source to the linked list of PropertySource
- Set whether the source is empty or not
- Only when the source is not empty can it be set in the environment
- Returns the variable form of Environment, and the available operations such as addFirst and addLast
- Remove bootstrap properties from propertySources
- Set propertySources according to the rules overridden by config server
- Processing configuration information of multiple active profiles
Note: this The propertysourcelocators in the propertysourcelocators collection are injected through the automatic assembly mechanism. The specific implementation is in the bootstrap importselector class.
Understanding and use of ApplicationContextInitializer
ApplicationContextInitializer is the original thing of Spring framework. Its main function is to allow us to further set and process the instance of ConfigurableApplicationContext before the ApplicationContext of ConfigurableApplicationContext type (or subtype) is refresh ed.
It can be used in web applications that need to programmatically initialize the application context, such as registering a propertySource or a configuration file according to the context. The requirements of the 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 load
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 on 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: unless otherwise stated, all articles on this blog adopt CC BY-NC-SA 4.0 license agreement. Reprint please indicate from Mic to take you to learn architecture!
If this article is helpful to you, please pay attention and praise. Your persistence is the driving force of my continuous creation. Welcome to WeChat public official account for more dry cargo.