对Spring PostConstruct注解的一点新认识

无论是Spring还是SpringBoot开发中,PostConstruct注解的使用频率还是比较高的,通常用于Bean初始化完成的一些动作。

在项目代码中,会将配置从配置中心中读取,然后初始化到指定的Bean中。其他需要动态获取配置的地方,直接依赖注入这个Bean即可。
示例代码如下:

ApplicationConfig

动态配置所在的类,主要是属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
java复制代码@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方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
java复制代码@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,基于配置信息,在类初始化完成后,做一些动作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码@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());
}
}

备注:

  1. 主要的类中,提供了一个无参的构造方法,以及一个使用了@PostConstructor注解的初始化方法,主要用于看一下执行顺序。
  2. 代码说明
    1. ApplicationConfigLoadService 的初始化方法中加载配置
    2. ApplicationConfig的setter方法完成配置初始化
    3. ApplicationClientFactory依赖ApplicationConfig中的属性完成一些初始化工作。

将上述代码执行一下,并查看日志。

1
2
3
4
5
6
properties复制代码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的名称。

1
2
3
4
java复制代码@DependsOn(value = {"applicationConfigLoadService"})
@Component
@Slf4j
public class ApplicationClientFactory
1
2
3
4
5
6
> vbnet复制代码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注解,可以解决问题。

1
2
3
4
5
6
properties复制代码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注解的初始化方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码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());
}
}

执行结果

1
2
3
4
5
6
properties复制代码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方法。对于后续维护的同学,很有可能无意将其移除。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%