写在前面

这个demo来说明怎么排查一个常见的spring expected single matching bean but found 2的异常。

https://github.com/hengyunabc/spring-boot-inside/tree/master/demo-expected-single

调试排查 expected single matching bean but found 2 的错误

把工程导入IDE里,直接启动应用,抛出来的异常信息是:

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'javax.sql.DataSource' available: expected single matching bean but found 2: h2DataSource1,h2DataSource2
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1041) ~[spring-beans-4.3.9.RELEASE.jar:4.3.9.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:345) ~[spring-beans-4.3.9.RELEASE.jar:4.3.9.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:340) ~[spring-beans-4.3.9.RELEASE.jar:4.3.9.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1090) ~[spring-context-4.3.9.RELEASE.jar:4.3.9.RELEASE]
at org.springframework.boot.autoconfigure.jdbc.DataSourceInitializer.init(DataSourceInitializer.java:71) ~[spring-boot-autoconfigure-1.4.7.RELEASE.jar:1.4.7.RELEASE]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_112]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_112]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_112]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_112]
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleElement.invoke(InitDestroyAnnotationBeanPostProcessor.java:366) ~[spring-beans-4.3.9.RELEASE.jar:4.3.9.RELEASE]
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:311) ~[spring-beans-4.3.9.RELEASE.jar:4.3.9.RELEASE]
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:134) ~[spring-beans-4.3.9.RELEASE.jar:4.3.9.RELEASE]
... 30 common frames omitted

很多人碰到这种错误时,就乱配置一通,找不到下手的办法。其实耐心排查下,是很简单的。

抛出异常的原因

异常信息写得很清楚了,在spring context里需要注入/获取到一个DataSource bean,但是现在spring context里出现了两个,它们的名字是:h2DataSource1,h2DataSource2

那么有两个问题:

  1. 应用是在哪里要注入/获取到一个DataSource bean?
  2. h2DataSource1,h2DataSource2 是在哪里定义的?

使用 Java Exception Breakpoint

在IDE里,新建一个断点,类型是Java Exception Breakpoint(如果不清楚怎么添加,可以搜索对应IDE的使用文档),异常类是上面抛出来的NoUniqueBeanDefinitionException

当断点停住时,查看栈,可以很清楚地找到是在DataSourceInitializer.init() line: 71这里要获取DataSource

Thread [main] (Suspended (exception NoUniqueBeanDefinitionException))
owns: ConcurrentHashMap<K,V> (id=49)
owns: Object (id=50)
DefaultListableBeanFactory.resolveNamedBean(Class<T>, Object...) line: 1041
DefaultListableBeanFactory.getBean(Class<T>, Object...) line: 345
DefaultListableBeanFactory.getBean(Class<T>) line: 340
AnnotationConfigEmbeddedWebApplicationContext(AbstractApplicationContext).getBean(Class<T>) line: 1090
DataSourceInitializer.init() line: 71
NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
NativeMethodAccessorImpl.invoke(Object, Object[]) line: 62
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 43
Method.invoke(Object, Object...) line: 498
InitDestroyAnnotationBeanPostProcessor$LifecycleElement.invoke(Object) line: 366
InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(Object, String) line: 311
CommonAnnotationBeanPostProcessor(InitDestroyAnnotationBeanPostProcessor).postProcessBeforeInitialization(Object, String) line: 134
DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).applyBeanPostProcessorsBeforeInitialization(Object, String) line: 409
DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).initializeBean(String, Object, RootBeanDefinition) line: 1620
DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).doCreateBean(String, RootBeanDefinition, Object[]) line: 555
DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).createBean(String, RootBeanDefinition, Object[]) line: 483
AbstractBeanFactory$1.getObject() line: 306
DefaultListableBeanFactory(DefaultSingletonBeanRegistry).getSingleton(String, ObjectFactory<?>) line: 230
DefaultListableBeanFactory(AbstractBeanFactory).doGetBean(String, Class<T>, Object[], boolean) line: 302
DefaultListableBeanFactory(AbstractBeanFactory).getBean(String, Class<T>, Object...) line: 220
DefaultListableBeanFactory.resolveNamedBean(Class<T>, Object...) line: 1018
DefaultListableBeanFactory.getBean(Class<T>, Object...) line: 345
DefaultListableBeanFactory.getBean(Class<T>) line: 340
DataSourceInitializerPostProcessor.postProcessAfterInitialization(Object, String) line: 62
DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).applyBeanPostProcessorsAfterInitialization(Object, String) line: 423
DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).initializeBean(String, Object, RootBeanDefinition) line: 1633
DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).doCreateBean(String, RootBeanDefinition, Object[]) line: 555
DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).createBean(String, RootBeanDefinition, Object[]) line: 483
AbstractBeanFactory$1.getObject() line: 306
DefaultListableBeanFactory(DefaultSingletonBeanRegistry).getSingleton(String, ObjectFactory<?>) line: 230
DefaultListableBeanFactory(AbstractBeanFactory).doGetBean(String, Class<T>, Object[], boolean) line: 302
DefaultListableBeanFactory(AbstractBeanFactory).getBean(String) line: 197
DefaultListableBeanFactory.preInstantiateSingletons() line: 761
AnnotationConfigEmbeddedWebApplicationContext(AbstractApplicationContext).finishBeanFactoryInitialization(ConfigurableListableBeanFactory) line: 867
AnnotationConfigEmbeddedWebApplicationContext(AbstractApplicationContext).refresh() line: 543
AnnotationConfigEmbeddedWebApplicationContext(EmbeddedWebApplicationContext).refresh() line: 122
SpringApplication.refresh(ApplicationContext) line: 762
SpringApplication.refreshContext(ConfigurableApplicationContext) line: 372
SpringApplication.run(String...) line: 316
SpringApplication.run(Object[], String[]) line: 1187
SpringApplication.run(Object, String...) line: 1176
DemoExpectedSingleApplication.main(String[]) line: 17

定位哪里要注入/使用DataSource

要获取DataSource具体的代码是:

//org.springframework.boot.autoconfigure.jdbc.DataSourceInitializer.init()
@PostConstruct
public void init() {
if (!this.properties.isInitialize()) {
logger.debug("Initialization disabled (not running DDL scripts)");
return;
}
if (this.applicationContext.getBeanNamesForType(DataSource.class, false,
false).length > 0) {
this.dataSource = this.applicationContext.getBean(DataSource.class);
}
if (this.dataSource == null) {
logger.debug("No DataSource found so not initializing");
return;
}
runSchemaScripts();
}

this.applicationContext.getBean(DataSource.class); 要求spring context里只有一个DataSource的bean,但是应用里有两个,所以抛出了NoUniqueBeanDefinitionException

BeanDefinition获取bean具体定义的代码

我们再来看 h2DataSource1,h2DataSource2 是在哪里定义的?

上面进程断在了DefaultListableBeanFactory.resolveNamedBean(Class<T>, Object...) 函数里的 throw new NoUniqueBeanDefinitionException(requiredType, candidates.keySet()); 这一行。

那么我们在这里执行一下(如果不清楚,先搜索下IDE怎么在断点情况下执行代码):

this.getBeanDefinition("h2DataSource1")
  • 1

返回的信息是:

Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=demoExpectedSingleApplication; factoryMethodName=h2DataSource1; initMethodName=null; destroyMethodName=(inferred);
defined in com.example.demo.expected.single.DemoExpectedSingleApplication

可以很清楚地定位到h2DataSource1这个bean是在 com.example.demo.expected.single.DemoExpectedSingleApplication里定义的。

所以上面两个问题的答案是:

  1. 是spring boot代码里的DataSourceInitializer.init() line: 71这里要获取DataSource,并且只允许有一个DataSource实例
  2. h2DataSource1,h2DataSource2 是在com.example.demo.expected.single.DemoExpectedSingleApplication里定义的

解决问题

上面排查到的原因是:应用定义了两个DataSource实例,但是spring boot却要求只有一个。那么有两种办法来解决:

  1. 使用@Primary来指定一个优先使用的DataSource,这样子spring boot里自动初始的代码会获取到@Primary的bean
  2. 把spring boot自动初始化DataSource相关的代码禁止掉,应用自己来控制所有的DataSource相关的bean

禁止的办法有两种:

  1. 在main函数上配置exclude


    @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class }) 

  2. 在application.properties里配置:


    spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration 

总结

    • 排查spring初始化问题时,灵活使用Java Exception Breakpoint
    • 从异常栈上,可以很容易找到哪里要注入/使用bean
    • BeanDefinition可以找到bean是在哪里定义的(哪个Configuration类/xml)

深入Spring Boot:怎样排查expected single matching bean but found 2的异常的更多相关文章

  1. Spring Boot 自定义配置文件异常"expected single matching bean but found 2"

    运行环境:Spring Boot 2.5.0, IDEA 2020.3.2 异常详细信息: Injection of resource dependencies failed; nested exce ...

  2. spring " expected single matching bean but found 2" 问题一例。

    初入java,使用spring时遇到一个问题,左边是一个接口和实现.右边是service和实现. @Service@Transactional(rollbackFor = Exception.clas ...

  3. spring依赖注入单元测试:expected single matching bean but found 2

    异常信息:org.springframework.beans.factory.UnsatisfiedDependencyException: Caused by: org.springframewor ...

  4. Spring 3.2 @Autowired异常:expected single matching bean but found 2

    在使用Sping做单元测试时候,对RequestMappingHandlerAdapter(从处理器包装过来的适配器)进行自动装配, 发现报:expected single matching bean ...

  5. 多个@bean无法通过@resource注入对应的bean(org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'javax.sql.DataSource' available: expected single matching bean but found )

    一.异常 org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type ' ...

  6. expected single matching bean but found 2

    org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'acc ...

  7. 多数据源报错 expected single matching bean but found 2: xxx,xxx

    问题: expected single matching bean but found 2: xxx,xxx 原因:在 Spring 容器中配置了两个类型Bean,Spring 容器将无法确定到底要用 ...

  8. MyBatis Plus:No qualifying bean of type 'com.baomidou.mybatisplus.mapper.BaseMapper<?>' available: expected single matching bean but found 4

    场景: 应用MyBatis Plus 和通用Mapper 继承自ServiceImpl实现对Service里的方法进行包装再处理. public interface IServiceBase2< ...

  9. expected single matching bean but found 2: menusServiceImpl,IMenusService

    问题如下: 接口也作为匹配的bean? 有点迷惑了....... 经过在网上找资料,发现和@MapperScan这个注解有关系,具体源码不止.但是这个注解会扫描路径下的所有类. 去掉这个注解就可以正常 ...

随机推荐

  1. Mybatis 系列10-结合源码解析mybatis 的执行流程

    [Mybatis 系列10-结合源码解析mybatis 执行流程] [Mybatis 系列9-强大的动态sql 语句] [Mybatis 系列8-结合源码解析select.resultMap的用法] ...

  2. 在线学习和在线凸优化(online learning and online convex optimization)—基础介绍1

    开启一个在线学习和在线凸优化框架专题学习: 1.首先介绍在线学习的相关概念 在线学习是在一系列连续的回合(rounds)中进行的: 在回合,学习机(learner)被给一个question:(一个向量 ...

  3. 安装 dubbo

    Dubbo的介绍: 是一个java版的RPC框架,由阿里巴巴开发并使用,结合zookeeper,实现流动计算架构完成资源调度和治理的工作 dubbo管控台可以对注册到zookeeper注册中心的服务或 ...

  4. 第11章 拾遗3:虚拟局域网(VLAN)

    1. 虚拟局域网(VLAN) (1)VLAN是建立在物理网络基础上的一种逻辑子网,它将把一个LAN划分成多个逻辑的局域网(VLAN),每个VLAN是一个广播域,VLAN内的主机间通信就和在一个LAN内 ...

  5. 由web项目中上传图片所引出的路径问题

    我在做javaweb项目的时候,有个项目中需要进行图片的上传,有次我重新部署项目后,发现之前上传的图片不见了,最后找出原因:图片上传在服务器目录上,而不是绝对路径,所以特别想弄清楚javaweb项目中 ...

  6. Java - 14 Java 日期时间

    java.util包提供了Date类来封装当前的日期和时间. Date类提供两个构造函数来实例化Date对象. 第一个构造函数使用当前日期和时间来初始化对象. Date( ) 第二个构造函数接收一个参 ...

  7. angularjs的cache

    首先要引入angular-cookies.js插件 angular.module('app').service('cache', ['$cookies', function($cookies){ th ...

  8. 爬虫概念 requests模块

    requests模块 - 基于如下5点展开requests模块的学习 什么是requests模块 requests模块是python中原生的基于网络请求的模块,其主要作用是用来模拟浏览器发起请求.功能 ...

  9. python学习过程中的踩坑记录<若干,随时更新>

    问题1:python中print的连串输出与java不一样? 输入print(code +"+++"); --在代码中写入,界面未报错,但是告诉你不行 会报错,如图: 解决办法: ...

  10. StanFord ML 笔记 第一部分

    本章节内容: 1.学习的种类及举例 2.线性回归,拟合一次函数 3.线性回归的方法: A.梯度下降法--->>>批量梯度下降.随机梯度下降 B.局部线性回归 C.用概率证明损失函数( ...