當前位置:
首頁 > 知識 > 深入 SpringBoot : 怎樣排查 expectedsinglematchingbeanbutfound 2 的異常

深入 SpringBoot : 怎樣排查 expectedsinglematchingbeanbutfound 2 的異常

(點擊

上方公眾號

,可快速關注)




來源:hengyunabc ,


blog.csdn.net/hengyunabc/article/details/78762121




寫在前面



這個

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")




返回的信息是:





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




禁止的辦法有兩種:




在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)




看完本文有收穫?請轉發分享給更多人


關注「ImportNew」,提升Java技能


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 ImportNew 的精彩文章:

推薦普通開發者學習使用的 6 個 JDK 內建工具
Jdk 動態代理異常處理分析,UndeclaredThrowableException

TAG:ImportNew |