spring - 自定义 Spring Boot Starter 库中读取当前属性并根据值设置新属性的最佳方式
问题描述
我正在 Spring Boot 之上构建一组库,打包为 Spring Boot Starters。我需要能够定义在应用程序启动时他们查看环境(即读取属性)的功能,并在此基础上设置其他属性值。我最初尝试在 an 中执行此操作,EnvironmentPostProcessor
但这似乎不是正确的地方,因为您遇到了并非所有PropertySource
s 都可用的订购问题。
我的具体用例是我想寻找该spring.boot.admin.client.url
属性的存在。如果未找到,则设置属性spring.boot.admin.client.enabled=false
。
在配置服务器端,我们为具有不同配置文件的不同应用程序提供不同的配置,其中一些设置 spring.boot.admin.client.url
为一个值,而另一些则没有。无论如何,应用程序本身都会捆绑spring-boot-admin-starter-client
依赖项。是否启用它只是由应用程序的运行时目标驱动。
我想知道正确的方法是什么?
我想到了一个ApplicationListener
或ApplicationContextInitializer
。
启动时触发两次,ApplicationContextInitializer
一次用于引导上下文,一次用于主上下文。被ApplicationEnvironmentPreparedEvent
触发两次(就在调用每个ApplicationContextInitializer
s 之前)。那时,配置服务属性源还不存在,我正在寻找的属性值也不存在。
然后触发了一堆不同ApplicationPreparedEvent
的 s & s(我数了 3 s(事件的相同实例 id),然后是 2 s(事件的相同实例 id),然后是 2 s(事件的相同实例 id) )。ApplicationStartedEvent
ApplicationPreparedEvent
ApplicationStartedEvent
ApplicationReadyEvent
2018 年 9 月 28 日更新
我想添加一些我也尝试过的测试。我从 Spring Initializr 构建了一个空白应用程序。我构建了一个ApplicationContextInitializr
和一个ApplicationListener
如下:
application.yml
:
spring:
application:
name: TestEventStartup
boot:
admin:
client:
url: http://localhost:8888
jackson:
serialization:
write-dates-as-timestamps: false
resources:
chain:
strategy:
content:
enabled: true
logging:
level:
root: warn
com.testeventstartup: debug
server:
compression:
enabled: true
mime-types: application/json,text/css,text/html
min-response-size: 2048
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
info:
git:
mode: full
public class AppContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println(String.format("Initializing Context - Got spring.boot.admin.client.url=%s", applicationContext.getEnvironment().getProperty("spring.boot.admin.client.url")));
}
}
public class AppListener implements ApplicationListener<SpringApplicationEvent> {
@Override
public void onApplicationEvent(SpringApplicationEvent event) {
System.out.println(String.format("Got event: %s", ToStringBuilder.reflectionToString(event)));
findEnvironment(event)
.ifPresent(environment -> System.out.println(String.format("%s: spring.boot.admin.client.url=%s", event.getClass().getSimpleName(), environment.getProperty("spring.boot.admin.client.url"))));
}
private Optional<Environment> findEnvironment(Object obj) {
return Optional.ofNullable(Optional.ofNullable(ReflectionUtils.findMethod(obj.getClass(), "getEnvironment"))
.map(method -> ReflectionUtils.invokeMethod(method, obj))
.orElseGet(() ->
Optional.ofNullable(ReflectionUtils.findMethod(obj.getClass(), "getApplicationContext"))
.map(method -> ReflectionUtils.invokeMethod(method, obj))
.flatMap(this::findEnvironment)
.orElse(null)
))
.filter(Environment.class::isInstance)
.map(Environment.class::cast);
}
}
/META-INF/spring.factories
:
org.springframework.context.ApplicationListener=\
com.testeventstartup.listener.AppListener
org.springframework.context.ApplicationContextInitializer=\
com.testeventstartup.listener.AppContextInitializer
当我在没有依赖项的情况下启动应用程序时org.springframework.cloud:spring-cloud-starter-config
,我在日志中看到了这一点:
Got event: org.springframework.boot.context.event.ApplicationStartingEvent@32709393[args={},timestamp=1538141292580]
Got event: org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent@399f45b1[environment=StandardServletEnvironment {activeProfiles=[], defaultProfiles=[default], propertySources=[StubPropertySource {name='servletConfigInitParams'}, StubPropertySource {name='servletContextInitParams'}, MapPropertySource {name='systemProperties'}, OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}, RandomValuePropertySource {name='random'}, OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application.yml] (document #1)'}]},args={},timestamp=1538141292628]
ApplicationEnvironmentPreparedEvent: spring.boot.admin.client.url=http://localhost:8888
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.0.5.RELEASE)
Initializing Context - Got spring.boot.admin.client.url=http://localhost:8888
2018-09-28 09:28:12.918 INFO 2534 --- [ main] c.t.TestEventStartupApplication : Starting TestEventStartupApplication on MAC-22XG8WL with PID 2534 (/Users/edeandre/workspaces/IntelliJ/test-event-startup/build/classes/java/main started by edeandre in /Users/edeandre/workspaces/IntelliJ/test-event-startup)
2018-09-28 09:28:12.920 DEBUG 2534 --- [ main] c.t.TestEventStartupApplication : Running with Spring Boot v2.0.5.RELEASE, Spring v5.0.9.RELEASE
2018-09-28 09:28:12.922 INFO 2534 --- [ main] c.t.TestEventStartupApplication : No active profile set, falling back to default profiles: default
Got event: org.springframework.boot.context.event.ApplicationPreparedEvent@6a370f4[context=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@49e53c76: startup date [Wed Dec 31 19:00:00 EST 1969]; root of context hierarchy,args={},timestamp=1538141292961]
ApplicationPreparedEvent: spring.boot.admin.client.url=http://localhost:8888
2018-09-28 09:28:15.385 INFO 2534 --- [ main] c.t.TestEventStartupApplication : Started TestEventStartupApplication in 2.809 seconds (JVM running for 3.32)
Got event: org.springframework.boot.context.event.ApplicationStartedEvent@7159139f[context=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@49e53c76: startup date [Fri Sep 28 09:28:12 EDT 2018]; root of context hierarchy,args={},timestamp=1538141295385]
ApplicationStartedEvent: spring.boot.admin.client.url=http://localhost:8888
Got event: org.springframework.boot.context.event.ApplicationReadyEvent@232cce0[context=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@49e53c76: startup date [Fri Sep 28 09:28:12 EDT 2018]; root of context hierarchy,args={},timestamp=1538141295387]
ApplicationReadyEvent: spring.boot.admin.client.url=http://localhost:8888
然后,当我org.springframework.cloud:spring-cloud-starter-config
在我的配置服务器中添加依赖项并连接时,这就是我在启动时看到的:
Got event: org.springframework.boot.context.event.ApplicationStartingEvent@23faf8f2[args={},timestamp=1538141399719]
Got event: org.springframework.boot.context.event.ApplicationStartingEvent@306279ee[args={},timestamp=1538141399814]
Got event: org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent@7cc0cdad[environment=StandardEnvironment {activeProfiles=[], defaultProfiles=[default], propertySources=[ConfigurationPropertySourcesPropertySource {name='configurationProperties'}, MapPropertySource {name='bootstrap'}, MapPropertySource {name='systemProperties'}, OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}, RandomValuePropertySource {name='random'}, MapPropertySource {name='springCloudClientHostInfo'}, OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/bootstrap.yml]'}]},args={},timestamp=1538141399815]
ApplicationEnvironmentPreparedEvent: spring.boot.admin.client.url=null
Initializing Context - Got spring.boot.admin.client.url=null
Got event: org.springframework.boot.context.event.ApplicationPreparedEvent@4e7912d8[context=org.springframework.context.annotation.AnnotationConfigApplicationContext@815b41f: startup date [Wed Dec 31 19:00:00 EST 1969]; root of context hierarchy,args={},timestamp=1538141400176]
ApplicationPreparedEvent: spring.boot.admin.client.url=null
2018-09-28 09:30:00.183 INFO 2568 --- [ main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@815b41f: startup date [Fri Sep 28 09:30:00 EDT 2018]; root of context hierarchy
2018-09-28 09:30:00.362 INFO 2568 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'configurationPropertiesRebinderAutoConfiguration' of type [org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration$$EnhancerBySpringCGLIB$$f1570cbf] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
Got event: org.springframework.boot.context.event.ApplicationPreparedEvent@4e7912d8[context=org.springframework.context.annotation.AnnotationConfigApplicationContext@815b41f: startup date [Fri Sep 28 09:30:00 EDT 2018]; root of context hierarchy,args={},timestamp=1538141400176]
ApplicationPreparedEvent: spring.boot.admin.client.url=null
Got event: org.springframework.boot.context.event.ApplicationStartedEvent@460ebd80[context=org.springframework.context.annotation.AnnotationConfigApplicationContext@815b41f: startup date [Fri Sep 28 09:30:00 EDT 2018]; root of context hierarchy,args={},timestamp=1538141400608]
ApplicationStartedEvent: spring.boot.admin.client.url=null
Got event: org.springframework.boot.context.event.ApplicationReadyEvent@16fdec90[context=org.springframework.context.annotation.AnnotationConfigApplicationContext@815b41f: startup date [Fri Sep 28 09:30:00 EDT 2018]; root of context hierarchy,args={},timestamp=1538141400609]
ApplicationReadyEvent: spring.boot.admin.client.url=null
Got event: org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent@e8df99a[environment=StandardServletEnvironment {activeProfiles=[], defaultProfiles=[default], propertySources=[ConfigurationPropertySourcesPropertySource {name='configurationProperties'}, StubPropertySource {name='servletConfigInitParams'}, StubPropertySource {name='servletContextInitParams'}, MapPropertySource {name='systemProperties'}, OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}, RandomValuePropertySource {name='random'}, OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application.yml] (document #1)'}, ExtendedDefaultPropertySource {name='defaultProperties'}, MapPropertySource {name='springCloudClientHostInfo'}]},args={},timestamp=1538141399781]
ApplicationEnvironmentPreparedEvent: spring.boot.admin.client.url=http://localhost:8888
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.0.5.RELEASE)
Initializing Context - Got spring.boot.admin.client.url=https://myspringbootadminserver.mycompany.com
2018-09-28 09:30:01.683 INFO 2568 --- [ main] c.t.TestEventStartupApplication : No active profile set, falling back to default profiles: default
Got event: org.springframework.boot.context.event.ApplicationPreparedEvent@5c87bfe2[context=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@2eee3069: startup date [Wed Dec 31 19:00:00 EST 1969]; parent: org.springframework.context.annotation.AnnotationConfigApplicationContext@815b41f,args={},timestamp=1538141401690]
ApplicationPreparedEvent: spring.boot.admin.client.url=https://myspringbootadminserver.mycompany.com
Got event: org.springframework.boot.context.event.ApplicationPreparedEvent@5c87bfe2[context=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@2eee3069: startup date [Fri Sep 28 09:30:01 EDT 2018]; parent: org.springframework.context.annotation.AnnotationConfigApplicationContext@815b41f,args={},timestamp=1538141401690]
ApplicationPreparedEvent: spring.boot.admin.client.url=https://myspringbootadminserver.mycompany.com
Got event: org.springframework.boot.context.event.ApplicationPreparedEvent@5c87bfe2[context=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@2eee3069: startup date [Fri Sep 28 09:30:01 EDT 2018]; parent: org.springframework.context.annotation.AnnotationConfigApplicationContext@815b41f,args={},timestamp=1538141401690]
ApplicationPreparedEvent: spring.boot.admin.client.url=https://myspringbootadminserver.mycompany.com
2018-09-28 09:30:03.858 INFO 2568 --- [ main] c.t.TestEventStartupApplication : Started TestEventStartupApplication in 4.143 seconds (JVM running for 4.88)
Got event: org.springframework.boot.context.event.ApplicationStartedEvent@3dfa819[context=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@2eee3069: startup date [Fri Sep 28 09:30:01 EDT 2018]; parent: org.springframework.context.annotation.AnnotationConfigApplicationContext@815b41f,args={},timestamp=1538141403859]
ApplicationStartedEvent: spring.boot.admin.client.url=https://myspringbootadminserver.mycompany.com
Got event: org.springframework.boot.context.event.ApplicationStartedEvent@3dfa819[context=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@2eee3069: startup date [Fri Sep 28 09:30:01 EDT 2018]; parent: org.springframework.context.annotation.AnnotationConfigApplicationContext@815b41f,args={},timestamp=1538141403859]
ApplicationStartedEvent: spring.boot.admin.client.url=https://myspringbootadminserver.mycompany.com
Got event: org.springframework.boot.context.event.ApplicationReadyEvent@4ce94d2f[context=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@2eee3069: startup date [Fri Sep 28 09:30:01 EDT 2018]; parent: org.springframework.context.annotation.AnnotationConfigApplicationContext@815b41f,args={},timestamp=1538141403861]
ApplicationReadyEvent: spring.boot.admin.client.url=https://myspringbootadminserver.mycompany.com
Got event: org.springframework.boot.context.event.ApplicationReadyEvent@4ce94d2f[context=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@2eee3069: startup date [Fri Sep 28 09:30:01 EDT 2018]; parent: org.springframework.context.annotation.AnnotationConfigApplicationContext@815b41f,args={},timestamp=1538141403861]
ApplicationReadyEvent: spring.boot.admin.client.url=https://myspringbootadminserver.mycompany.com
连接到配置服务器后,您会注意到一些事情:
ApplicationContextInitializer
触发两次 - 一次用于引导程序,一次用于“主”应用 程序- 当“主”应用程序触发时,所有属性都已解析
- 各种生命周期事件被多次触发
- 如果你仔细观察,同一个事件实例会被触发多次(即
org.springframework.boot.context.event.ApplicationPreparedEvent@732c2a62
(触发两次)、org.springframework.boot.context.event.ApplicationPreparedEvent@6b9ce1bf
(触发 3 次)、org.springframework.boot.context.event.ApplicationStartedEvent@1f6917fb
(触发两次)、org.springframework.boot.context.event.ApplicationReadyEvent@41eb94bc
(触发两次)
- 如果你仔细观察,同一个事件实例会被触发多次(即
我知道生命周期事件应该被触发两次,一次用于引导上下文和“主”上下文,但是在每个上下文的生命周期内,为什么同一个事件实例会被触发多次?
此外 - 这整件事让我觉得我的解决方案应该是我应该使用 anApplicationContextInitializer
作为我的要求的最佳解决方案(检查当前Environment
的属性值的当前状态,然后有条件地设置一个属性值/添加一个附加PropertySource
。如果我的要求只是以编程方式添加属性值,那么继续使用EnvironmentPostProcessor
可能是更好的解决方案。
现在的缺点是逻辑ApplicationContextInitializer
发生了两次,每个上下文一次。有没有办法让一个ApplicationContextInitializer
理解它在哪个上下文中运行,然后只在“主要”上下文中真正做我们想要的?
有人可以权衡我的观察吗?@安迪威尔金森?
解决方案
您可以使用 Spring 应用程序事件和侦听器。在您的情况下,您可以使用ApplicationEnvironmentPreparedEvent
如下:
public class OverridePropertiesListener implements
ApplicationListener<ApplicationEnvironmentPreparedEvent> {
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
Properties props = new Properties();
props.put("myProperty", "<my value>");
environment.getPropertySources().addFirst(new PropertiesPropertySource("myProps", props));
}
}
有关侦听器的更多信息:应用程序事件和侦听器
推荐阅读
- python - Pyinstaller 安装成功但无法正常工作
- aws-lambda - 订阅 sns fifo 主题的 lambda 函数
- apache - 您无权访问请求的目录。没有索引文档或目录被读保护。xampp -phpmyadmin
- android - 使用 adb 命令从 youtube 剪辑 stat 以获取 nerds 信息
- pdf - Uipath 需要来自 abbyy 的许可证
- azure - 通过 CLI 启用分析存储时间
- html - CSS:如何根据背景颜色更改自定义滚动条颜色
- php - 选择另一个表值时回显表值
- javascript - 从 .json 文件返回 JSON 对象的函数
- kubernetes - Kubernetes Istio 配置与 Keycloak 用于 JWT 令牌验证