Using alicloud ACM configuration center in Springboot

Posted by murdocsvan on Tue, 15 Oct 2019 03:56:58 +0200

When our project is just a simple Springboot project, it is inconvenient to use Aliyun ACM configuration center to load and read remote database or redis configuration information when Springboot starts. Official sdk packages either need Spring Cloud or just come out of nocos as registries; so they can only be self-reliant.

Realization principle

Referring to the implementation of Spring Cloud, the overload of configuration information is implemented in the following class: org. spring framework. cloud. bootstrap. Bootstrap Application Listener.

public class BootstrapApplicationListener
	implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered 
{
...
  [@Override](https://my.oschina.net/u/1162528)
  public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
   ....
   }
   ...

This class implements a Liistener interface for Application Environment Prepared Event and is configured in the spring.factories file so that the class is automatically loaded when spring boot starts loading the configuration information for the preparation system. This class loads and updates the configuration information at this time. Since we can do this, we can certainly do a similar simplified operation.

When we debug the springboot startup process, you will find this class: org.springframework.boot.context.config.ConfigFileApplicationListener

It will execute all the implementation classes of the EnvironmentPostProcessor according to the serial number, which happens to be the operation of the relevant configuration read by spring boot, and the configuration of this class is the last one according to the order serial number; then we implement this interface and set its serial number after this class. A more detailed look at springboot startup principles.

Specific realization:

Create a META-INF directory under the resources directory in the project, and create a spring.factories file inside (without worrying about being overwritten by other jar packages) to write the following configuration:

# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=top.vchar.alibaba.acm.ACMConfigEnvironmentPostProcessor

Create the ACMConfigEnvironmentPostProcessor class with the following sample code:

public class ACMConfigEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
​
	private final DeferredLog logger = new DeferredLog();
​
	/**
	 * The default order for the processor.
   *
   * This takes the order value of ConfigFileApplicationListener as the initial value + 1
	 */
	public static final int DEFAULT_ORDER = ConfigFileApplicationListener.DEFAULT_ORDER + 1;
​
	private int order = DEFAULT_ORDER;
​
	[@Override](https://my.oschina.net/u/1162528)
	public int getOrder() {
		return this.order;//The bigger the order, the later it will be executed
	}
​
	[@Override](https://my.oschina.net/u/1162528)
	public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
		MutablePropertySources propertySources = environment.getPropertySources();

		// Get the application configuration for all spring boots
		List<PropertySource<?>> applicationConfig = propertySources.stream().filter(p -> p instanceof OriginTrackedMapPropertySource && p.getName().contains("applicationConfig")).collect(Collectors.toList());
		if(applicationConfig.size()==0){
			logger.warn("do not find applicationConfig PropertySource");
			return;
		}
​
	//Get configuration acm information
		AcmProperties acmProperties = new AcmProperties();
		if(applicationConfig.size()==1){
			// only one application config
			loadAcmConfig((OriginTrackedMapPropertySource) applicationConfig.get(0), acmProperties);
		}else {
			for(PropertySource<?> propertySource: applicationConfig){
				loadAcmConfig((OriginTrackedMapPropertySource) propertySource, acmProperties);
			}
			String[] activeProfiles = environment.getActiveProfiles();
			if(activeProfiles!=null && activeProfiles.length>0){
				List<PropertySource<?>> activeSources = propertySources.stream().filter(p -> p instanceof OriginTrackedMapPropertySource && p.getName().contains("applicationConfig") && p.getName().contains(activeProfiles[0])).collect(Collectors.toList());
				if(activeSources.size() > 0){
					loadAcmConfig((OriginTrackedMapPropertySource) activeSources.get(0), acmProperties);
				}
			}
		}
​
		// TODO retrieves acm information coverage profile from system environment variables
	...
	//Initialize the acm configuration service according to the configuration information and use official demo.
	...
		//Pull information about remote acm configuration
		Map<String, Object> config = loadConfig(acmProperties);
		if(!config.isEmpty()){
			newSource.putAll(config);
		}

	//Update the obtained configuration information to the current environment
		for(PropertySource<?> propertySource: applicationConfig){
			OriginTrackedMapPropertySource source  = (OriginTrackedMapPropertySource) propertySource;
			source.getSource().putAll(newSource);
		}
	}
}

Project source code https://github.com/vcharfred/springboot-config

Topics: Programming Spring SpringBoot Database Redis