Spring Boot 2 - 在豆子初始化之前做一些事情

2022-09-04 21:05:14

问题陈述

我想在初始化 Bean 之前从类路径中的属性文件或外部位置加载属性。这些属性也是 Bean 初始化的一部分。我无法自动连接Spring标准apprament.properties或其自定义的属性,因为相同的属性文件必须可由多个可部署对象访问。

我尝试了什么

我知道春季应用事件;事实上,我已经挂接了 ContextRefreshedEvent,以便在 Spring Context 初始化后执行一些任务(Bean 在此阶段也已初始化)。

对于我的问题陈述,从Spring Docs的描述来看,看起来很有希望,但钩子不起作用。ApplicationEnvironmentPreparedEvent


@SpringBootApplication
public class App {

    public static void main(String[] args) throws IOException {
        SpringApplication.run(App.class, args);
    }


    @EventListener
    public void onStartUp(ContextRefreshedEvent event) {
        System.out.println("ContextRefreshedEvent");    // WORKS
    }

    @EventListener
    public void onShutDown(ContextClosedEvent event) {
        System.out.println("ContextClosedEvent");   // WORKS
    }

    @EventListener
    public void onEvent6(ApplicationStartedEvent event) {
        System.out.println("ApplicationStartedEvent");  // WORKS BUT AFTER ContextRefreshedEvent
    }


    @EventListener
    public void onEvent3(ApplicationReadyEvent event) {
        System.out.println("ApplicationReadyEvent");    // WORKS WORKS BUT AFTER ContextRefreshedEvent
    }


    public void onEvent1(ApplicationEnvironmentPreparedEvent event) {
        System.out.println("ApplicationEnvironmentPreparedEvent");  // DOESN'T WORK
    }


    @EventListener
    public void onEvent2(ApplicationContextInitializedEvent event) {
        System.out.println("ApplicationContextInitializedEvent");   // DOESN'T WORK
    }


    @EventListener
    public void onEvent4(ApplicationContextInitializedEvent event) {
        System.out.println("ApplicationContextInitializedEvent");
    }

    @EventListener
    public void onEvent5(ContextStartedEvent event) {
        System.out.println("ContextStartedEvent");
    }

}

更新

正如 M.Deinum 在注释中建议的那样,我尝试添加一个应用程序上下文初始值设定项,如下所示。它似乎也不起作用。

    public static void main(String[] args) {
        new SpringApplicationBuilder()
                .sources(App.class)
                .initializers(applicationContext -> {
                    System.out.println("INSIDE CUSTOM APPLICATION INITIALIZER");
                })
                .run(args);

    }

更新 #2

虽然我的问题陈述是关于加载属性的,但我的问题/好奇心实际上是关于如何在类初始化为bean并放入Spring IoC容器之前运行一些代码。现在,这些bean在初始化期间需要一些属性值,由于以下原因,我不能/不想自动连接它们:

如评论和答案中所述,可以使用Spring Boot的外部化配置和配置文件完成相同的操作。但是,我需要分别维护应用程序属性和与域相关的属性。基域属性应至少具有 100 个属性,并且该属性会随着时间的推移而增长。应用程序属性和与域相关的属性都有一个用于不同环境(dev、SIT、UAT、Production)的属性文件。属性文件将覆盖一个或多个基属性。这是 8 个属性文件。现在,需要将同一应用部署到多个地理位置。这使得它的属性文件是地理区域的数量。我希望所有属性文件都存储在一个公共模块中,以便不同的可部署对象可以访问它们。环境和地理位置在运行时将作为系统属性已知。8 * nn

虽然这些可以通过使用Spring配置文件和优先级顺序来实现,但我希望对它有一个编程控制(我也会维护我自己的属性存储库)。例如。我会编写一个名为的便利实用程序并像这样访问它们:MyPropUtil

public class MyPropUtil {
     private static Map<String, Properties> repository;

     public static initialize(..) {
         ....
     }

     public static String getDomainProperty(String key) {
        return repository.get("domain").getProperty(key);
     }

     public static String getAppProperty(String key) {
         return repository.get("app").getProperty(key);
     }

     public static String getAndAddBasePathToAppPropertyValue(String key) {
        ...
     }

}

@Configuration
public class MyComponent {

    @Bean
    public SomeClass getSomeClassBean() {
        SomeClass obj = new SomeClass();
        obj.someProp1(MyPropUtil.getDomainProperty('domainkey1'));
        obj.someProp2(MyPropUtil.getAppProperty('appkey1'));
        // For some properties
         obj.someProp2(MyPropUtil.getAndAddBasePathToAppPropertyValue('some.relative.path.value'));
        ....
        return obj;
    }

}

从文档来看,它似乎符合我的需求,但我无法让它们为我的问题陈述工作。ApplicationEventsApplicationInitializers


答案 1

派对有点晚了,但希望我能为你更新的问题陈述提供解决方案。

这将重点关注如何在类初始化为bean并放入Spring IoC容器之前运行一些代码的问题。

我注意到的一个问题是,您正在通过@EventListener注释定义应用程序事件。

只有在启动所有bean后才会调用它们,因为这些注释由EventListenerMethodProcessor处理,该处理器仅在上下文准备就绪时触发(请参阅SmartInitializingSingleton#afterSingletonsInstantiated)

因此,在上下文准备就绪之前发生的一些事件。例如,ContextStartedEvent,ApplicationContextInitializedEvent不会让它进入你的听众。

相反,您可以做的是直接扩展这些事件的接口。

@Slf4j
public class AllEvent implements ApplicationListener<ApplicationEvent> {

    @Override
    public void onApplicationEvent(final ApplicationEvent event) {
        log.info("I am a {}", event.getClass().getSimpleName());
    }

请注意缺少的@Component。甚至 Bean 实例化也可能在其中一些事件发生发生。如果您使用@Component,则会收到以下日志

I am a DataSourceSchemaCreatedEvent
I am a ContextRefreshedEvent
I am a ServletWebServerInitializedEvent
I am a ApplicationStartedEvent
I am a ApplicationReadyEvent

仍然比注释性侦听器更好,更即时,但仍然不会收到初始化事件。为此,您需要做的是按照此处的说明进行操作

总而言之,

  • 创建目录资源/META-INF
  • 创建文件 spring.factories
  • org.springframework.context.ApplicationListener=full.path.to.my.class.AllEvent

结果:-

I am a ApplicationContextInitializedEvent
I am a ApplicationPreparedEvent
I am a DataSourceSchemaCreatedEvent
I am a ContextRefreshedEvent
I am a ServletWebServerInitializedEvent
I am a ApplicationStartedEvent
I am a ApplicationReadyEvent

特别是,ApplicationContextInitializedEvent 应该允许您执行所需的任何每个实例化任务。


答案 2

我认为Spring Cloud Config是您问题陈述的完美解决方案。详细文档请点击此处

Spring Cloud Config为分布式系统中的外部化配置提供服务器端和客户端支持。

因此,您可以轻松管理应用程序外部的配置,并且所有实例都将使用相同的配置。


推荐