【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>
本文代码下载
Spring Boot为我们提供了很多注解来方便我们开发项目,比如使用@ConditionalOnXXX注解来控制Bean的加载。但是前几天发现自动配置加了@ConditionalOnBean注解后,竟然没有生效,花了很长时间最终解决了,在此记录一下。
一、代码简介
首先将示例代码打开,直接运行,可以看到有日志输出,证明一切正常

然后我们简单介绍一下示例代码中主要的类
1、RabbitAutoConfiguration.java
/**
* 当local-config.rabbitmq.init配置为true的时候,加载配置类
* 同时将ConnectionFactory注册为bean
*/
@Configuration
@ConditionalOnProperty(name = "local-config.rabbitmq.init", havingValue = "true")
public class RabbitAutoConfiguration {
@Bean
public ConnectionFactory connectionFactory() {
return new ConnectionFactory();
}
}
2、WebsocketAutoConfiguration.java
/**
* 当local-config.rabbitmq.init配置为true的时候,加载配置类
* 然后扫描com.iceberg.boot.autoconfig.framework.websocket.component包中的类(WebsocketHandler)
* 配置加载顺序排在RabbitAutoConfiguration后边
*/
@Configuration
@ComponentScan("com.iceberg.boot.autoconfig.framework.websocket.component")
//@ConditionalOnBean(ConnectionFactory.class)
@ConditionalOnProperty(name = "local-config.rabbitmq.init", havingValue = "true")
@AutoConfigureAfter(RabbitAutoConfiguration.class)
public class WebsocketAutoConfiguration {
}
3、BootAutoconfigApplication.java
/**
* 启动类,如果项目启动成功,打印日志
*/
@Slf4j
@SpringBootApplication
public class BootAutoconfigApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(BootAutoconfigApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
log.warn("-------------------------------项目启动成功--------------------------------");
}
}
4、TradingService.java
/**
* 默认会被启动类加载,如果WebsocketHandler在spring中不存在,会报错
*/
@Service
public class TradingService {
@Autowired
private WebsocketHandler websocketHandler;
}
默认情况下,示例代码工作良好
二、使用@ConditionalOnBean注解
现在我们把WebsocketAutoConfiguration中的@ConditionalOnProperty注释掉,然后打开@ConditionalOnBean的注释:
@Configuration
@ComponentScan("com.iceberg.boot.autoconfig.framework.websocket.component")
@ConditionalOnBean(ConnectionFactory.class)
//@ConditionalOnProperty(name = "local-config.rabbitmq.init", havingValue = "true")
@AutoConfigureAfter(RabbitAutoConfiguration.class)
public class WebsocketAutoConfiguration {
}
再启动一次项目:

可以看到这里提示不存在类型是WebsocketHandler的bean,说明WebsocketAutoConfiguration上配置的包扫描没有生效。
在遇到这个问题以前,我一直以为配置了@AutoConfigureAfter以后,会等到RabbitAutoConfiguration全部加载完成之后才会加载WebsocketAutoConfiguration,然而事实告诉我不是这样的。
三、Spring Boot配置加载流程
下面进入痛苦而又愉悦的源码调试环节,首先确定入口
@Slf4j
@SpringBootApplication
public class BootAutoconfigApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(BootAutoconfigApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
log.warn("-------------------------------项目启动成功--------------------------------");
}
}
然后一路跟下去:
//SpringApplication.java
//第295行
public ConfigurableApplicationContext run(String... args) {
//省略部分代码
refreshContext(context);
//省略部分代码
}
//AbstractApplicationContext.java
//第515行
public void refresh() throws BeansException, IllegalStateException {
//省略部分代码
invokeBeanFactoryPostProcessors(beanFactory);
//省略部分代码
}
//AbstractApplicationContext.java
//第704行
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
//省略部分代码
}
然后我们就可以看到BeanFactoryPostProcessor相关的处理逻辑,我们这次重点关注的实现类是ConfigurationClassPostProcessor,它的作用就是加载所有的配置类。
//ConfigurationClassPostProcessor.java
//第220行
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
//省略部分代码
processConfigBeanDefinitions(registry);
}
//ConfigurationClassPostProcessor.java
//第261行
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
//省略部分代码
//candidates就是BootAutoconfigApplication启动类
parser.parse(candidates);
//省略部分代码
}
//ConfigurationClassParser.java
//第163行
public void parse(Set<BeanDefinitionHolder> configCandidates) {
//省略部分代码
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
//省略部分代码
}
//ConfigurationClassParser.java
//第198行
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(metadata, beanName));
}
//ConfigurationClassParser.java
//第217行
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
//省略部分代码
SourceClass sourceClass = asSourceClass(configClass);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass);
}
while (sourceClass != null);
//省略部分代码
}
//ConfigurationClassParser.java
//第258行
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
//省略部分代码
//读取@SpringBootApplication中的注解
//注意这段代码会被循环执行,请确认configClass是BootAutoconfigApplication
processImports(configClass, sourceClass, getImports(sourceClass), true);
//省略部分代码
}
//ConfigurationClassParser.java
//第542行
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
//org.springframework.boot.autoconfigure.AutoConfigurationPackages$Registrar
//org.springframework.boot.autoconfigure.AutoConfigurationImportSelector
//解析上边两个类,具体过程省略
}
之后BootAutoconfigApplication就被解析完成,然后开始处理加载自动配置类
//ConfigurationClassParser.java
//第163行
public void parse(Set<BeanDefinitionHolder> configCandidates) {
//省略部分代码
this.deferredImportSelectorHandler.process();
//省略部分代码
}
//ConfigurationClassParser.java
//第763行
public void process() {
List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
this.deferredImportSelectors = null;
try {
if (deferredImports != null) {
DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
deferredImports.forEach(handler::register);
handler.processGroupImports();
}
}
finally {
this.deferredImportSelectors = new ArrayList<>();
}
}
//ConfigurationClassParser.java
//第799行
public void processGroupImports() {
for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
//获取并遍历配置在spring.factories中的自动配置类
//注意这里获取的时候会进行第一波过滤,有兴趣的可以调试下getImports()方法
grouping.getImports().forEach(entry -> {
ConfigurationClass configurationClass = this.configurationClasses.get(
entry.getMetadata());
try {
processImports(configurationClass, asSourceClass(configurationClass),
asSourceClasses(entry.getImportClassName()), false);
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configurationClass.getMetadata().getClassName() + "]", ex);
}
});
}
}
//ConfigurationClassParser.java
//第542行
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
//省略部分代码
processConfigurationClass(candidate.asConfigClass(configClass));
//省略部分代码
}
//ConfigurationClassParser.java
//第217行
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
//省略部分代码
SourceClass sourceClass = asSourceClass(configClass);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass);
}
while (sourceClass != null);
//省略部分代码
}
这里我们又回到了刚才处理BootAutoconfigApplication的地方,只不过这次处理的是自动配置类
//ConfigurationClassParser.java
//第258行
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
//省略部分代码
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
//省略部分代码
}
//省略部分代码
}
可以看到上边取出@ComponentScan注解之后,有一个if判断,这里就是之前WebsocketHandler未被加载的原因
//ConditionEvaluator.java
//第80行
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
for (Condition condition : conditions) {
ConfigurationPhase requiredPhase = null;
if (condition instanceof ConfigurationCondition) {
requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
}
//这里判断不通过,所以@Component未生效
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
return true;
}
}
}
那为啥未生效呢,我们不是配置了@AutoConfigureAfter保证了自动配置的顺序了吗,难道没用?
@AutoConfigureAfter是有用的,但是Spring的配置解析和Bean的加载并不是同时发生的,而是有先后的:
//ConfigurationClassPostProcessor.java
//第261行
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
//省略部分代码
//先解析所有的配置
parser.parse(candidates);
//省略部分代码
//再根据配置将类加载到Spring中
this.reader.loadBeanDefinitions(configClasses);
//省略部分代码
}
从上边的代码可以看到Spring是先处理配置类,再加载Bean的,所以配置上使用@ConditionalOnBean注解,都是无效的。
四、总结
通过以上分析,可以发现@ConditionalOnBean似乎不能用到配置类上,建议老老实实的用@ConditionalOnProperty。
但是有个问题,Spring Boot的某些配置类也使用了@ConditionalOnBean注解,却能正常工作,这个后续还要继续研究一下,有懂的老哥还望赐教~
来源:oschina
链接:https://my.oschina.net/icebergxty/blog/3153051