关于ConditionalOnMissingBean失效问题的追踪

作者:伊布拉西莫 时间:2021-08-19 17:42:04 

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

现场回放

services

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

关于ConditionalOnMissingBean失效问题的追踪

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();
   }
}

抛出异常

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

关于ConditionalOnMissingBean失效问题的追踪

在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)这个方法中。

首先再次回顾下配置类:

关于ConditionalOnMissingBean失效问题的追踪

在注入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之前,执行程序成功。

关于ConditionalOnMissingBean失效问题的追踪

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

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

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

来源:https://blog.csdn.net/it_freshman/article/details/100031510

标签:ConditionalOnMissingBean,失效
0
投稿

猜你喜欢

  • Spring Boot配置AOP打印日志的全过程

    2023-08-07 12:56:38
  • Eclipse最新版使用过程中遇到的问题总结

    2023-12-03 04:14:14
  • springboot整合EHCache的实践方案

    2023-08-23 23:48:31
  • 基于html5+java实现大文件上传实例代码

    2023-09-26 02:14:29
  • 基于使用BeginInvoke,EndInvoke异步调用委托的实现代码

    2023-04-29 09:46:49
  • Java读取并下载网络文件的方法

    2023-03-18 11:47:05
  • C#新手常犯的错误汇总

    2021-10-29 05:05:53
  • java反射机制给实体类相同字段自动赋值实例

    2023-11-25 19:52:29
  • 基于SpringBoot核心原理(自动配置、事件驱动、Condition)

    2023-08-23 01:46:31
  • Java List集合排序实现方法解析

    2023-01-06 05:40:14
  • C#实现对Json字符串处理实例

    2023-06-21 08:26:24
  • Java实现树形List与扁平List互转的示例代码

    2023-03-15 00:18:23
  • Java file类中renameTo方法操作实例

    2021-06-13 01:21:03
  • Java(TM) Platform SE binary 打开jar文件的操作

    2021-10-02 00:08:12
  • @Value如何获取yml和properties配置参数

    2021-12-12 13:24:01
  • Java实现冒泡排序算法

    2023-07-13 03:02:28
  • C# Socket编程实现简单的局域网聊天器的示例代码

    2022-10-01 19:45:45
  • 用Java实现简单画板功能

    2023-01-19 08:17:10
  • 基于java SSM springboot实现抗疫物质信息管理系统

    2021-12-01 13:46:34
  • JavaWeb dbutils执行sql命令并遍历结果集时不能查到内容的原因分析

    2022-04-11 22:50:02
  • asp之家 软件编程 m.aspxhome.com