SpringBoot读取配置的一次实践
我有这样一个Service,我需要在外面注入queueSize
和timeout
这两个属性:
@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());
我通过实现InitializingBean
的afterPropertiesSet
方法,在属性设置阶段之后进行初始化,这时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());
}
}