深入Spring Boot:怎样排查expected single matching bean but found 2的异常
来源:互联网 发布:sql select as 用法 编辑:程序博客网 时间:2024/06/08 04:37
写在前面
这个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
那么有两个问题:
- 应用是在哪里要注入/获取到一个
DataSource
bean? - 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")
返回的信息是:
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
里定义的。
所以上面两个问题的答案是:
- 是spring boot代码里的
DataSourceInitializer.init() line: 71
这里要获取DataSource
,并且只允许有一个DataSource
实例 - h2DataSource1,h2DataSource2 是在
com.example.demo.expected.single.DemoExpectedSingleApplication
里定义的
解决问题
上面排查到的原因是:应用定义了两个DataSource
实例,但是spring boot却要求只有一个。那么有两种办法来解决:
- 使用
@Primary
来指定一个优先使用的DataSource
,这样子spring boot里自动初始的代码会获取到@Primary
的bean - 把spring boot自动初始化
DataSource
相关的代码禁止掉,应用自己来控制所有的DataSource
相关的bean
禁止的办法有两种:
在main函数上配置exclude
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class })在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的异常
- spring注解错误-expected single matching bean but found 2
- expected single matching bean but found 2
- expected single matching bean but found 2
- expected single matching bean but found 2
- expected single matching bean but found 2
- spring boot+mybatis 多数据源报错 expected single matching bean but found
- Injection expected single matching bean but found
- Spring的JUnit错误:NoSuchBeanDefinitionException: No unique bean of type,expected single matching bean but found 2 [dataSource1,data
- expected single matching bean but found 2 注解继承冲突
- 多数据源报错 expected single matching bean but found 2
- 开发:异常收集之 expected single matching bean but found 2
- spring依赖注入单元测试:expected single matching bean but found 2
- spring依赖注入单元测试:expected single matching bean but found 2
- spring依赖注入单元测试:expected single matching bean but found 2
- 在开发自己淘宝客网站配置spring 遇到问题:expected single matching bean but found 2
- No qualifying bean of type is defined: expected single matching bean but found 2
- mybatis封装dao servcie时碰到expected single matching bean but found 2:
- 设计模式之享元模式
- CSS 字体
- 【嵌入式学习历程15】多线程编程
- C语言实验——各位数字之和排序
- 14. Longest Common Prefix
- 深入Spring Boot:怎样排查expected single matching bean but found 2的异常
- 支持向量机原理与实践(二):scikit-learn中SVM的使用
- IT的道德与伦理
- Java集合框架--2
- springCloud和springBoot的关系
- iscsi网络磁盘共享
- Finance系列(4)之Credit Card
- 网络通信问题
- [贪心 高维前缀和] BZOJ5092. 分割序列