Spring中@Autowired注解的原理詳解
前言
在分享之前,我們先來(lái)回顧一下說(shuō)明是依賴注入,依賴注入中手動(dòng)注入和自動(dòng)裝配的幾種方式,以便于提高大家對(duì)本篇文章的理解。
一、依賴注入的方式
對(duì)于spring配置一個(gè)bean時(shí),如果需要給該bean提供一些初始化參數(shù),則需要通過(guò)依賴注入方式
所謂的依賴注入就是通過(guò)spring將bean所需要的一些參數(shù)傳遞到bean實(shí)例對(duì)象的過(guò)程(將依賴關(guān)系注入到對(duì)象中)
手動(dòng)注入
①使用屬性的setter方法注入 ,這是最常用的方式:
要求: 屬性注入要求Bean提供一個(gè)默認(rèn)的構(gòu)造函數(shù),并為需要注入的屬性提供對(duì)應(yīng)的Setter方法。Spring先調(diào)用Bean的默認(rèn)構(gòu)造函數(shù)實(shí)例化Bean對(duì)象,然后通過(guò)反射的方式調(diào)用Setter方法注入屬性值。
② 構(gòu)造器注入
使用方式:
第一,在類中,不用為屬性設(shè)置setter方法(但是可以有),但是需要生成該類帶參的構(gòu)造方法。
第二,在配置文件中配置該類的bean,并配置構(gòu)造器,在配置構(gòu)造器中用到了<constructor-arg>節(jié)點(diǎn),該節(jié)點(diǎn)有四個(gè)屬性:
- index是索引,指定注入的屬性位置,從0開(kāi)始;
- type是指該屬性所對(duì)應(yīng)的類型;
- ref 是指引用的依賴對(duì)象;
- value 當(dāng)注入的不是依賴對(duì)象,而是基本數(shù)據(jù)類型時(shí),就用value;
自動(dòng)裝配
XML方式進(jìn)行自動(dòng)裝配
大家可以看到用xml裝配bean是一件很繁瑣的事情,而且我們還要找到對(duì)應(yīng)類型的bean才能裝配。
創(chuàng)建應(yīng)用對(duì)象之間協(xié)作關(guān)系的行為稱為裝配。也就是說(shuō)當(dāng)一個(gè)對(duì)象的屬性是另一個(gè)對(duì)象時(shí),實(shí)例化時(shí),需要為這個(gè)對(duì)象屬性進(jìn)行實(shí)例化,這就是裝配。
如果一個(gè)對(duì)象只通過(guò)接口來(lái)表明依賴關(guān)系,那么這種依賴就能夠在對(duì)象本身毫不知情的情況下,用不同的具體實(shí)現(xiàn)進(jìn)行切換。但是這樣會(huì)存在一個(gè)問(wèn)題,在傳統(tǒng)的依賴注入配置中,我們必須要明確要給屬性裝配哪一個(gè)bean的引用,一旦bean很多,就不好維護(hù)了。基于這樣的場(chǎng)景,spring使用注解來(lái)進(jìn)行自動(dòng)裝配,解決這個(gè)問(wèn)題。自動(dòng)裝配就是開(kāi)發(fā)人員不必知道具體要裝配哪個(gè)bean的引用,這個(gè)識(shí)別的工作會(huì)由spring來(lái)完成。
在sping框架中共有5種自動(dòng)裝配 :
- no:默認(rèn)的方式是不進(jìn)行自動(dòng)裝配的,通過(guò)手工設(shè)置ref屬性來(lái)進(jìn)行裝配bean。
- byName:通過(guò)bean的名稱進(jìn)行自動(dòng)裝配,如果一個(gè)bean的 property 與另一bean 的name 相同,就進(jìn)行自動(dòng)裝配。
- byType:通過(guò)參數(shù)的數(shù)據(jù)類型進(jìn)行自動(dòng)裝配。
- constructor:利用構(gòu)造函數(shù)進(jìn)行裝配,并且構(gòu)造函數(shù)的參數(shù)通過(guò)byType進(jìn)行裝配。
- autodetect:自動(dòng)探測(cè),如果有構(gòu)造方法,通過(guò) construct的方式自動(dòng)裝配,否則使用 byType的方式自動(dòng)裝配。
上面所有方式的applicationContext.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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 使用注解前的配置 -->
<context:annotation-config></context:annotation-config>
<!-- 掃描 -->
<context:component-scan base-package="com.ape.pojo"></context:component-scan>
<!-- 手動(dòng)注入 -->
<!-- set注入 -->
<bean id="Student" class="com.ape.pojo.Student">
<property name="sid" value="1"></property>
<property name="sname" value="張三"></property>
<property name="smoney" value="100.00"></property>
<property name="cat" ref="cat"></property>
</bean>
<!-- 構(gòu)造器注入construct -->
<bean id="Student" class="com.ape.pojo.Student">
<constructor-arg name="sid" value="1"></constructor-arg>
<constructor-arg name="sname" value="張三"></constructor-arg>
<constructor-arg name="smoney" value="100.00"></constructor-arg>
<constructor-arg name="cat" ref="cat"></constructor-arg>
</bean>
<bean id="Student" class="com.ape.pojo.Student">
<constructor-arg type="int" value="1"></constructor-arg>
<constructor-arg type="java.lang.String" value="張三"></constructor-arg>
<constructor-arg type="double" value="100.00"></constructor-arg>
<constructor-arg type="com.ape.pojo.Cat" ref="cat"></constructor-arg>
</bean>
<bean id="Student" class="com.ape.pojo.Student">
<constructor-arg index="0" value="1"></constructor-arg>
<constructor-arg index="1" value="張三"></constructor-arg>
<constructor-arg index="2" value="100.00"></constructor-arg>
<constructor-arg index="3" ref="cat"></constructor-arg>
</bean>
<!-- 自動(dòng)裝配 -->
<!-- xml -->
<bean id="Student" class="com.ape.pojo.Student" autowire="no">
<property name="sid" value="1"></property>
<property name="sname" value="張三"></property>
<property name="smoney" value="100.00"></property>
<property name="cat" ref="cat"></property>
</bean>
<bean id="Student" class="com.ape.pojo.Student" autowire="byName">
</bean>
<bean id="Student" class="com.ape.pojo.Student" autowire="byType">
</bean>
<bean id="Student" class="com.ape.pojo.Student" autowire="constructor">
</bean>
<bean id="Student" class="com.ape.pojo.Student" autowire="default">
</bean>
</beans>二、注解@Autowired的自動(dòng)裝配原理
@Autowired自動(dòng)裝配過(guò)程
使用@Autowired注解來(lái)自動(dòng)裝配指定的bean。在使用@Autowired注解之前需要在Spring配置文件進(jìn)行配置<context:annotation-config />。
在啟動(dòng)spring IoC時(shí),容器自動(dòng)裝載了一個(gè)AutowiredAnnotationBeanPostProcessor后置處理器,當(dāng)容器掃描到@Autowied、@Resource或@Inject時(shí),就會(huì)在IOC容器自動(dòng)查找需要的bean,并裝配給該對(duì)象的屬性。
在使用@Autowired時(shí),首先在容器中查詢對(duì)應(yīng)類型的bean: 如果查詢結(jié)果剛好為一個(gè),就將該bean裝配給@Autowired指定的數(shù)據(jù); 如果查詢的結(jié)果不止一個(gè),那么@Autowired會(huì)根據(jù)名稱來(lái)查找;
如果上述查找的結(jié)果為空,那么會(huì)拋出異常。解決方法時(shí),使用required=false。
實(shí)現(xiàn)原理
①首先看看spring的源代碼定義

閱讀代碼我們可以看到,Autowired注解可以應(yīng)用在構(gòu)造方法,普通方法,參數(shù),字段,以及注解這五種類型的地方,它的保留策略是在運(yùn)行時(shí)。在Spring源代碼當(dāng)中,Autowired注解位于包org.springframework.beans.factory.annotation之中,如上圖。
②核心邏輯在buildAutowiringMetadata中
通過(guò)反射獲取該類的每一個(gè)字段和方法,并且分別用 findAutowiredAnnotation方法遍歷每一個(gè)字段和方法,如果有@Autowired注解修飾,則返回注解相關(guān)屬性。最后這個(gè)方法返回的就是包含所有帶有autowire注解修飾的一個(gè)InjectionMetadata集合。
private InjectionMetadata buildAutowiringMetadata(Class<?> clazz) {
if (!AnnotationUtils.isCandidateClass(clazz, this.autowiredAnnotationTypes)) {
return InjectionMetadata.EMPTY;
} else {
List<InjectedElement> elements = new ArrayList();
Class targetClass = clazz;
do {
List<InjectedElement> currElements = new ArrayList();
ReflectionUtils.doWithLocalFields(targetClass, (field) -> {
MergedAnnotation<?> ann = this.findAutowiredAnnotation(field);
if (ann != null) {
if (Modifier.isStatic(field.getModifiers())) {
if (this.logger.isInfoEnabled()) {
this.logger.info("Autowired annotation is not supported on static fields: " + field);
}
return;
}
boolean required = this.determineRequiredStatus(ann);
currElements.add(new AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement(field, required));
}
});
ReflectionUtils.doWithLocalMethods(targetClass, (method) -> {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
if (BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
MergedAnnotation<?> ann = this.findAutowiredAnnotation(bridgedMethod);
if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
if (Modifier.isStatic(method.getModifiers())) {
if (this.logger.isInfoEnabled()) {
this.logger.info("Autowired annotation is not supported on static methods: " + method);
}
return;
}
if (method.getParameterCount() == 0 && this.logger.isInfoEnabled()) {
this.logger.info("Autowired annotation should only be used on methods with parameters: " + method);
}
boolean required = this.determineRequiredStatus(ann);
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new AutowiredAnnotationBeanPostProcessor.AutowiredMethodElement(method, required, pd));
}
}
});
elements.addAll(0, currElements);
targetClass = targetClass.getSuperclass();
} while(targetClass != null && targetClass != Object.class);
return InjectionMetadata.forElements(elements, clazz);
}
}③InjectionMetadata類
這個(gè)類由兩部分組成: targetClass目標(biāo)類和我們要獲得的injectedElements集合。
public InjectionMetadata(Class<?> targetClass, Collection<InjectionMetadata.InjectedElement> elements) {
this.targetClass = targetClass;
this.injectedElements = elements;
}④ 實(shí)現(xiàn)注入邏輯
調(diào)用InjectionMetadata中的公共inject方法遍歷調(diào)用protect的inject方法
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
InjectionMetadata metadata = this.findAutowiringMetadata(beanName, bean.getClass(), pvs);
try {
metadata.inject(bean, beanName, pvs);
return pvs;
} catch (BeanCreationException var6) {
throw var6;
} catch (Throwable var7) {
throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", var7);
}
}⑤調(diào)用InjectionMetadata中的公共inject
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
Collection<InjectionMetadata.InjectedElement> checkedElements = this.checkedElements;
Collection<InjectionMetadata.InjectedElement> elementsToIterate = checkedElements != null ? checkedElements : this.injectedElements;
if (!((Collection)elementsToIterate).isEmpty()) {
Iterator var6 = ((Collection)elementsToIterate).iterator();
while(var6.hasNext()) {
InjectionMetadata.InjectedElement element = (InjectionMetadata.InjectedElement)var6.next();
element.inject(target, beanName, pvs);
}
}
}⑥遍歷調(diào)用protect的inject方法
protected void inject(Object target, @Nullable String requestingBeanName, @Nullable PropertyValues pvs) throws Throwable {
if (this.isField) {
Field field = (Field)this.member;
ReflectionUtils.makeAccessible(field);
field.set(target, this.getResourceToInject(target, requestingBeanName));
} else {
if (this.checkPropertySkipping(pvs)) {
return;
}
try {
Method method = (Method)this.member;
ReflectionUtils.makeAccessible(method);
method.invoke(target, this.getResourceToInject(target, requestingBeanName));
} catch (InvocationTargetException var5) {
throw var5.getTargetException();
}
}
}實(shí)質(zhì)就是inject也使用了反射技術(shù)并且依然是分成字段和方法去處理的。
到此這篇關(guān)于Spring中@Autowired注解的原理詳解的文章就介紹到這了,更多相關(guān)@Autowired注解的原理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java Web開(kāi)發(fā)之基于Session的購(gòu)物商店實(shí)現(xiàn)方法
這篇文章主要介紹了Java Web開(kāi)發(fā)之基于Session的購(gòu)物商店實(shí)現(xiàn)方法,涉及Java針對(duì)session的操作及數(shù)據(jù)庫(kù)操作技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10
springboot如何獲取yml文件的自定義參數(shù)
這篇文章主要介紹了springboot如何獲取yml文件的自定義參數(shù),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
Java對(duì)象和Map之間相互轉(zhuǎn)換的五種方法
在Java開(kāi)發(fā)中,經(jīng)常需要將Java對(duì)象轉(zhuǎn)換成Map,或者反過(guò)來(lái)將Map轉(zhuǎn)換成Java對(duì)象,這種轉(zhuǎn)換在很多場(chǎng)景下都非常有用,比如在序列化和反序列化過(guò)程中、在數(shù)據(jù)傳輸和持久化時(shí)、或者在進(jìn)行對(duì)象屬性的批量操作時(shí),本文將介紹幾種不同的方法來(lái)實(shí)現(xiàn)Java對(duì)象和Map之間的相互轉(zhuǎn)換2025-02-02
解決java.lang.ClassCastException的java類型轉(zhuǎn)換異常的問(wèn)題
這篇文章主要介紹了解決java.lang.ClassCastException的java類型轉(zhuǎn)換異常的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09
SpringBoot使用Kaptcha實(shí)現(xiàn)驗(yàn)證碼的生成與驗(yàn)證功能
這篇文章主要介紹了SpringBoot使用Kaptcha實(shí)現(xiàn)驗(yàn)證碼的生成與驗(yàn)證功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03

