自定义Spring Boot Starter库中读取当前属性并根据值设置新属性的最佳方法

时间:2018-09-26 11:30:57

标签: spring spring-boot

我正在Spring Boot的基础上构建一组库,这些库打包为Spring Boot Starters。我需要能够定义功能,以便在应用程序启动时它们可以查看环境(即读取属性),并以此为基础设置其他属性值。我最初尝试在EnvironmentPostProcessor中进行此操作,但这似乎不是正确的地方,因为您遇到的问题是尚无法使用所有PropertySource的问题。

我的特定用例是我要寻找spring.boot.admin.client.url属性的存在。如果找不到,则设置属性spring.boot.admin.client.enabled=false

在配置服务器端,对于具有不同配置文件的不同应用程序,我们具有不同的配置,其中一些将spring.boot.admin.client.url设置为一个值,而另一些则没有。应用程序本身捆绑了spring-boot-admin-starter-client依赖项。是否启用它仅由应用程序的运行时目标决定。

我想知道什么是正确的方法?

我想到了ApplicationListenerApplicationContextInitializer

ApplicationContextInitializer在启动时被触发两次,一次用于引导上下文,一次用于主上下文。 ApplicationEnvironmentPreparedEvent被触发两次(正好在每个ApplicationContextInitializer的调用之前)。到那时,配置服务属性源还不存在,我正在寻找的属性值还不存在。

然后发射了一堆不同的ApplicationPreparedEventApplicationStartedEvent(我算了3 ApplicationPreparedEvent(事件的相同实例ID),然后是2 {{1} }(事件的相同实例ID),然后是2 ApplicationStartedEvent(事件的相同实例ID)。

已于2018年9月28日更新

我也想补充一些我尝试过的测试。我从Spring Initializr构建了一个空白应用程序。我按照以下步骤构建了ApplicationReadyEventApplicationContextInitializr

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

连接到配置服务器后,您会注意到一些事情:

  1. 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 会触发两次-一次引导,一次“ main”应用
    • 在“主”应用程序触发时,所有属性均已解析
  2. 各种lifecycle events被触发多次
    • 不过,如果仔细观察,会多次触发同一事件实例(即ApplicationContextInitializer(触发两次),org.springframework.boot.context.event.ApplicationPreparedEvent@732c2a62(触发3次),org.springframework.boot.context.event.ApplicationPreparedEvent@6b9ce1bf(触发两次) ),org.springframework.boot.context.event.ApplicationStartedEvent@1f6917fb(触发两次)

我知道lifecycle events应该被触发两次,一次用于引导程序上下文和“主”上下文,但是在每个上下文的生命周期内,为什么同一事件实例多次触发?

此外-整件事使我朝着解决方案的方向走,我应该使用org.springframework.boot.context.event.ApplicationReadyEvent@41eb94bc作为满足我要求的最佳解决方案(检查当前ApplicationContextInitializer状态值,然后有条件地设置一个属性值/添加一个额外的Environment。如果我的需求只是以编程方式添加属性值,那么继续使用PropertySource可能是更好的解决方案。

现在的缺点是EnvironmentPostProcessor中的逻辑发生两次,每种情况下发生一次。 ApplicationContextInitializer是否有办法了解它在哪个上下文中运行,然后才真正在“主”上下文中执行我们想要的操作?

有人可以在我的观察中权衡一下吗? @AndyWilkinson?

1 个答案:

答案 0 :(得分:0)

您可以使用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));
   }
}

有关监听器的更多信息:Application Events and Listeners