第一、业务场景
在 SpringBoot 项目启动成功后,根据业务需求的特殊性,在某个组件被调用前,我们可能还需要做一些准备工作。
比如把配置文件properties 里的变量赋值给组件里的静态变量,从而实例化某个对象,加载某个资源等等。
这些准备工作都有一个特点,就是需要在组件被容器实例化后,在组件其他任何方法被调用之前执行。
如果不做这一步,那么组件里的静态成员变量,就获取不到配置文件properties 里的变量,在后续使用静态变量就会为 null,从而影响程序的正常运行和使用。
最终导致用户无法正常使用某些功能,甚至在使用的过程中产生严重bug,给用户带来经济损失,造成不好的用户体验。
第二、一个真实案例
在项目里有一个工具类,对接第三方插件。在这个工具类里,有好几个参数都被定义成static,不仅如此,这几个参数还被写到了配置文件里。
获取properties配置文件里的变量,大家都会,但是如何把这几个变量赋值给静态变量呢?
static关键字用于定义类级别的变量和方法,而不是类的实例。static成员变量在所有实例之间共享,只有一份拷贝存在于内存中。static变量通常用于存储类的常量信息或者那些需要被类的所有实例共享的数据。
@Value是Spring框架提供的一个注解,用于注入外部配置(如application.properties或application.yml)到Spring管理的beans中。每个被@Value注解的变量的副本数量取决于其所在bean的实例数量。
也就是说,static变量的生命周期与类的加载和卸载相同,而被@Value注解的变量的生命周期则由Spring容器的bean生命周期管理决定。
因此呢,static 和 @Value是不可以搭配使用的,把它们搭配起来也获取不到配置文件里的变量。
针对这种特殊情况,java原生包和SpringBoot框架各有解决办法。
第三、两种实现方式
1、实现业务场景模拟
先写一个入口测试程序,用于调用第三方工具类中的方法
java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
ThirdPartyUtil.function();
}
}
封装第三方工具类,获取配置文件中的属性并赋值给静态变量,以供静态方法使用。
java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class ThirdPartyUtil {
@Value("${spring.tel}")
private static String tel;
public static void function(){
System.out.println("需要使用参数tel:" + tel);
}
}
application.properties
java
spring.tel=1234567
运行结果
java
2024-05-06 13:49:11.564 INFO 23440 --- [ main] f.a.AutowiredAnnotationBeanPostProcessor : Autowired annotation is not supported on static fields: private static java.lang.String com.example.demo.ThirdPartyUtil.tel
2024-05-06 13:49:11.627 INFO 23440 --- [ main] com.example.demo.App : Started App in 0.506 seconds (JVM running for 1.014)
需要使用参数tel:null
通过打印结果,我们看到并没有获取到配置文件中的变量。而且在日志中有提示,注解不适合静态变量。
2、Spring框架提供的 InitializingBean 接口
把代码调整一下,实现InitializingBean 接口:
java
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class ThirdPartyUtil implements InitializingBean {
@Value("${spring.tel}")
private String tel;
private static String TEL;
public static void function() {
System.out.println("需要使用参数tel:" + TEL);
}
@Override
public void afterPropertiesSet() throws Exception {
TEL = tel;
}
}
实现InitializingBean 接口,就要重写 afterPropertiesSet 方法,我们在该方法中,把获取到的成员变量赋值给静态变量,这样配置文件中的配置就可以在实例运行之前被赋值给静态变量。
运行结果:
java
需要使用参数tel:1234567
InitializingBean 提供一种机制,允许Bean在其所有的属性都被初始化后,但在Bean被使用之前,执行初始化代码或逻辑。
当Bean需要执行一些如设置初始状态、执行自检逻辑或者启动一个自动加载过程的操作时,实现InitializingBean接口会非常有用。
InitializingBean 是 Spring 生命周期中的一个关键点,它在属性注入 (Property Injection) 之后和使用 bean 之前被调用。这提供了一个干净的生命周期钩子,可以用来确保 bean 在被使用之前是完全准备好的。
实现InitializingBean接口可以直接在Bean内部控制初始化过程,不需要配置文件或注解指定初始化方法。
afterPropertiesSet方法是在所有Bean属性设置之后立即调用,这确保了所有的依赖注入完成后才执行初始化代码。
3、Java EE 5 引入的@PostConstruct
使用@PostConstruct对接口进行调整:
java
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@Component
public class ThirdPartyUtil {
@Value("${spring.tel}")
private String tel;
private static String TEL;
public static void function() {
System.out.println("需要使用参数tel:" + TEL);
}
@PostConstruct
public void init() {
TEL = tel;
}
}
定义了一个初始化方法,并在该初始化方式上加了@PostConstruct注解。运行结果:
java
需要使用参数tel:1234567
@PostConstruct是Java中的一个注解,主要用于标记在依赖注入完成后需要执行的方法。这个注解是由Java EE 5引入的,现在也包含在Java标准库中。
Spring框架支持这个注解,并在Spring容器创建并完成对Bean属性的注入之后,自动调用被@PostConstruct注解的方法。
@PostConstruct注解的方法在Bean构造函数执行后和所有依赖注入完成后调用,这确保了所有必需的依赖都被设置。
@PostConstruct注解的方法只会被调用一次,在Bean的整个生命周期中不会再次被调用。
通常在这个方法中执行一些初始化的逻辑,比如检查必要属性是否被注入,或者初始化一些内部状态。
第四、最后总结
在以上的初始化业务场景中,@PostConstruct 和 InitializingBean到底用哪个好呢?
@PostConstruct注解是Java EE 5引入的,不依赖于Spring,可以用于任何Java类。与之相比,InitializingBean是Spring特有的接口。
@PostConstruct注解的方法在构造函数执行后、依赖注入完成后调用,与InitializingBean的afterPropertiesSet方法类似。但@PostConstruct提供了更多的灵活性和清晰性,因为它不需要实现特定的接口。
Spring官方推荐首先使用@PostConstruct注解方法进行初始化,但在某些特定情况下,实现InitializingBean接口可以提供更多控制,例如当需要访问Spring的特定功能或在初始化过程中处理Spring特定的异常时。