阅读 214

对Spring PostConstruct注解的一点新认识

无论是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方法。对于后续维护的同学,很有可能无意将其移除。

文章分类
后端