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

共11个回答, 标签: java spring spring-boot

问题陈述

我想在 bean 初始化之前,从类路径中的属性文件或外部位置加载属性。因为这些属性也是 Bean 初始化的一部分。我不能自动装配这些属性从春天的标准应用程序.属性或者它是自定义的,因为相同的属性文件需要被多个 depoyables 访问。

我试过的

我知道Spring 应用程序事件; 事实上我已经在勾搭了Contextrefreshevent在 Spring 上下文被初始化后执行一些任务 (bean 也在这个阶段被初始化)。

对于我的问题陈述,从 Spring 文档的描述ApplicationEnvironmentPreparedEvent看起来很有希望,但我不能让钩子工作。


@ SpringBootApplication
公共类应用 {

公共静态 void main (String [] args) 抛出 IOException {
SpringApplication.run (App.class,args);
}


@ EventListener
公共无效启动 (contextrefreshevent 事件) {
System.out.println (“contextrefreshevent”);//作品
}

@ EventListener
公共 void onShutDown (ContextClosedEvent 事件) {
System.out.println (“ContextClosedEvent”);//作品
}

@ EventListener
公共 void onEvent6 (ApplicationStartedEvent 事件) {
System.out.println (“ApplicationStartedEvent”);//工作,但在 contextrefreshevent 之后
}


@ EventListener
公共 void onEvent3 (ApplicationReadyEvent 事件) {
System.out.println (“ApplicationReadyEvent”);//工作正常,但在 contextrefreshevent 之后
}


公共 void onEvent1 (ApplicationEnvironmentPreparedEvent 事件) {
System.out.println (“ApplicationEnvironmentPreparedEvent”);//不起作用
}


@ EventListener
公共 void onEvent2 (ApplicationContextInitializedEvent 事件) {
System.out.println (“ApplicationContextInitializedEvent”);//不起作用
}


@ EventListener
公共无效 onEvent4 (ApplicationContextInitializedEvent 事件) {
System.out.println (“ApplicationContextInitializedEvent”);
}

@ EventListener
公共 void onEvent5 (ContextStartedEvent 事件) {
System.out.println (“ContextStartedEvent”);
}

}

更新

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

公共静态 void main (字符串 [] args) {
新 SpringApplicationBuilder ()
。来源 (App.class)
。初始化器 (applicationContext-> {
System.out.println (“内部自定义应用程序初始值设定项”);
})
。运行 (args);

}

更新 #2

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

而正如评论和答案中所述,同样可以使用 Spring Boot 的外化配置和配置文件来完成。但是,我需要分别维护应用程序属性和域相关属性。基本域属性预计至少有 100 个属性,这些属性将随着时间的推移而增长。它们都有一个不同环境 (dev 、 SIT 、 UAT 、 Production) 的属性文件,将覆盖一个或多个

第1个答案

我认为 Spring Cloud 配置是您的问题陈述的完美解决方案。详细文档在这里

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

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

第2个答案

参加聚会有点晚,但希望我能为你更新的问题陈述提供一个解决方案。

这将集中在问题上如何在类被初始化为 bean 并放入 Spring IoC 容器之前运行一些代码

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

只有在启动所有 bean 后才调用这些注释,因为这些注释由EventListenerMethodProcessor只有当上下文准备好时才触发 (参见 SmartInitializingSingleton # afterSingletonsInstantiated)

因此,在上下文准备好之前发生的一些事件。例如,ContextStartedEvent,ApplicationContextInitializedEvent 不会到达你的听众。

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

@ Slf4j
公共类 AllEvent 实现了 ApplicationListener{

@ 覆盖
公共 void onApplicationEvent (最终 ApplicationEvent 事件) {
Log.info (“我是 {}”,event.getClass ().Getsimpleename ());
}

注意缺少 @ 组件。甚至可以发生 bean 实例之后其中一些事件。如果您使用 @ Component,那么您将获得以下日志

我是 DataSourceSchemaCreatedEvent
我是 contextrefreshevent
我是 ServletWebServerInitializedEvent
我是一个应用程序 startedevent
我是一个 ApplicationReadyEvent

仍然比注释性监听器更好、更即时,但仍然不会收到初始化事件。为此,你需要做的是按照找到的说明在这里

总结一下,

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

结果:-

我是 ApplicationContextInitializedEvent
我是一个应用程序 preparedevent
我是 DataSourceSchemaCreatedEvent
我是 contextrefreshevent
我是 ServletWebServerInitializedEvent
我是一个应用程序 startedevent
我是一个 ApplicationReadyEvent

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

第3个答案

创建一个将成为属性存储库的 bean,并将其注入需要属性的其他 bean 中。

在你的例子中,而不是有静态方法MyPropUtil, make the class a bean itself with instance methods. Initialize Map repository in the initialize method annotated with @PostConstruct

@ 组件
公共类 MyPropUtil {

私有静态最终字符串 DOMAIN_KEY = "domain";
私有静态最终字符串 APP_KEY = "app";

私人地图存储库;

@ PostConstruct
公共无效 init () {
属性 domainProps = 新属性 ();
//DomainProps.load ();
Repository.put (DOMAIN_KEY,domainProps);

属性 appProps = 新属性 ();
//AppProps.load ();
Repository.put (APP_KEY,appProps);
}

公共字符串 getDomainProperty (字符串键) {
返回存储库。get (DOMAIN_KEY)。getProperty (key);
}

公共字符串 getAppProperty (字符串键) {
返回存储库。get (APP_KEY)。getProperty (key);
}

公共字符串 getAndAddBasePathToAppPropertyValue (字符串密钥) {
//...
}
}

@ 配置
公共类 MyComponent {

@ 自动装配
私人 MyPropUtil myPropUtil;

@ Bean
公共类 getSomeClassBean () {
SomeClass obj = 新的 SomeClass ();
Obj.someProp1 (myPropUtil.getDomainProperty ("domainkey1"));
Obj.someProp2 (myPropUtil.getAppProperty (“appkey1”));
对于一些属性
Obj.someProp2 (myPropUtil.getAndAddBasePathToAppPropertyValue (“some.relative.path.value”);
//...
返回 obj;
}
}

或者你可以注射MyPropUtil directly to the SomeClass:

@ 组件
公共类 SomeClass {

私人最终字符串 someProp1;
私人最终字符串 someProp2;

@ 自动装配
公共类 (MyPropUtil myPropUtil) {
This.someProp1 = myPropUtil.getDomainProperty ("domainkey1");
This.someProp2 = myPropUtil.getAppProperty (“appkey1”);
}
//...
}
第4个答案

正如在这篇文章您可以添加这样的外部属性文件;

公共组织 PropertySourcesPlaceholderConfigurer () {
PropertySourcesPlaceholderConfigurer 属性 = 新 PropertySourcesPlaceholderConfigurer ();
Properties.setLocation (新文件系统资源 (“/Users/home/conf.properties”);
属性。setIgnoreResourceNotFound (假);
返回属性;
}

如果你不想使用这个,只需用 jackson 读取属性文件,并将属性设置为System.setProperty("key","value") in the main春天开始前的方法。

如果你也不想使用这个,看看BeanPostProcessor#postProcessBeforeInitialization方法。它在 spring 初始化 bean 属性之前运行。

第5个答案

我可能错过了你所说的 “bean 初始化” 到底是什么意思,可能是一个问题中这样一个 bean 的例子可能是有益的。

我认为你应该区分阅读部分和 bean 初始化的属性。 到 bean 初始化时,属性已经被读取并可用。如果你愿意,这是春天魔法的一部分。

这就是为什么下面的代码工作例如:

@ 组件
公共类 MySampleBean {

公共 MySampleBean (@ Value (“$ {some.prop}” 字符串 someProp) {.}
}

这些财产从哪里来并不重要 (春季开机定义这些地方有许多不同的方式,它们之间有优先权),这将在 bean 的初始化发生之前发生。

现在,让我们回到你原来的问题:

我想从类路径中的属性文件或外部位置加载属性 (在 bean 被初始化之前-无关)。

在 spring/spring-boot 中,有一个基本上允许创建文件的配置文件的概念application-foo.properties (or yaml) and when you load with --spring.profiles.active=foo it will automatically load properties defined in this application-foo.properties in addition to the regular application.properties

所以你可以把你想 “从类路径加载” 的东西放在application-local.properties (the word local is for the sake of example only) and start the application with --spring.profiles.active=local(在部署脚本、 docker 文件或其他文件中)

如果要从外部位置 (类路径之外) 运行属性,可以使用:--spring.config.location=

请注意,即使您将一些属性放入常规application.properties and still use --spring.config.location对于相同的键值对,它们将优先于类路径中的属性。

或者,您只能使用--sring.profiles.active=local or remote并且根本不使用配置位置。

第6个答案

可以直接在命令行中配置外部位置:

Java-jar 应用.jar-spring.config.location = 文件://用户/主页/配置/外部属性
第7个答案

你可以使用WebApplicationInitializer在之前执行代码类被初始化为 bean

公共类 MyWebInitializer 实现 WebApplicationInitializer {
@ 覆盖
公共 void onStartup (ServletContext servletContext) 抛出 ServletException {
Var ctx = 新注释 configwebapplicationcontext ();
Ctx.register (WebConfig.class);
Ctx.setServletContext (servletContext);

我们创建一个 AnnotationConfigWebApplicationContext 并使用 register () 注册一个 web 配置文件。

第8个答案

你可以检查一下PropertySource可能会帮助你。

示例:

@ PropertySource ({“类路径: 持久性/持久性。属性”})

您可以在每个@Configuration or @SpringBootApplication豆子

第9个答案

听起来你想对 bean 初始化的一部分拥有一些所有权。通常人们会想到春天完成_Bean 配置,但在您的情况下,可能更容易将 Spring 视为开始_它。

所以,你的 bean 有一些属性要配置,和一些你想要的春天要配置。只需注释您想要 Spring 配置的 (使用@Autowire or @Inject, or whatever flavour you prefer), and then take over the control from there, using @PostConstruct or InitializingBean

类 MyMultiStageBoosterRocket {

私人 Foo foo;
私人酒吧酒吧;
私人猫猫;

@ Autowire
公共 MyMultiStageBoosterRocket (Foo foo,酒吧) {
这个。foo = foo;
这个.bar = bar'
}

//在弹簧注射后被调用,但是在豆之前被调用
//在上下文中注册
@ PostConstruct
Public void postConstruct () {
//你的魔法属性从你想要的任何来源注入
ServiceLoaderLoader = ServiceLoader.load (CatProvider.class);
//等.
}
}

当然,您的属性解析机制需要以某种方式静态可用,但这似乎适合您MyPropUtil例如。

获取更多的参与,你开始直接查看 Bean Post 处理器 (@PostConstruct是一个简单的变种)。

这里有一个以前的问题,有一个有用的答案Spring BeanPostProcessor 到底是如何工作的?,但是为了简单起见,你会做一些像

公共类 CustomBeanPostProcessor 实现 BeanPostProcessor {

@ 覆盖
公共对象 postProcessBeforeInitialization (对象 bean,字符串 beanName)
抛出 BeansException {

//Fixme: 检测这个 bean 是否需要花哨的初始化

返回 bean;
}
}

显然@PostProcess, or InitializingBean更简单,但自定义后处理器有一个很大的优势.它可以与其他弹簧管理豆注射。这意味着你可以管理你的财产注入任何东西,并且仍然手动管理实际的注入过程。

第10个答案

试着在 main 之前加载你需要的一切

SpringApplication.run ()

呼叫

公共静态 void main (字符串 [] args) {
在 spring 初始化之前
TimeZone.setDefault (TimeZone.getTimeZone ("UTC"));
SpringApplication.run (CyberRiskApplication.class,args);
}
第11个答案

您可以使用 ApplicationEnvironmentPreparedEvent,但不能使用 EventListener 注释进行配置。因为这个时候豆 drfinitions 没有加载。请参见下面的链接,了解如何协调此事件。 Https://www.thetechnojournals.com/2019/10/spring-boot-application-events.html

相关问题

Java 是 "逐项传递" 还是 "按值传递"? 如何比较 Java 中的字符串? 什么是 Null Pointerexception, 我如何修复它? Spring Boot 2-在 bean 初始化之前做一些事情 获取 Java 中的所有异常并远程发送 如果 Spring 可以在 @ Configuration 类中成功拦截类内函数调用,为什么它在常规 bean 中不支持它? 为什么我在 pom.xml 的第一行遇到未知错误? 有条件地向 HashMap 添加项目的有效方法