对Spring PostConstruct注解的一点新认识

1,099 阅读4分钟

无论是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());
  }
}

备注:

  1. 主要的类中,提供了一个无参的构造方法,以及一个使用了@PostConstructor注解的初始化方法,主要用于看一下执行顺序。
  2. 代码说明
    1. ApplicationConfigLoadService 的初始化方法中加载配置
    2. ApplicationConfig的setter方法完成配置初始化
    3. 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方法。对于后续维护的同学,很有可能无意将其移除。