无论是Spring还是SpringBoot开发中,PostConstruct注解的使用频率还是比较高的,通常用于Bean初始化完成的一些动作。
在项目代码中,会将配置从配置中心中读取,然后初始化到指定的Bean中。其他需要动态获取配置的地方,直接依赖注入这个Bean即可。
示例代码如下:
ApplicationConfig
动态配置所在的类,主要是属性。
@Configuration
@Data
@Slf4j
public class ApplicationConfig {
/**
* client host
*/
private String host;
/**
* client port
*/
private String port;
public ApplicationConfig() {
log.info("ApplicationConfig constructor execute");
}
@PostConstruct
public void init() {
log.info("ApplicationConfig postConstructor execute");
}
}
ApplicationConfigLoadService
从远程配置中心中获取配置信息,主要依赖PostConstruct方法。
@Service
@Slf4j
public class ApplicationConfigLoadService {
@Resource
private ApplicationConfig applicationConfig;
public ApplicationConfigLoadService() {
log.info("ApplicationConfigLoadService constructor execute");
}
@PostConstruct
public void load() {
log.info("ApplicationConfigLoadService postConstruct execute");
// 可以是从数据库,或者远程的配置中心中读取配置
String host = "127.0.0.1";
String port = "8080";
applicationConfig.setHost(host);
applicationConfig.setPort(port);
}
}
ApplicationClientFactory
使用ApplicationConfig,基于配置信息,在类初始化完成后,做一些动作。
@Component
@Slf4j
public class ApplicationClientFactory {
@Resource
private ApplicationConfig applicationConfig;
public ApplicationClientFactory() {
log.info("ApplicationClientFactory constructor execute");
}
@PostConstruct
public void init() {
log.info("ApplicationClientFactory postConstruct execute, host:{}, port:{}",
applicationConfig.getHost(), applicationConfig.getPort());
}
}
备注:
- 主要的类中,提供了一个无参的构造方法,以及一个使用了@PostConstructor注解的初始化方法,主要用于看一下执行顺序。
- 代码说明
- ApplicationConfigLoadService 的初始化方法中加载配置
- ApplicationConfig的setter方法完成配置初始化
- ApplicationClientFactory依赖ApplicationConfig中的属性完成一些初始化工作。
将上述代码执行一下,并查看日志。
2021-06-06 15:38:28.591 INFO 2790 --- [ main] c.y.m.c.ApplicationClientFactory : ApplicationClientFactory constructor execute
2021-06-06 15:38:28.598 INFO 2790 --- [ main] c.y.m.configuration.ApplicationConfig : ApplicationConfig constructor execute
2021-06-06 15:38:28.599 INFO 2790 --- [ main] c.y.m.configuration.ApplicationConfig : ApplicationConfig postConstructor execute
2021-06-06 15:38:28.599 INFO 2790 --- [ main] c.y.m.c.ApplicationClientFactory : ApplicationClientFactory postConstruct execute, host:null, port:null
2021-06-06 15:38:28.602 INFO 2790 --- [ main] c.y.m.c.ApplicationConfigLoadService : ApplicationConfigLoadService constructor execute
2021-06-06 15:38:28.603 INFO 2790 --- [ main] c.y.m.c.ApplicationConfigLoadService : ApplicationConfigLoadService postConstruct execut
可以看到ApplicationClientFactory的构造方法先被执行,然后由于依赖ApplicationConfig类,所以ApplicationConfig的构造方法和标识了PostConstruct注解的方法被执行,然后才会执行ApplicationClientFactory自己的postConstruct方法。
但是从日志中可以看出,此时由于ApplicationConfigLoadService还没被加载,所以读取到的配置都是空的。
尝试的解决方案
方案1:是可以采用DependsOn指定Bean的加载顺序。
修改代码如下:
value即为依赖Bean的名称。
@DependsOn(value = {"applicationConfigLoadService"})
@Component
@Slf4j
public class ApplicationClientFactory
Beans on which the current bean depends. Any beans specified are guaranteed to be created by the container before this bean. Used infrequently in cases where a bean does not explicitly depend on another through properties or constructor arguments, but rather depends on the side effects of another bean's initialization.
从JDK文档可以看出,DependsOn注解主要的使用场景是当前Bean没有显示通过属性或者构造参数依赖另外一个Bean,但是却要依赖另外一个Bean的一些初始化动作。
在上述代码示例中,通过添加DependsOn注解,可以解决问题。
2021-06-06 16:36:59.944 INFO 3688 --- [ main] c.y.m.c.ApplicationConfigLoadService : ApplicationConfigLoadService constructor execute
2021-06-06 16:36:59.948 INFO 3688 --- [ main] c.y.m.configuration.ApplicationConfig : ApplicationConfig constructor execute
2021-06-06 16:36:59.949 INFO 3688 --- [ main] c.y.m.configuration.ApplicationConfig : ApplicationConfig postConstructor execute
2021-06-06 16:36:59.949 INFO 3688 --- [ main] c.y.m.c.ApplicationConfigLoadService : ApplicationConfigLoadService postConstruct execute
2021-06-06 16:36:59.950 INFO 3688 --- [ main] c.y.m.c.ApplicationClientFactory : ApplicationClientFactory constructor execute
2021-06-06 16:36:59.951 INFO 3688 --- [ main] c.y.m.c.ApplicationClientFactory : ApplicationClientFactory postConstruct execute, host:127.0.0.1, port:8080
方案2: 显示通过@Resource或者@Autowired注入待依赖的Bean
在DependsOn的JDK代码中也可以看到,通过显示依赖可以解决问题。通过签名日志可以看出,当显示依赖注入某个Bean时,被注入Bean会依次执行对应的构造函数以及@PostConstructor注解的初始化方法。
public class ApplicationClientFactory {
@Resource
private ApplicationConfig applicationConfig;
// 显示依赖
@Resource
private ApplicationConfigLoadService applicationConfigLoadService;
public ApplicationClientFactory() {
log.info("ApplicationClientFactory constructor execute");
}
@PostConstruct
public void init() {
log.info("ApplicationClientFactory postConstruct execute, host:{}, port:{}",
applicationConfig.getHost(), applicationConfig.getPort());
}
}
执行结果
2021-06-06 16:08:17.458 INFO 3286 --- [ main] c.y.m.c.ApplicationClientFactory : ApplicationClientFactory constructor execute
2021-06-06 16:08:17.464 INFO 3286 --- [ main] c.y.m.configuration.ApplicationConfig : ApplicationConfig constructor execute
2021-06-06 16:08:17.465 INFO 3286 --- [ main] c.y.m.configuration.ApplicationConfig : ApplicationConfig postConstructor execute
2021-06-06 16:08:17.466 INFO 3286 --- [ main] c.y.m.c.ApplicationConfigLoadService : ApplicationConfigLoadService constructor execute
2021-06-06 16:08:17.467 INFO 3286 --- [ main] c.y.m.c.ApplicationConfigLoadService : ApplicationConfigLoadService postConstruct execute
2021-06-06 16:08:17.467 INFO 3286 --- [ main] c.y.m.c.ApplicationClientFactory : ApplicationClientFactory postConstruct execute, host:127.0.0.1, port:8080
此时可以看到在ApplicationClientFactory的postConstruc中,依赖的ApplicationConfig是有对应属性值的。
但是,此处会存在一个风险问题,由于applicationConfigLoadService这个变量在当前类中并未实际使用,仅仅是为了依赖其postConstruct方法。对于后续维护的同学,很有可能无意将其移除。
网友评论