Spring中@Autowired注解的原理詳解
前言
在分享之前,我們先來回顧一下說明是依賴注入,依賴注入中手動注入和自動裝配的幾種方式,以便于提高大家對本篇文章的理解。
一、依賴注入的方式
對于spring配置一個bean時,如果需要給該bean提供一些初始化參數(shù),則需要通過依賴注入方式
所謂的依賴注入就是通過spring將bean所需要的一些參數(shù)傳遞到bean實例對象的過程(將依賴關系注入到對象中)
手動注入
①使用屬性的setter方法注入 ,這是最常用的方式:
要求: 屬性注入要求Bean提供一個默認的構造函數(shù),并為需要注入的屬性提供對應的Setter方法。Spring先調用Bean的默認構造函數(shù)實例化Bean對象,然后通過反射的方式調用Setter方法注入屬性值。
② 構造器注入
使用方式:
第一,在類中,不用為屬性設置setter方法(但是可以有),但是需要生成該類帶參的構造方法。
第二,在配置文件中配置該類的bean,并配置構造器,在配置構造器中用到了<constructor-arg>節(jié)點,該節(jié)點有四個屬性:
- index是索引,指定注入的屬性位置,從0開始;
- type是指該屬性所對應的類型;
- ref 是指引用的依賴對象;
- value 當注入的不是依賴對象,而是基本數(shù)據(jù)類型時,就用value;
自動裝配
XML方式進行自動裝配
大家可以看到用xml裝配bean是一件很繁瑣的事情,而且我們還要找到對應類型的bean才能裝配。
創(chuàng)建應用對象之間協(xié)作關系的行為稱為裝配。也就是說當一個對象的屬性是另一個對象時,實例化時,需要為這個對象屬性進行實例化,這就是裝配。
如果一個對象只通過接口來表明依賴關系,那么這種依賴就能夠在對象本身毫不知情的情況下,用不同的具體實現(xiàn)進行切換。但是這樣會存在一個問題,在傳統(tǒng)的依賴注入配置中,我們必須要明確要給屬性裝配哪一個bean的引用,一旦bean很多,就不好維護了?;谶@樣的場景,spring使用注解來進行自動裝配,解決這個問題。自動裝配就是開發(fā)人員不必知道具體要裝配哪個bean的引用,這個識別的工作會由spring來完成。
在sping框架中共有5種自動裝配 :
- no:默認的方式是不進行自動裝配的,通過手工設置ref屬性來進行裝配bean。
- byName:通過bean的名稱進行自動裝配,如果一個bean的 property 與另一bean 的name 相同,就進行自動裝配。
- byType:通過參數(shù)的數(shù)據(jù)類型進行自動裝配。
- constructor:利用構造函數(shù)進行裝配,并且構造函數(shù)的參數(shù)通過byType進行裝配。
- autodetect:自動探測,如果有構造方法,通過 construct的方式自動裝配,否則使用 byType的方式自動裝配。
上面所有方式的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> <!-- 手動注入 --> <!-- 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> <!-- 構造器注入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> <!-- 自動裝配 --> <!-- 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的自動裝配原理
@Autowired自動裝配過程
使用@Autowired注解來自動裝配指定的bean。在使用@Autowired注解之前需要在Spring配置文件進行配置<context:annotation-config />。
在啟動spring IoC時,容器自動裝載了一個AutowiredAnnotationBeanPostProcessor后置處理器,當容器掃描到@Autowied、@Resource或@Inject時,就會在IOC容器自動查找需要的bean,并裝配給該對象的屬性。
在使用@Autowired時,首先在容器中查詢對應類型的bean: 如果查詢結果剛好為一個,就將該bean裝配給@Autowired指定的數(shù)據(jù); 如果查詢的結果不止一個,那么@Autowired會根據(jù)名稱來查找;
如果上述查找的結果為空,那么會拋出異常。解決方法時,使用required=false。
實現(xiàn)原理
①首先看看spring的源代碼定義
閱讀代碼我們可以看到,Autowired注解可以應用在構造方法,普通方法,參數(shù),字段,以及注解這五種類型的地方,它的保留策略是在運行時。在Spring源代碼當中,Autowired注解位于包org.springframework.beans.factory.annotation之中,如上圖。
②核心邏輯在buildAutowiringMetadata中
通過反射獲取該類的每一個字段和方法,并且分別用 findAutowiredAnnotation方法遍歷每一個字段和方法,如果有@Autowired注解修飾,則返回注解相關屬性。最后這個方法返回的就是包含所有帶有autowire注解修飾的一個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類
這個類由兩部分組成: targetClass目標類和我們要獲得的injectedElements集合。
public InjectionMetadata(Class<?> targetClass, Collection<InjectionMetadata.InjectedElement> elements) { this.targetClass = targetClass; this.injectedElements = elements; }
④ 實現(xiàn)注入邏輯
調用InjectionMetadata中的公共inject方法遍歷調用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); } }
⑤調用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); } } }
⑥遍歷調用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(); } } }
實質就是inject也使用了反射技術并且依然是分成字段和方法去處理的。
到此這篇關于Spring中@Autowired注解的原理詳解的文章就介紹到這了,更多相關@Autowired注解的原理內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Java Web開發(fā)之基于Session的購物商店實現(xiàn)方法
這篇文章主要介紹了Java Web開發(fā)之基于Session的購物商店實現(xiàn)方法,涉及Java針對session的操作及數(shù)據(jù)庫操作技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-10-10springboot如何獲取yml文件的自定義參數(shù)
這篇文章主要介紹了springboot如何獲取yml文件的自定義參數(shù),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03解決java.lang.ClassCastException的java類型轉換異常的問題
這篇文章主要介紹了解決java.lang.ClassCastException的java類型轉換異常的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09SpringBoot使用Kaptcha實現(xiàn)驗證碼的生成與驗證功能
這篇文章主要介紹了SpringBoot使用Kaptcha實現(xiàn)驗證碼的生成與驗證功能,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-03-03