Loading

SpringBoot读取配置的一次实践

我有这样一个Service,我需要在外面注入queueSizetimeout这两个属性:

@Service
@Slf4j
public class BlockingQueueMessageService implements MessageService, InitializingBean {

    private Duration timeout;
    private Integer queueSize;

    private BlockingQueue<SMSMessage> messageQueue;

}

直接在本类使用@ConfigurationProperties

最初,我直接在本类使用的@ConfigurationProperties

@Service
@Slf4j
@ConfigurationProperties(prefix="blocking-message-service")
public class BlockingQueueMessageService implements MessageService, InitializingBean {

    private BlockingQueue<SMSMessage> messageQueue;
    private Duration timeout;

    private BlockingQueue<SMSMessage> messageQueue;

并在配置文件中指定:

blocking-message-service:
  queue-size: 10
  timeout: 1000

不过我还没有测试就陷入了疑惑,本类中除了这两个需要注入的属性之外,还有一个BlockingQueue,Spring会如何处理它?如果外部通过配置文件也配置了一个叫messageQueue的属性呢?会不会报错?

所以,我感觉上面这种方式非常不优雅,所以我立即替换了实现方式

提供单独的属性配置类

我提供了一个单独的属性配置类,并且把它声明为了@Component,然后再使用@ConfigurationProperties读取配置文件

@Data
@Component
@ConfigurationProperties(prefix = "blocking-message-service")
public class BlockingQueueMessageServiceProperties {
    private Integer queueSize;
    @DurationUnit(ChronoUnit.MILLIS)
    private Duration timeout;
}

然后在原先的类中,我通过@Autowired注入这个属性配置类:

@Service
@Slf4j
public class BlockingQueueMessageService implements MessageService, InitializingBean {

    @Autowired
    private BlockingQueueMessageServiceProperties properties;

    private BlockingQueue<SMSMessage> messageQueue;
}

这样,就不会产生疑惑了,messageQueue这个属性无论如何都是安全的。

使用自动注入的配置值进行初始化

@Autowired注入发生在Bean的属性设置阶段,所以如果在上面的代码中直接使用属性值来初始化messageQueue,就会报空指针异常,因为properties属性还没有被注入

@Autowired
private BlockingQueueMessageServiceProperties properties;

// !!不要这样做!! 在实例化阶段`properties`还没被注入,它等于null!!
private BlockingQueue<SMSMessage> messageQueue = new LinkedBlockingQueue(properties.getQueueSize());

我通过实现InitializingBeanafterPropertiesSet方法,在属性设置阶段之后进行初始化,这时properties已经被安全的注入:

@Service
@Slf4j
public class BlockingQueueMessageService implements MessageService, InitializingBean {

    // ...

    @Override
    public void afterPropertiesSet() throws Exception {
        messageQueue = new LinkedBlockingQueue<>(properties.getQueueSize());
    }

}

避免空值

如果在配置文件中没有给定一个值的话,你可以通过给属性直接赋值来给它一个默认值,否则,对应的属性有可能有为null的风险:

blocking-message-service:
  # queue-size: 10
  timeout: 1000
@Data
@Component
@ConfigurationProperties(prefix = "blocking-message-service")
public class BlockingQueueMessageServiceProperties {
    // 这个赋值发生在实例化阶段,`@ConfigurationProperties`中如果有对应的值会覆盖这个设置
    private Integer queueSize = 1024;
    @DurationUnit(ChronoUnit.MILLIS)
    private Duration timeout = Duration.ofMillis(1000);
}

如果不想这样设置默认值,你还可以提供一个这样的工具方法:

public static <T> T getOrDefault(Supplier<T> supplier, T defaultValue) {
    T t = supplier.get();
    return t == null ? defaultValue : t;
}

然后,在读取值时可以使用这个方法:

timeout = getOrDefault(properties::getTimeout, Duration.ofMillis(1000));

可见性

一直很好奇的一个点就是,在多线程环境中,如果我们向下面一样设置,那么这个messageQueue的可见性能保证吗?要不要给它设置成volatile

@Service
@Slf4j
public class BlockingQueueMessageService implements MessageService, InitializingBean {

    // ...
    private BlockingQueue<SMSMessage> messageQueue;

    @Override
    public void afterPropertiesSet() throws Exception {
        messageQueue = new LinkedBlockingQueue<>(properties.getQueueSize());
    }

}
posted @ 2022-07-31 09:43  yudoge  阅读(91)  评论(0编辑  收藏  举报