spring中bean id相同引發(fā)故障的分析與解決
前言
最近因?yàn)橥耣ean配置的問題導(dǎo)致生產(chǎn)環(huán)境往錯(cuò)誤的redis實(shí)例寫入大量的數(shù)據(jù),差點(diǎn)搞掛redis。經(jīng)過快速的問題定位,發(fā)現(xiàn)是同事新增一個(gè)redis配置文件,并且配置的RedisSentinelConfiguration的id是一樣的,然后在使用@Autowired注入bean的時(shí)候因?yàn)閟pring bean覆蓋的機(jī)制導(dǎo)致讀取的redis配置不是原來的。
總結(jié)起來,有兩點(diǎn)問題:
- 為什么相同bean id的bean會(huì)被覆蓋
- @Autowired注解不是按照byType的方式進(jìn)行注入的嗎
代碼如下:
public class UserConfiguration { private int id; private String name; private String city; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } }
UserClient:
public class UserClient { private UserConfiguration configuration; public UserClient(UserConfiguration configuration) { this.configuration = configuration; } public String getCity() { return configuration.getCity(); } }
beans.xml:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="userConfiguration" class="com.rhwayfun.springboot.starter.rest.UserConfiguration"> <property name="id" value="${user1.id}"/> <property name="name" value="${user1.name}"/> <property name="city" value="${user1.city}"/> </bean> <bean id="userClient" class="com.rhwayfun.springboot.starter.rest.UserClient" autowire="byName"> <constructor-arg ref="userConfiguration"/> </bean> </beans>
beans2.xml:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="userConfiguration" class="com.rhwayfun.springboot.starter.rest.UserConfiguration"> <property name="id" value="${user2.id}"/> <property name="name" value="${user2.name}"/> <property name="city" value="${user2.city}"/> </bean> <bean id="userClient2" class="com.rhwayfun.springboot.starter.rest.UserClient"> <constructor-arg ref="userConfiguration"/> </bean> </beans>
application.properties:
user1.id=1 user1.name=bean1 user1.city=Hangzhou user2.id=2 user2.name=bean2 user2.city=Shanghai
Applition:
@SpringBootApplication public class Application{ @Autowired UserClient userClient2; @PostConstruct public void init() { String city = userClient2.getCity(); System.out.println(city); } public static void main(String[] args) throws InterruptedException { SpringApplication.run(Application.class, args); Thread.sleep(Long.MAX_VALUE); } }
運(yùn)行程序,你會(huì)發(fā)現(xiàn)不管注入的userClient2還是userClient1,輸出的結(jié)果都是Shanghai。但是我們想實(shí)現(xiàn)的是,注入userClient1的時(shí)候輸出的應(yīng)該是Hangzhou,注入userClient2的時(shí)候輸出的應(yīng)該是Shanghai。這也是導(dǎo)致開頭說的問題的源頭所在。要實(shí)現(xiàn)這個(gè)效果很簡(jiǎn)單,UserConfiguration換一個(gè)名字就可以了。
但是,為什么換個(gè)名字就可以了呢,不同spring配置文件相同bean id的bean為什么不會(huì)分別創(chuàng)建呢?原因就在于spring 對(duì)具有相同bean id的實(shí)例做了覆蓋處理。你可以理解為一個(gè)Map,key是bean id,value就是class,那么當(dāng)兩次put相同id的bean的時(shí)候自然就被覆蓋了。
我們先回憶下bean的生命周期:
- 實(shí)例化
- 填充屬性
- 調(diào)用BeanNameAware的setBeanName方法
- 調(diào)用BeanFactoryAware的setBeanFactory方法
- 調(diào)用ApplicationContextAware的setApplicationContext方法
- 調(diào)用BeanPostProcessor的預(yù)初始化方法
- 調(diào)用InitializingBean的afterPropertiesSet方法
- 調(diào)用自定義的初始化方法
- 調(diào)用BeanPostProcessor的初始化方法
- 實(shí)例化完畢
問題出在注冊(cè)bean定義的時(shí)候,我們可以控制臺(tái)看到以下輸出
Overriding bean definition for bean 'userConfiguration' with a different definition: replacing [Generic bean: class [com.rhwayfun.springboot.starter.rest.UserConfiguration]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [/Users/chubin/IdeaProjects/spring-boot-learning-examples/ spring-boot-starter-rest/target/classes/beans.xml]] with [Generic bean: class [com.rhwayfun.springboot.starter.rest.UserConfiguration]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [/Users/chubin/IdeaProjects/spring-boot-learning-examples /spring-boot-starter-rest/target/classes/beans2.xml]]
就是說beans.xml中配置的UserConfiguration被beans2.xml配置的UserConfiguration實(shí)例覆蓋了。那么自然我們得到的結(jié)果是Shanghai了。
spring bean覆蓋
經(jīng)過上面的分析,我們已經(jīng)知道是因?yàn)楸桓采w的導(dǎo)致的,那么怎么體現(xiàn)的呢?遇到解決不了的問題,看源碼往往能得到答案:
這段代碼的邏輯就是,如果不允許具有相同bean id的實(shí)例存在就拋出異常,而這個(gè)值默認(rèn)是true,也就是允許存在相同的bean id定義。
@Autowired注解實(shí)現(xiàn)機(jī)制
bean覆蓋的問題解決了,那么還有一個(gè)問題,為什么使用@Autowired注入U(xiǎn)serClient沒有報(bào)錯(cuò)呢,明明配置了兩個(gè)類型的bean啊。@Autowired不是按照byType注入的嗎。
你確定嗎?不完全正確。
因?yàn)锧Autowired是spring提供的注解,我們可以看到是如何注入的代碼,在AutowiredAnnotationBeanPostProcessor.AutowiredMethodElement.inject()
方法中。
1.解析依賴
2.獲取候選bean、決定最終被被注入的最優(yōu)bean
3.最優(yōu)bean的決策過程:1)判斷時(shí)候有@Primary注解;2)如果沒有,得到最高優(yōu)先級(jí)的bean,也就是是否有實(shí)現(xiàn)了org.springframework.core.Ordered
接口的bean(優(yōu)先級(jí)比較,可以通過注解@Order(0)
指定,數(shù)字越小,優(yōu)先級(jí)越高);3)如果仍然沒有,則根據(jù)屬性名裝配
優(yōu)先級(jí)定義:
/** * Useful constant for the highest precedence value. * @see java.lang.Integer#MIN_VALUE */ int HIGHEST_PRECEDENCE = Integer.MIN_VALUE; /** * Useful constant for the lowest precedence value. * @see java.lang.Integer#MAX_VALUE */ int LOWEST_PRECEDENCE = Integer.MAX_VALUE;
至此,我們就能理解為什么@Autowired能夠通過屬性名注入不同的bean了。
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來一定的幫助,如果有疑問大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
- 使用監(jiān)聽器對(duì)Spring bean id進(jìn)行唯一校驗(yàn)過程解析
- Spring實(shí)戰(zhàn)之容器中的工程Bean用法示例
- Spring實(shí)戰(zhàn)之抽象Bean和子Bean定義與用法示例
- Spring實(shí)戰(zhàn)之調(diào)用實(shí)例工廠方法創(chuàng)建Bean操作示例
- Spring實(shí)戰(zhàn)之使用靜態(tài)工廠方法創(chuàng)建Bean操作示例
- Spring如何使用注解的方式創(chuàng)建bean
- Spring實(shí)戰(zhàn)之注入嵌套Bean操作示例
- Java類獲取Spring中bean的5種方式
- Spring的自動(dòng)裝配Bean的三種方式
- Spring實(shí)戰(zhàn)之獲得Bean本身的id操作示例
相關(guān)文章
SpringData如何通過@Query注解支持JPA語句和原生SQL語句
這篇文章主要介紹了SpringData如何通過@Query注解支持JPA語句和原生SQL語句,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11分享Java性能調(diào)優(yōu)的11個(gè)實(shí)用技巧
這些建議中的大多數(shù)都是基于Java的,但是也不一定,也有一些是可以應(yīng)用于所有的應(yīng)用程序和編程語言的。在我們分享基于Java的性能調(diào)優(yōu)技巧之前,讓我們先討論一下這些通用的性能調(diào)優(yōu)技巧2017-11-11spring+hibernate 兩種整合方式配置文件的方法
本篇文章主要介紹了spring+hibernate 兩種整合方式配置文件的方法,主要有兩種方式 1、注解方式 2、xml方式實(shí)現(xiàn),有興趣的可以了解一下。2017-04-04Java模板動(dòng)態(tài)生成word文件的方法步驟
最近項(xiàng)目中需要根據(jù)模板生成word文檔,模板文件也是word文檔。本文使用使用freemarker模板生成word文件,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-07-07Spring定時(shí)任務(wù)關(guān)于@EnableScheduling的用法解析
這篇文章主要介紹了Spring定時(shí)任務(wù)關(guān)于@EnableScheduling的用法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-06-06spring mvc @PathVariable綁定URI模板變量值方式
這篇文章主要介紹了spring mvc @PathVariable綁定URI模板變量值方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11