我的编程空间,编程开发者的网络收藏夹
学习永远不晚

关于ConditionalOnMissingBean失效问题的追踪

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

关于ConditionalOnMissingBean失效问题的追踪

遇到一个@ConditionalOnMissingBean失效的问题,今天花点时间来分析一下。

现场回放

services

首先介绍下代码结构:有RunService,以及它的两个实现类:TrainRunServiceImpl和CarRunServiceImpl

RunService

public interface RunService {
    void run();
}

TrainRunServiceImpl

public class TrainRunServiceImpl implements RunService {
    @Override
    public void run() {
        System.out.println("开火车,wuwuwuwuwu");
    }
}

CarRunServiceImpl

public class CarRunServiceImpl implements RunService {
    @Override
    public void run() {
        System.out.println("汽车,dididi");
    }
}

操作类

操作类MyInitBean中,注入了RunService – byType

@Component
public class MyInitBean implements InitializingBean {
    @Autowired
    private RunService runService;
    @Override
    public void afterPropertiesSet() throws Exception {
        runService.run();
    }
}

configuration

我们在配置类中,注入RunService的实现bean,并通过@ConditionalOnMissingBean来判断是否注入。

@Configuration
public class MyConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public RunService carRunServiceImpl() {
        return new CarRunServiceImpl();
    }
    
    @Bean
    public RunService trainRunServiceImpl() {
        return new TrainRunServiceImpl();
    }
}

抛出异常

按照上述的代码,执行后,本以为会成功执行,但是却抛出了异常,异常信息如下:

在spring容器中存在了两个RunService实现类。

这导致了MyInitBean无法决定它到底该使用这两个中的哪一个。(默认是byType注入的)

按照上述的异常信息,它给出了两种解决方案:

@Qualifier

在注入bean时,指定bean的名称.

@Controller
public class MyInitBean implements InitializingBean {
    @Autowired
    @Qualifier("carRunServiceImpl")
    private RunService runService;
}

通过@Configuration配置类注入的bean,默认名称为方法名称

  @Bean //  `trainRunServiceImpl `
  public RunService trainRunServiceImpl() {
     return new TrainRunServiceImpl();
 }

直接在类头部申明注入的bean,默认名称为类名称

@Service  //  `trainRunServiceImpl`
public class TrainRunServiceImpl implements RunService {
}

@Primary

@Primary的作用是,在bean存在多个候选者且无法决定使用哪一个时,优先使用带有该注解的bean.

在配置类中Configuration添加

  @Bean
  @Primary
  public RunService trainRunServiceImpl() {
      return new TrainRunServiceImpl();
  }

在类申明中添加

@Primary
public class TrainRunServiceImpl implements RunService {
}

注意

在上述给出的两种方法中,无论是使用@Primary还是这里容器中仍然存在多个实现类,

这并不是我们想要的结果。

这里为什么@ConditionalOnMissingBean会失效呢?

问题定位

在进行问题定位前,我们先来回顾一下@ConditionalOnMissingBean的工作原理

工作原理

@ConditionalOnMissingBean

ConditionalOnMissingBean的注解定义如下:

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnMissingBean {
    Class<?>[] value() default {};
    String[] type() default {};
    
    //略....
}

@ConditionalOnMissingBean通常可以有如下三种使用方式:

    @Bean
//    @ConditionalOnMissingBean(type ="xxx.yyy.zzz.service")
//    @ConditionalOnMissingBean(value = RunService.class)
    @ConditionalOnMissingBean //无参数,表示按照返回值类型过滤
    public RunService carRunServiceImpl() {
        return new CarRunServiceImpl();
    }

在注解上看到了一个OnBeanCondition类,在@ConditionalOnBean,ConditionalOnSingleCandidate和ConditionalOnMissingBean都看到了它的身影。

OnBeanCondition

@Order(Ordered.LOWEST_PRECEDENCE)
class OnBeanCondition extends SpringBootCondition implements ConfigurationCondition {
    @Override
    public ConditionOutcome getMatchOutcome(ConditionContext context,
            AnnotatedTypeMetadata metadata) {
        //ConditionalOnBean  略
        //ConditionalOnSingleCandidate 略
        
        if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
            //寻找 @ConditionalOnMissingBean 匹配的 type;
            BeanSearchSpec spec = new BeanSearchSpec(context, metadata,ConditionalOnMissingBean.class);
            //从容器中寻找指定的type ---  step1
            MatchResult matchResult = getMatchingBeans(context, spec);
            if (matchResult.isAnyMatched()) {
                //如果存在指定的type
                //reason:  found beans of type 'service.Service' AServiceImpl
                String reason = createOnMissingBeanNoMatchReason(matchResult);
                //创建 ConditionOutcome.noMatch: return new ConditionOutcome(false, message);
                return ConditionOutcome.noMatch(ConditionMessage
                        .forCondition(ConditionalOnMissingBean.class, spec)
                        .because(reason));
            }
            
            matchMessage = matchMessage.andCondition(ConditionalOnMissingBean.class, spec)
                    .didNotFind("any beans").atAll();
        }
        //默认 创建 ConditionOutcome.match : return new ConditionOutcome(true, message);
        return ConditionOutcome.match(matchMessage);
    }
}

ConditionOutcome 的用法:当match= true时,才注入容器.

若@ConditionalOnMissingBean找到了匹配项,则返回ConditionOutcome.notMatch,则不注入容器。

问题出在哪? 

有了上面的一系列原理支撑,但是为什么没有执行到我们想要的结果呢?

debug执行后,发现问题出现在OnBeanCondition .getMatchingBeans(context, spec)这个方法中。

首先再次回顾下配置类:

在注入carRunServiceImpl时,执行OnBeanCondition .getMatchingBeans(context, spec)并没有找到下面定义的trainRunServiceImpl.

真相只有一个:

@Configuration 在初始化bean的时候,顺序出现了问题,那么如何控制初始化bean的顺序呢?

解决问题

一顿分析之后,我们发现只要控制了bean的加载顺序之后,上述的问题就可以解决了。

接下来我们来尝试控制bean初始化顺序:

Configuration中bean使用@Order ----------------- failure

@Configuration
public class MyConfiguration {
    @Order(2)
    @Bean
    @ConditionalOnMissingBean
    public RunService carRunServiceImpl() {
        return new CarRunServiceImpl();
    }
    @Order(1)
    @Bean
    public RunService trainRunServiceImpl() {
        return new TrainRunServiceImpl();
    }
}

Configuration 调整bean申明顺序----------------- success

将带有@ConditionalOnMissingBean注解的bean,申明在代码的末尾位置,操作成功:

@Configuration
public class MyConfiguration {
	@Bean
    public RunService trainRunServiceImpl() {
        return new TrainRunServiceImpl();
    }
    @Bean
    @ConditionalOnMissingBean
    public RunService carRunServiceImpl() {
        return new CarRunServiceImpl();
    }
}

配置多个Configuration类,并通过@Order指定顺序---------------- failure

@Configuration
@Order(Ordered.LOWEST_PRECEDENCE) //最低优先级
public class MyConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public RunService carRunServiceImpl() {
        return new CarRunServiceImpl();
    }
}
@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE) //最高优先级
public class MyConfiguration2 {
    @Bean
    public RunService trainRunServiceImpl() {
        return new TrainRunServiceImpl();
    }
}

@Configuration并不能通过@Order指定顺序。

大胆猜测下: @Configuration通过配置类名的自然顺序来加载的。

@Configuration配置类加载顺序通过类名顺序来加载 ------- 验证success

将MyConfiguration2重命名为Configuration2,而它的加载顺序在MyConfiguration之前,执行程序成功。

这里貌似所有的问题似乎都解决了, 只需要我们自定义的配置类名称保证最优先加载就可以了。我们只需要注意配置类的命名规则即可.

但是,这种解决方案,似乎并不是那么令人信服。

@AutoConfigureBefore,@AutoConfigureAfter

经查文档,终于找到了需要的东西:我们可以通过@AutoConfigureBefore,@AutoConfigureAfter来控制配置类的加载顺序。

@Configuration
public class MyConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public RunService carRunServiceImpl() {
        return new CarRunServiceImpl();
    }
}
@Configuration
@AutoConfigureBefore(MyConfiguration.class)
public class MyConfiguration2 {
    @Bean
    public RunService trainRunServiceImpl() {
        return new TrainRunServiceImpl();
    }
}

注意:

如果要开启@EnableAutoConfiguration需要在META-INF/spring.factories文件中添加如下内容:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
xxx.configuration.MyConfiguration2,\
xxx.configuration.MyConfiguration

结论

我们需要控制目标bean的加载顺序即可。

但是我们在实际的使用一些通用plugin过程中(如redis),并没有刻意的指定bean的加载顺序,这是为什么呢?

因为:在实际的应用过程中,我们使用第三方插件,他们的默认配置都会存在于插件的jar包中,而我们的个性化配置则存在于自身的应用中。

而容器会优先执行classes/,然后才执行jars/classes.

以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

关于ConditionalOnMissingBean失效问题的追踪

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

关于elementel-input的autofocus失效的问题及解决

这篇文章主要介绍了关于elementel-input的autofocus失效的问题及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
2022-12-08

Vue关于访问外链失败的问题

这篇文章主要介绍了Vue关于访问外链失败的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
2023-03-11

Spring事务失效之怎么解决关于this调用的问题

这篇文章主要介绍“Spring事务失效之怎么解决关于this调用的问题”,在日常操作中,相信很多人在Spring事务失效之怎么解决关于this调用的问题问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Sprin
2023-06-25

Vue关于访问外链失败的问题如何解决

这篇文章主要介绍了Vue关于访问外链失败的问题如何解决的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Vue关于访问外链失败的问题如何解决文章都会有所收获,下面我们一起来看看吧。Vue访问外链失败在公司项目中,点
2023-07-05

关于linux(ubuntu18.04)中idea操作数据库失败的问题

如题, 记录一次失败解决步骤 : linux(ubuntu 18.04) 中idea操作数据库失败,报错信息:Client does not support authentication protocol requested by serv
2022-05-27

关于vuex强刷数据丢失问题的解决方法

这篇文章主要讲解了“关于vuex强刷数据丢失问题的解决方法”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“关于vuex强刷数据丢失问题的解决方法”吧!vuex-persistedstate核心
2023-06-14

关于Android HTML5 audio autoplay无效问题的解决方案

前言:在android HTML5 开发中有不少人遇到过 audio 标签 autoplay在某些设备上无效的问题,网上大多是讲怎么在js中操作,即在特定的时刻调用audio的play()方法,在android上还是无效。 一、解决方案 在
2022-06-06

关于ios配置微信config出现验签失败的问题解决

在开发中,出现了一个关于微信配置的问题。 使用的开发工具以及开发框架为 uniapp , JSSDK为 jweixin 使用uniapp进行公众号开发,需要在进入某个页面时候进行微信配置来达到更改分享信息的效果。 问题描述:请求后台获取了微
2022-05-28

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录