原文首發于掘金
作者:walkinger
鏈接:https://juejin.im/post/5d4163ede51d4561f64a078b
有同事在開發新功能測試時,報了個錯,大致就是,在使用 @Autowired 注入時,某個類有兩個bean,一個叫a,一個叫b,Spring不知道該使用哪個bean注入。
一般這種情況應該聲明注入哪個bean,他沒有聲明,他不知道這個類有兩個bean,他說他和別人寫的一樣,別的都不報錯。
OK,那來分析下吧。
前提:@Autowired是根據類型(byType)進行自動裝配的
在默認情況下只使用 @Autowired 注解進行自動注入時,Spring 容器中匹配的候選 Bean 數目必須有且僅有一個。
使用@Autowired 注解時要注意以下情況:
spring4 security,所以在使用 @Autowired 注解時要滿足以下條件:
什么意思呢?我們用代碼說話。
首先,我們建一個實體類 Student :
public class Student{private String name;//getter and setter...
}
然后我們在 Spring 容器中創建多個 Student 的實例,如下:
我們通過 XML 配置文件的方式在 Spring 的配置文件里實現一個類型多個 bean。
如下,創建了兩個 Student 的 bean ,id 分別為 student 和 student02,對應的 bean 的name 屬性 分別為小紅和小明。
<bean id="student" class="com.autowiredtest.entity.Student"><property name="name" value="小紅"/>
</bean>
<bean id="student02" class="com.autowiredtest.entity.Student"><property name="name" value="小明"/>
</bean>
我們也可以通過使用配置類+注解的方式實現一個類型多個 bean:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class StudentConfiguration{@BeanStudent student03(){Student student = new Student();student.setName("小華");return student;}@BeanStudent student04(){Student student = new Student();student.setName("小玲");return student;}
}
Spring Framework,當然這個 StudentConfiguration 配置類需要被注解掃描器掃描到,即要在掃包的配置里加上這個類所在的包。 所以現在Student 類有4個 Bean,student、student02、student03和student04。
(注:這里體現了用兩種方式來實現一個類型多個bean。)
在controller聲明 Student 類型的變量,并使用 @Autowired 注入
@Controller
public class AutowiredTestController{@Autowiredprivate Student student;@RequestMapping("/AutowiredTest")@ResponseBodypublic String loanSign(){String docSignUrl = "ok";System.out.println("--------------要打印了------------");System.out.println(student.getName());System.out.println("--------------打印結束------------");return docSignUrl;}
}
(這里就是用一個簡單的spring mvc的小demo來驗證這個問題。)
運行web項目,期間沒有報錯,訪問http://localhost:8080/AutowiredTest,控制臺輸出:
Springboot常用注解,是不是很奇怪?和上面說的不符合啊!這里 Student 類有4個實例,為啥沒報錯。
非但沒有在調用時拋出 BeanCreationException 異常,反而正常運行,輸出【小紅】,說明注入的是 id 為 student 的 bean。
即 :當@Autowired private Student student;
時
我們的 Student 變量名是 student ,那么在 Spring 為其注入時,如果有多個 bean 的話就默認去容器中找 bean id 為 student 得那個 bean。
驗證一下
把 Student 的變量名改為 student02,@Autowired private Student student02
重啟,并訪問http://localhost:8080/AutowiredTest,控制臺輸出:
Spring常用的三種注入方式、同樣,改為 student03、student04控制臺相應輸出小華、小玲。
所以我們的大膽猜想是正確的!這里使用的 Spring 版本為 4.2.0.RELEASE。
驗證一下
把版本改低一點。首先,把 Spring 版本改為2.5(@Autowired第一次出現在該版本),這時候 @ResponseBody @Configuration 以及 @Bean 都不能用了(更高版本才能用),所以現在有兩個 Bean ,student 和 student02。
這時候啟動項目,不報錯,訪問http://localhost:8080/AutowiredTest,報錯:
控制臺錯誤信息:
1 嚴重: Context initialization failed2 org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'autowiredTestController': Autowiring of fields failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.autowiredtest.entity.Student com.autowiredtest.controller.AutowiredTestController.student02;nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [com.autowiredtest.entity.Student] is defined: expected single matching bean but found 2: [student, student02]3 at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessAfterInstantiation(AutowiredAnnotationBeanPostProcessor.java:231)4 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:978)5 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:539)6 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory$1.run(AbstractAutowireCapableBeanFactory.java:485)7 at java.security.AccessController.doPrivileged(Native Method)8 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:455)9 at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:251)
10 at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:169)
11 at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:248)
12 at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:170)
13 at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:413)
14 at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:735)
15 at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:369)
16 at org.springframework.web.servlet.FrameworkServlet.createWebApplicationContext(FrameworkServlet.java:332)
17 at org.springframework.web.servlet.FrameworkServlet.initWebApplicationContext(FrameworkServlet.java:266)
18 at org.springframework.web.servlet.FrameworkServlet.initServletBean(FrameworkServlet.java:236)
19 at org.springframework.web.servlet.HttpServletBean.init(HttpServletBean.java:126)
20 at javax.servlet.GenericServlet.init(GenericServlet.java:158)
21 at org.apache.catalina.core.StandardWrapper.initServlet(StandardWrapper.java:1279)
22 at org.apache.catalina.core.StandardWrapper.loadServlet(StandardWrapper.java:1192)
23 at org.apache.catalina.core.StandardWrapper.allocate(StandardWrapper.java:864)
24 at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:134)
25 at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
26 at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:501)
27 at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:170)
28 at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:98)
29 at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:950)
30 at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
31 at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
32 at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1040)
33 at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:607)
34 at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.doRun(AprEndpoint.java:2441)
35 at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.run(AprEndpoint.java:2430)
36 at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
37 at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
38 at java.lang.Thread.run(Thread.java:745)
39 Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.autowiredtest.entity.Student com.autowiredtest.controller.AutowiredTestController.student02; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [com.autowiredtest.entity.Student] is defined: expected single matching bean but found 2: [student, student02]
40 at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredElement.inject(AutowiredAnnotationBeanPostProcessor.java:375)
41 at org.springframework.beans.factory.annotation.InjectionMetadata.injectFields(InjectionMetadata.java:61)
42 at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessAfterInstantiation(AutowiredAnnotationBeanPostProcessor.java:228)
43 ... 35 more
44 Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [com.autowiredtest.entity.Student] is defined: expected single matching bean but found 2: [student, student02]
45 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.resolveDependency(AbstractAutowireCapableBeanFactory.java:425)
46 at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredElement.inject(AutowiredAnnotationBeanPostProcessor.java:361)
47 ... 37 more
控制臺錯誤輸出圖:
springsecurity官網,關鍵的異常信息:
這時候報了預期的錯了。
我們再增大版本號,去測試一下到底是哪個版本號開始兼容了這個問題。
Spring Authorization Server?這是版本的發布情況,采用二分法逼近。
先從當前用的版本 4.2.0.RELEASE 換到 3.2.18.RELEASE(Spring 3.x的最后一個版本)也是沒問題的,3.0.0.RELEASE也沒問題,2.5.5報錯,2.5.6報錯,2.5.6.SEC03報錯(2.x的最后一個版本)。
因此可以斷定,從 Spring 3.x 開始兼容了這個問題,使更加人性化。
所以上述關于 @Autowired 的使用規則要發生變化了:
@Autowired private Student student;
,student 就是多個Bean中其中一個Bean的id)Spring Security、如果我創建變量的時候就是想自定義變量名,咋辦? 如:@Autowired private Student stu; 這樣的話對于一些 IDE 這樣寫完就直接回給出警告或直接報錯,請看:
idea 直接告訴你,現在有兩個 bean ,一個叫 student 另一個叫 student02,你現在寫的變量名不是這倆種的任一個,你寫的不對,給你報錯!
而對于另外一些 IDE 則是沒這么智能,如 eclipse。那就只有等到測試的時候才能發現了。
回到正題,怎么自定義變量名呢?有2種方法:1、@Autowired 和 @Qualifier
我們可以使用 @Qualifier 配合 @Autowired 來解決這些問題。 和找不到一個類型匹配 Bean 相反的一個錯誤是:如果 Spring 容器中擁有多個候選 Bean,Spring 容器在裝配時也會拋出 BeanCreationException 異常(Spring 3.x之后)。
Spring 允許我們通過 @Qualifier 注釋指定注入 Bean 的名稱,這樣就消除歧義了。 所以 @Autowired 和 @Qualifier 結合使用時,自動注入的策略就從 byType 轉變成 byName 了。
@Autowired
@Qualifier("student")
private Student stu;
這樣 Spring 會找到 id 為 student 的 bean 進行裝配。
另外,當不能確定 Spring 容器中一定擁有某個類的 Bean 時,可以在需要自動注入該類 Bean 的地方可以使用 @Autowired(required = false)
,這等于告訴 Spring:在找不到匹配 Bean 時也不報錯。(required默認是true) 如:
@Autowired(required = false)
public Student stu
但是idea可不慣著你,依舊給你報錯提示,雖然這時候可以忽略它繼續啟動,但訪問時還是會報 BeanCreationException:
Spring Boot,
當然,一般情況下,使用 @Autowired 的地方都是需要注入 Bean 的,使用了自動注入而又允許不注入的情況一般僅會在開發環境或測試時碰到(如為了快速啟動 Spring 容器,僅引入一些模塊的 Spring 配置文件),所以 @Autowired(required = false) 會很少用到。2、使用@Resource(name = "xxx") 注解
這個和 @Autowired 和 @Qualifier 的作用一樣,可以理解為是二者的合并吧。
分析了這么多,我那個同事的問題是因為啥呢? 仔細看代碼,那兩個 bean 一個id是類名小寫,一個id是類名小寫并加了別的后綴。 而他使用時起的變量名不是類名小寫的,直接就是類名(很難發現寫的是大寫),所以匹配不到任何一個bean。 這是工作不細心導致的,同時也引發了對@Autowired注入時兼容性的研究分析。
springmvc的注解?附:為什么@Autowired 和 @Qualifier注解不合成一個?
@Autowired 可以對成員變量、方法以及構造函數進行注釋,而 @Qualifier 的標注對象是成員變量、方法入參、構造函數入參。 正是由于注釋對象的不同,所以 Spring 不將 @Autowired 和 @Qualifier 統一成一個注釋類。
//對成員變量使用 @Qualifier 注釋
public class Boss { @Autowired private Car car; @Autowired @Qualifier("office") private Office office; … }
//對構造函數變量使用 @Qualifier 注釋
public class Boss { private Car car; private Office office; @Autowired public Boss(Car car , @Qualifier("office")Office office){ this.car = car; this.office = office ; }
}
作者:walkinger
鏈接:https://juejin.im/post/5d4163ede51d4561f64a078b
來源:掘金
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。
工作时间:8:00-18:00
客服电话
电子邮件
admin@qq.com
扫码二维码
获取最新动态