@ConditionalOnBean导致自动配置无效的问题

青春壹個敷衍的年華 提交于 2020-01-07 01:56:37

【推荐】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注解,却能正常工作,这个后续还要继续研究一下,有懂的老哥还望赐教~

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!