软件对配置的使用,从启动参数、本地配置文件、数据库配置,到外部独立的配置中心。一步一步朝着开放和灵活在迈进。
使用配置中心,软件启动运行的参数都抽离到单独的分布式配置服务中心。Spring Boot在一开始就提供了对配置扩展的支持,非常简单就扩展就适应了。
外部化配置的扩展可以参见 Spring Boot 指定外部启动配置文件, spring boot的官方文档Externalized Configuration章节
用了diamond的配置中心,发现@Value属性配置注入未生效。
@Value("${xx.url}")
private String url;
环境变量不存在对应属性,抛出异常:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'XxBean': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'xx.url' in value "${xx.url}"
具体的加载过程参见:
AutowiredAnnotationBeanPostProcessor
PropertyPlaceholderConfigurer#resolvePlaceholder
有两种方案,一种是实现ApplicationContextInitializer接口
public class ConfigCenterInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
private int order = Ordered.LOWEST_PRECEDENCE - 9;
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return this.order;
}
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
// 加载远程配置省略
Properties prop = loadRemoteProperties();
ConfigurableEnvironment env = applicationContext.getEnvironment();
env.getPropertySources().addLast(new PropertiesPropertySource("remote-prop", prop));
}
}
在META-INF/spring.factories添加如下配置:
org.springframework.context.ApplicationContextInitializer=\
x.xx.ConfigCenterInitializer
另一种是实现EnvironmentPostProcessor接口
public class ConfigPostProcessor implements EnvironmentPostProcessor, Ordered {
@Override
public void postProcessEnvironment(ConfigurableEnvironment env, SpringApplication application) {
// 加载远程配置省略
Properties prop = loadRemoteProperties();
env.getPropertySources().addLast(new PropertiesPropertySource("remote-prop", prop));
}
@Override
public int getOrder() {
return ConfigFileApplicationListener.DEFAULT_ORDER - 1;
}
}
在META-INF/spring.factories添加如下配置:
org.springframework.boot.env.EnvironmentPostProcessor=\
x.xx.ConfigPostProcessor
两种方式都是在spring boot 启动的时候,在Bean加载之前,加载配置合并入Environment中的:
// SpringApplication
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
this.configureHeadlessProperty();
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// EnvironmentPostProcessor 在prepareEnvironment中
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
Banner printedBanner = this.printBanner(environment);
context = this.createApplicationContext();
new FailureAnalyzers(context);
// ApplicationContextInitializer 在prepareContext中
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
this.refreshContext(context);
this.afterRefresh(context, applicationArguments);
listeners.finished(context, (Throwable)null);
stopWatch.stop();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, listeners, (FailureAnalyzers)analyzers, var9);
throw new IllegalStateException(var9);
}
}
可以看到EnvironmentPostProcessor加载在ApplicationContextInitializer之前,时机非常早,Spring的Banner都还没打印,日志组件都未初始化的时候。
ApplicationContextInitializer 在SpringApplication#prepareContext() -> applyInitializers()
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);
}
}
ApplicationContextInitializer在Spring Boot中使用很广,看看Spring Boot中的spring.factories就可以看到:
#spring-boot
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
#spring-boot-autoconfigure
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
在Spring Boot中使用ApplicationContextInitializer,还有2种方式:
1、使用context.initializer.classes
配置项
application.properties 中
context.initializer.classes=XxApplicationContextInitializer
会由DelegatingApplicationContextInitializer加载;
2、使用SpringApplication.addInitializers()
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(DemoApplication.class);
springApplication.addInitializers(new XxApplicationContextInitializer());
springApplication.run(args);
}
}
在SpringApplication#prepareEnvironment() -> listeners.environmentPrepared(environment) .发布了一个ApplicationEnvironmentPreparedEvent 事件。
ConfigFileApplicationListener 会监听到此事件, onApplicationEvent() -> onApplicationEnvironmentPreparedEvent()
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
postProcessors.add(this);
AnnotationAwareOrderComparator.sort(postProcessors);
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(),
event.getSpringApplication());
}
}
了解了扩展外部配置是在Bean加载之前,修改Environment。按照上述分析的过程,发现还有许多地方也可以扩展并实现配置的修改:
基于ApplicationListener
基于SpringApplicationRunListener#environmentPrepared
基于SpringApplicationRunListener#contextPrepared
基于SpringApplicationRunListener#contextLoaded
SpringApplicationRunListener 的几个接口,可以回顾下SpringApplication#prepareContext()
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
applyInitializers(context);// 这里调用了ApplicationContextInitializer#initialize
listeners.contextPrepared(context); // 这里调用了 SpringApplicationRunListener#contextPrepared
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
if (printedBanner != null) {
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
}
// Load the sources
Set<Object> sources = getSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[sources.size()]));
listeners.contextLoaded(context); //这里调用了 SpringApplicationRunListener#contextLoaded
}
参考:
Springboot–扩展外部化配置(一)
Springboot–扩展外部化配置(一)
Springboot–扩展外部化配置(三)
SpringBoot核心机制解读二ApplicationContextInitializer
SpringBoot扩展点之EnvironmentPostProcessor