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

Dil*_*ral 6 java spring spring-boot

问题陈述

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

我尝试过的

我知道Spring应用程序事件;实际上,在初始化Spring Context之后,我已经钩住 ContextRefreshedEvent来执行一些任务(在此阶段还初始化了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");
    }

}

Run Code Online (Sandbox Code Playgroud)

更新资料

正如M.Deinum在评论中所建议的那样,我尝试添加如下应用程序上下文初始化程序。它似乎也不起作用。

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

    }
Run Code Online (Sandbox Code Playgroud)

更新#2

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

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

尽管可以通过使用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) {
        ...
     }

}

Run Code Online (Sandbox Code Playgroud)
@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;
    }

}
Run Code Online (Sandbox Code Playgroud)

从文档,好像ApplicationEventsApplicationInitializers适合我的需要,但我不能,让他们工作,为我的问题发言。

Mat*_*t R 6

聚会有点晚,但希望我可以为您更新的问题陈述提供解决方案。

这将关注如何在类初始化为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());
    }
Run Code Online (Sandbox Code Playgroud)

注意缺少的@Component。其中一些事件之后,甚至可以发生 bean 实例化。如果你使用@Component,那么你会得到以下日志

I am a DataSourceSchemaCreatedEvent
I am a ContextRefreshedEvent
I am a ServletWebServerInitializedEvent
I am a ApplicationStartedEvent
I am a ApplicationReadyEvent
Run Code Online (Sandbox Code Playgroud)

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

总结一下,

  • 创建目录资源/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
Run Code Online (Sandbox Code Playgroud)

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


小智 1

我觉得您的主要问题是您需要分别维护应用程序属性和域相关属性。 从 spring 的角度来看,这并不重要,因为所有属性文件在加载到内存中后都会合并在一起。例如,您有两个包含一些属性的文件:

application.related=property1 # this is in application.properties
Run Code Online (Sandbox Code Playgroud)
domain.related=property2 # this is in domain-specific.properties
Run Code Online (Sandbox Code Playgroud)

当它们被加载之后,你会得到一个包含所有属性的大东西,如果我没记错的话,它是一个org.springframework.core.env.ConfigurableEnvironment实例。

然后你需要做的就是使用类似的东西注入你需要的属性@Value

对于主要问题,要将属性分离到不同的文件中,您只需要指定 spring 的spring.config.name属性(通过环境变量、命令行或以编程方式)。按照上面的例子,应该是spring.config.name=application,domain-specific

此外,如果您确实想要进行编程控制,您可以添加一个EnvironmentPostProcessor公开ConfigurableEnvironment实例的自定义。