spring cloud 中Environment参数自定义扩展配置源码简析和实例

本文基于Spring Cloud Hoxton.SR8和Spring Boot 2.3.2.RELEASE,探讨了如何在spring cloud中自定义扩展Environment配置。通过两阶段创建ApplicationContext,分析了启动过程中的Environment源码,介绍了如何在阶段一和阶段二加载自定义配置,并提供了自定义扩展Environment的实例和配置方法。

1. 前言

本文基于Spring Cloud Version:Spring Cloud Hoxton.SR8,Spring Boot Version:2.3.2.RELEASE

在spring boot中Environment的扩展配置可借助ConfigFileApplicationListener这个ApplicationListener,通过自定义类实现EnvironmentPostProcessor接口来扩展,详细介绍可以我之前的文章《spring boot参数配置之Environment源码分析》《spring boot参数配置之Environment扩展》.

在spring cloud中Environment的扩展配置还可以借助PropertySourceBootstrapConfiguration这个ApplicationContextInitializer,通过自定义类实现PropertySourceLocator接口来扩展。

2. spring cloud 启动处理Environment源码分析
2.1. spring cloud 启动ApplicationContext两阶段创建

在spring cloud中启动时SpringApplication.run(String... args)会执行两次,也就是说ApplicationContext会分为两阶段来创建。

  1. 第一次执行run(阶段一):阶段一执行会只会加载bootstrap.yml里配置的参数以及创建spring.factoriesorg.springframework.cloud.bootstrap.BootstrapConfiguration里配置的类,这些类往往是一些跟配置相关的类,这一阶段创建的类优先级很高,主要为阶段二ApplicationContext里需要创建的类提供一些准备。
    可以看看 spring-cloud-context-2.2.5.RELEASE.jar里的spring.factories# Bootstrap components相关配置

  2. 第二次执行run(阶段二):阶段二执行会加载剩余所有的参数以及创建所有的bea对象,主要是通过@ComponentScan可以扫描到的、xml里配置的bean、以及通过@EnableAutoConfiguration加载spring.factories里的org.springframework.boot.autoconfigure.EnableAutoConfiguration的相关配置

可以看看 spring-cloud-context-2.2.5.RELEASE.jar里的spring.factories# AutoConfiguration相关配置

# AutoConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,\
org.springframework.cloud.autoconfigure.LifecycleMvcEndpointAutoConfiguration,\
org.springframework.cloud.autoconfigure.RefreshAutoConfiguration,\
org.springframework.cloud.autoconfigure.RefreshEndpointAutoConfiguration,\
org.springframework.cloud.autoconfigure.WritableEnvironmentEndpointAutoConfiguration
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.cloud.bootstrap.BootstrapApplicationListener,\
org.springframework.cloud.bootstrap.LoggingSystemShutdownListener,\
org.springframework.cloud.context.restart.RestartListener
# Bootstrap components
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration,\
org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration,\
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.cloud.util.random.CachedRandomPropertySourceAutoConfiguration
2.2. spring cloud 启动参数相关源码分析

主线:PropertySourceBootstrapConfiguration的initialize(…)->PropertySourceLocator.locate()
核心源码如下:

public ConfigurableApplicationContext run(String... args) {
   	StopWatch stopWatch = new StopWatch();
   	stopWatch.start();
   	ConfigurableApplicationContext context = null;
   	Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
   	configureHeadlessProperty();
   	SpringApplicationRunListeners listeners = getRunListeners(args);
   	listeners.starting();
   	try {
   		ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
   		//environment参数创建准备工作
   		ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
   		configureIgnoreBeanInfo(environment);
   		Banner printedBanner = printBanner(environment);
   		context = createApplicationContext();
   		exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
   				new Class[] { ConfigurableApplicationContext.class }, context);
   		prepareContext(context, environment, listeners, applicationArguments, printedBanner);
   		refreshContext(context);
   		afterRefresh(context, applicationArguments);
   		stopWatch.stop();
   		if (this.logStartupInfo) {
   			new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
   		}
   		listeners.started(context);
   		callRunners(context, applicationArguments);
   	}
   	catch (Throwable ex) {
   		handleRunFailure(context, ex, exceptionReporters, listeners);
   		throw new IllegalStateException(ex);
   	}

   	try {
   		listeners.running(context);
   	}
   	catch (Throwable ex) {
   		handleRunFailure(context, ex, exceptionReporters, null);
   		throw new IllegalStateException(ex);
   	}
   	return context;
   }
   private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
   		ApplicationArguments applicationArguments) {
   	// Create and configure the environment
   	ConfigurableEnvironment environment = getOrCreateEnvironment();
   	configureEnvironment(environment, applicationArguments.getSourceArgs());
   	ConfigurationPropertySources.attach(environment);
   	//这里触发再一次执行上面的run方法,也就是出发第一阶段applicationContest创建的创建
   	listeners.environmentPrepared(environment);
   	bindToSpringApplication(environment);
   	if (!this.isCustomEnvironment) {
   		environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
   				deduceEnvironmentClass());
   	}
   	ConfigurationPropertySources.attach(environment);
   	return environment;
   }
   private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
   		SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
   	context.setEnvironment(environment);
   	postProcessApplicationContext(context);
   	applyInitializers(context);
        …………
   }
   protected void applyInitializers(ConfigurableApplicationContext context) {
   	for (ApplicationContextInitializer initializer : getInitializers()) {
   		Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
   				ApplicationContextInitializer.class);
   		Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
   		initializer.initialize(context);
   	}
   }

这个代码的详细分析可以参见文章《spring boot参数配置之Environment源码分析》

PropertySourceBootstrapConfiguration的创建

阶段一在refreshContext之前,SpringApplication中所有的initializers截图如下,并没有PropertySourceBootstrapConfiguration
在这里插入图片描述
从上面spring-cloud-context-2.2.5.RELEASE.jar里的spring.factories的配置有org.springframework.cloud.bootstrap.BootstrapConfiguration=\org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration,所以在第一阶段执行完后,SpringApplication中所有的initializers截图如下,以包含PropertySourceBootstrapConfiguration
在这里插入图片描述这一阶段同样需要创建自定义的PropertySourceLocator对象,如:NacosPropertySourceLocator实现类,其配置如下:

org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.alibaba.cloud.nacos.NacosConfigBootstrapConfiguration

PropertySourceBootstrapConfiguration的initialize(…)->PropertySourceLocator.locate()执行

阶段二的applyInitializers方法会调用PropertySourceBootstrapConfigurationinitialize方法,核心代码如下:

public void initialize(ConfigurableApplicationContext applicationContext) {
   	List<PropertySource<?>> composite = new ArrayList<>();
   	AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
   	boolean empty = true;
   	ConfigurableEnvironment environment = applicationContext.getEnvironment();
   	for (PropertySourceLocator locator : this.propertySourceLocators) {
   		Collection<PropertySource<?>> source = locator.locateCollection(environment);
   		if (source == null || source.size() == 0) {
   			continue;
   		}
   		List<PropertySource<?>> sourceList = new ArrayList<>();
   		for (PropertySource<?> p : source) {
   			if (p instanceof EnumerablePropertySource) {
   				EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) p;
   				sourceList.add(new BootstrapPropertySource<>(enumerable));
   			}
   			else {
   				sourceList.add(new SimpleBootstrapPropertySource(p));
   			}
   		}
   		logger.info("Located property source: " + sourceList);
   		composite.addAll(sourceList);
   		empty = false;
   	}
   	if (!empty) {
   		MutablePropertySources propertySources = environment.getPropertySources();
   		String logConfig = environment.resolvePlaceholders("${logging.config:}");
   		LogFile logFile = LogFile.get(environment);
   		for (PropertySource<?> p : environment.getPropertySources()) {
   			if (p.getName().startsWith(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
   				propertySources.remove(p.getName());
   			}
   		}
   		insertPropertySources(propertySources, composite);
   		reinitializeLoggingSystem(environment, logConfig, logFile);
   		setLogLevels(applicationContext, environment);
   		handleIncludedProfiles(environment);
   	}
   }

核心逻辑就是遍历所有propertySourceLocators执行其locate(……)方法,将自定义加载的CompositePropertySource然后追加到spring中的environment中。

2.3. 自定义PropertySourceLocator扩展environment实例

PropertySourceLocator实现如下:

public class CustPropertySourceLocator implements PropertySourceLocator {

   @Override
   public PropertySource<?> locate(Environment environment) {
       //可以从自己的配置中心获取数据
       Map<String, Object> configs =new HashMap<>();
       configs.put("metric.post.url","http://baidu.com");
       return new MapPropertySource("MY_CONFIG_CENTER",configs);
   }
}

spring.factories配置如下:

org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.example.nacoshttpproducer.configure.CustPropertySourceLocator
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值