1.描述
1.1 背景
最近在做一个系统交互日志模块,要监控一个http请求,并记录请求与响应日志。项目中使用RestTemplate来发送http请求,所以打算给RestTemplate设置拦截器,来进行自定义操作。但是,只对当前类生效,不能影响到其他RestTemplate的使用。然而,在改造过程中,遇到了bean为空的情况,经排查,是因为在构造方法中使用这个bean给RestTemplate初始化拦截器操作时,bean的依赖注入还未完成,出现了空指针异常。那么是不是有一种方式,在bean依赖注入完成之后,再执行初始化呢?当然有,@PostConstruct就能解决这个问题,比较推荐这种方式。
1.2 注意事项
关于bean的使用,我们要注意:
(1)要注意bean的加载与使用顺序:如果使用该bean时,bean为空,无非就俩种情况:a:未被spring管理、b:使用该bean时,该bean的依赖注入还未完成。
(2)要将bean交给spring管理,通过依赖注入的方式去使用:如果该bean中,依赖了别的bean。那么要将该bean交给spring管理,且使用该bean时,要通过依赖注入的方式去拿,而不要去new。
2.@PostConstruct简单介绍
2.1 简介:
@PostConstruct
是 Java 中的一个注解,用于在 Spring 框架中指定初始化方法。当一个 Bean 被实例化之后,如果该 Bean 类中存在用 @PostConstruct
注解的方法,那么该方法会在依赖注入(DI)完成后被自动调用,用于执行一些自定义的初始化操作。
2.2 @PostConstruct的作用
- 初始化操作:在bean的依赖注入完成后,执行一些初始化操作。
- 资源准备:比如数据库连接、文件系统访问等,需要在依赖注入完成后才能进行。
- 状态验证:可以检查bean的依赖是否正确注入,或者做一些状态的验证。
2.3 @PostConstruct
的一些关键点:
@PostConstruct
注解用于在依赖注入完成后执行 Bean 的初始化逻辑。- 一个类中只能有一个方法用
@PostConstruct
注解。 - 被
@PostConstruct
注解的方法不能有参数,并且返回类型必须为void
。 - 被
@PostConstruct
注解的方法通常应该是非静态的,但如果它所在类是单例模式,那么静态方法也可以被注解。
2.4 举例
(1)执行顺序:构造函数被调用——>@PostConstruct 方法被调用
import javax.annotation.PostConstruct;
public class MyClass {
private String message;
public MyClass() {
System.out.println("构造函数被调用");
}
@PostConstruct
public void init() {
System.out.println("@PostConstruct 方法被调用");
message = "Hello, World!";
}
public String getMessage() {
return message;
}
}
在这个例子中,我们有一个名为 MyClass
的类,它有一个私有成员变量 message
。我们在构造函数中打印一条消息,然后在 init
方法上使用 @PostConstruct
注解。当 Spring 容器创建 MyClass
的实例并完成依赖注入后,它会调用 init
方法来初始化 message
变量。
(2)初始化时从文件系统加载一些配置数据:
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
@Service
public class ConfigurationService {
private String configData;
@PostConstruct
public void loadConfigData() {
try (BufferedReader reader = new BufferedReader(new FileReader("config.properties"))) {
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line).append("\n");
}
configData = sb.toString();
System.out.println("Configuration data loaded.");
} catch (IOException e) {
throw new RuntimeException("Failed to load configuration data", e);
}
}
public String getConfigData() {
return configData;
}
}
在这个例子中,loadConfigData
方法被@PostConstruct
注解所标记。当Spring IoC容器创建并完成ConfigurationService
Bean的依赖注入后,这个方法就会被自动调用。
此外,一个类中可以有多个带有@PostConstruct
注解的方法,但是通常建议只使用一个这样的方法来保持代码的清晰性和可维护性。如果需要更复杂的初始化逻辑,考虑使用InitializingBean
接口或者init-method
属性。
注:Spring bean的加载注入顺序大体是构造方法 -> 依赖注入 -> @PostConstruct注解的方法。
3.扩展:bean的加载流程
Spring的bean加载和注入过程主要分为以下几个步骤:
加载BeanDefinition:
- Spring容器通过扫描类路径、解析注解或读取XML配置文件,加载bean定义。
注册BeanDefinition:
- 将加载的bean定义注册到
BeanDefinitionRegistry
中。
- 将加载的bean定义注册到
创建Bean实例:
- 容器根据bean定义创建bean实例。
属性填充:
- 容器会根据bean定义中的属性值,填充bean实例的属性。
依赖注入:
- 容器会解析bean定义中的依赖关系,并注入相应的bean或值。
调用@PostConstruct方法:
- 在依赖注入完成后,调用bean的
@PostConstruct
方法。
- 在依赖注入完成后,调用bean的
初始化Bean:
- 容器会调用bean的初始化方法(如果有),比如实现了
InitializingBean
接口的afterPropertiesSet
方法。
- 容器会调用bean的初始化方法(如果有),比如实现了
Bean准备就绪:
- 所有初始化操作完成后,bean就可以被使用了。