conditionalonbean不生效,conditionalonmissingbean的作用

  conditionalonbean不生效,conditionalonmissingbean的作用

  

目录

现场回放服务操作类配置抛出异常问题定位工作原理问题出在哪?解决问题结论遇到一个@ ConditionalOnMissingBean失效的问题,今天花点时间来分析一下。

 

  

现场回放

 

  

services

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

 

  RunService

  公共接口运行服务{ void run();}TrainRunServiceImpl

  公共类列车运行服务实现RunService { @ Override public void run(){ system。出去。println(开火车,呜呜呜’);} }CarRunServiceImpl

  公共类CarRunServiceImpl实现RunService { @ Override public void run(){ system。出去。println(汽车、迪迪);}}

  

操作类

操作类MyInitBean中,注入了运行服务按类型

 

  @Componentpublic类MyInitBean实现正在初始化bean { @ auto wired private run service运行服务;@覆盖public void afterPropertiesSet()抛出异常{运行服务。run();}}

  

configuration

我们在配置类中,注入运行服务的实现豆子,并通过@ ConditionalOnMissingBean来判断是否注入。

 

  @ configuration public class my configuration { @ Bean @ ConditionalOnMissingBean public run service carRunServiceImpl(){ return new carRunServiceImpl();} @ Bean公共运行服务trainRunServiceImpl(){ return new trainRunServiceImpl();}}

  

抛出异常

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

 

  在春天容器中存在了两个运行服务实现类。

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

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

  @Qualifier

  在注入豆时,指定豆的名称。

  @Controllerpublic类MyInitBean实现正在初始化bean { @ auto wired @ Qualifier( carRunServiceImpl )私有RunService runService}通过@配置配置类注入的豆子,默认名称为方法名称

  @ Bean//` trainRunServiceImpl ` public run service trainRunServiceImpl(){ return new Trai

  nRunServiceImpl(); }直接在类头部申明注入的bean,默认名称为类名称

  

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

@Primary

 

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

  在配置类中Configuration添加

  

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

在类申明中添加

 

  

@Primarypublic 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

  

@Configurationpublic 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,申明在代码的末尾位置,操作成功:

  

@Configurationpublic 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来控制配置类的加载顺序。

  

@Configurationpublic 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.

  以上为个人经验,希望能给大家一个参考,也希望大家多多支持盛行IT。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: