AutowireCapableBeanFactory探密(2)——傳統裝配模式與現代註解驅動注入方式

回顧

上篇,介紹了AutowireCapableBeanFactory的作用:讓Spring管理的Bean去裝配和填充那些不被Spring託管的Bean,爲第三方框架賦能。其中,介紹AutowireCapableBeanFactory接口時簡單提了一下該接口定義了6個屬性,其中有5個(AUTOWIRE_*)是與autowireMode相關的常量值定義,用於描述該用何種方式來進行裝配Bean

正文

這裏有個容易讓人誤解的點,就是關於autowireMode的各個常量的命名。其中有兩個常量定義分別是int AUTOWIRE_BY_NAME = 1int AUTOWIRE_BY_TYPE = 2,從命名上看跟我們最常用的@Resouce、@Autowired這種註解使用的方式很像,但實際上二者八杆子打不着關係,千萬不要把他們混爲一談

實際上,註解注入方式在Spring中被稱爲annotation-driven injection,而autowireMode這種裝配在Spring裏其實被稱爲traditional autowiring

autowireMode之所以稱爲traditional,是因爲這種使用姿勢已經足夠老,老到新一代的Javaer,甚至都沒有在項目中用過這種裝配模式,上來就是註解也能很好地work,因此,traditional autowiring在Spring 2.5後已式微,大概只有在古董項目上才能窺之身影

先來看一下,Spring 2.5以前項目裏如何使用traditional autowiring

定義案例需要用到的類FooServiceBarService,其中FooService依賴BarService

public class FooService {
    private BarService barService;

    public void setBarService(BarService barService) {
        this.barService = barService;
    }
}

public class BarService {
}

在很久很久以前,那時候Spring還沒有大量使用註解,也沒有提供各類註解給應用層使用,各類配置項在都是依靠XML進行配置,如下所示

<bean id="fooService" class="com.example.demo.service.FooService">
    <property name="barService" ref="barService"/>
</bean>

<bean id="barService" class="com.example.demo.service.BarService"/>

需要用戶在XML配置文件中定義Spring需要管理哪些Bean,並通過手動裝配方式指定依賴關係。如果只有兩個Bean以及它們之間的依賴關係,這種方式也很OK,但如果有大量的Bean呢,都要手動指定那得多麻煩枯燥

byName

懶惰是科技進步的動力,程序員天性懶惰但又很聰明,想出了自動裝配的方式,如下所示:

<bean id="fooService" class="com.example.demo.service.FooService" autowire="byName"/>

<bean id="barService" class="com.example.demo.service.BarService"/>

這樣,無論FooService裏依賴多少其它Bean,都不再需要手動一個個裝配,通過autowire="byName"的方式,Spring 就會在構建FooService之後,內過Java Bean內省機制拿到Bean裏所有的propertyName(案例中的propertyName:barService),然後上Spring IoC容器中找到beanName爲propertyName的bean(barService)並將之注入,完成裝配。這種方式要求propertyName與beanName一致,故命名爲byName

byType

如果想要想打破上面的限制,不要求propertyName與beanName一致也能完成注入,可以配置爲autowire="byType",如下所示:

<bean id="fooService" class="com.example.demo.service.FooService" autowire="byType"/>

<bean id="baz" class="com.example.demo.service.BarService"/>

甚至將FooService對BarService的依賴改的"面目全非"(此處,propertyName:quz)

public class FooService {
    private BarService xxx;

    public void setQuz(BarService xxx) {
        this.xxx = xxx;
    }
}

將BarService的bean id設置爲baz,儘管與FooService中的propertyName(quz)不一致,但通過指定autowire="byType",依然能夠將barService注入到fooService實例中。

原理是: Spring 在構建FooService之後,內過Java Bean內省機制拿到Bean裏所有的propertyName(案例中的propertyName:quz),最終拿到DependencyDescriptor(依賴描述符,它描述了一個待注入的依賴信息:要麼是構造器參數,要麼是方法參數,要麼是字段,並且提供了非常友好的、一種統一的方式去訪問)。在本案例中,DependencyDescriptor描述的是:我的名字叫setQuz,是在FooService類中被聲名的(declaringClass),需要一個入參且其類型是BarService(parameterTypes、resolvableType)。Spring在IoC容器中找到type爲BarService的bean返回並注入,完成裝配。這種方式只要求待注入的參數,其類型能在Spring裏找到,故命名爲byType

小總結:無論是byName還是byType,Spring都是通過Java Bean的內省機制找到property,然後上IoC容器中找到對應的Bean來完成的注入,這也就是古董項目重充斥大量setter方法的緣由

簡單看一下源碼中,處理byNamebyType的地方,依然是在populateBean方法內部

// org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean

//如果是byName或byType
if (mbd.getResolvedAutowireMode() == AUTOWIRE_BY_NAME || mbd.getResolvedAutowireMode() == AUTOWIRE_BY_TYPE) {
	MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
	// Add property values based on autowire by name if applicable.
	if (mbd.getResolvedAutowireMode() == AUTOWIRE_BY_NAME) {
		autowireByName(beanName, mbd, bw, newPvs);
	}
	// Add property values based on autowire by type if applicable.
	if (mbd.getResolvedAutowireMode() == AUTOWIRE_BY_TYPE) {
		autowireByType(beanName, mbd, bw, newPvs);
	}
	pvs = newPvs;
}
// ...(省略)[省略的內容就包括處理@Resouce、@Autowired註解驅動注入的部分]

// 上面是收集屬性到pvs,並不執行屬性裝配的動作,下邊才真正執行
if (pvs != null) {
	applyPropertyValues(beanName, mbd, bw, pvs);
}

通過判斷autowireMode是byName還是byType,分別進入autowireByNameautowireByType方法,將待裝配的對象收集到pvs,最後再通過applyPropertyValues真正執行屬性裝配。此處再次提醒:該段邏輯與處理@Resouce、@Autowired的註解驅動注入無關,省略部分是上篇內容,即上篇內容提到ibp.postProcessProperties纔是處理annotation-driven injection的邏輯的地方,且真正執行了注入的邏輯

pvs是MutablePropertyValues類型實例,該類主要職責是維護內部的PropertyValue集合,實現了PropertyValues接口,提供對集合的增刪查操作

public class MutablePropertyValues implements PropertyValues, Serializable {

    private final List<PropertyValue> propertyValueList;
    // ...(省略)
}

public interface PropertyValues extends Iterable<PropertyValue>

PropertyValue代表的是某個Bean屬性的屬性名與屬性值,類似於一個鍵值對

public class PropertyValue extends BeanMetadataAttributeAccessor implements Serializable {

	private final String name;

	@Nullable
	private final Object value;
	// ...(省略)
}

因此,pvs也即List代表了某個Bean裏所有的屬性名及屬性值

constructor

byNamebyType都是setter方法注入,接着看看autowireMode = constructor構造器裝配使用姿勢

public class FooService {
    private BarService barService;

    public FooService(BarService barService) {
        this.barService = barService;
    }
}
<bean id="fooService" class="com.example.demo.service.FooService" autowire="constructor"/>

<bean id="baz" class="com.example.demo.service.BarService"/>

通過在FooService中定入參爲BarService的構造函數,在定義Spring bean時指定autowire="constructor",那麼Spring在構造FooService實例時,就會找到該構造函數,並從IoC容器中找到類型爲BarService的bean,傳入構造函數中進行構造,如此,生成的FooService實例就自動注入了BarService

原理涉及的核心代碼如下:

// org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance

Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
// 如果找到合適的構造器或者autowireMode = AUTOWIRE_CONSTRUCTOR 則執行autowireConstructor
if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
		mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
	return autowireConstructor(beanName, mbd, ctors, args);
}

這裏有一點特殊的地方值得一提:本案例中不配置autowire="constructor",也能夠通過構造器進行裝配,原因在於determineConstructorsFromBeanPostProcessors的返回值如果不爲空,表明找到了合適的構造器,也會進入autowireConstructor中執行構造器裝配

// org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#determineCandidateConstructors

// ...(省略)
if (!candidates.isEmpty()) { 
	// 與@Autowired、@Value 注入有關,不是本節重點,暫時忽略
	// Add default constructor to list of optional constructors, as fallback.
	if (requiredConstructor == null) {
		if (defaultConstructor != null) {
			candidates.add(defaultConstructor);
		}
		else if (candidates.size() == 1 && logger.isInfoEnabled()) {
			// ...(省略,log信息)
		}
	}
	candidateConstructors = candidates.toArray(new Constructor<?>[0]);
}
else if (rawCandidates.length == 1 && rawCandidates[0].getParameterCount() > 0) {
	candidateConstructors = new Constructor<?>[] {rawCandidates[0]}; // 有且只有一個非空參構造函數
}
// ...(省略兩個else if,它們與kotlin相關,跳過不看)
else {
	candidateConstructors = new Constructor<?>[0];
}

return (candidateConstructors.length > 0 ? candidateConstructors : null);

在目前的Spring實現中,AutowiredAnnotationBeanPostProcessor實現了determineCandidateConstructors方法,如果找到有且只有一個非空參構造器,那麼就可以成功返回
,很顯然,我們的FooService符合這樣的條件,因此返回public FooService(BarService barService)構造器,進入autowireConstructor中執行構造器裝配

假如給FooService再添加一個空參構造器,或者別的帶參構造器,就不再滿足上述條件,需要配置autowire="constructor"才能實現構造器注入

AUTODETECT

該裝配模式是byTypeconstructor的混合體,從Spring 3.0已經標識爲Deprecated,Spring認爲,如果你不想關注到底是哪種autowireMode,就想閉着眼睛讓Spring幫忙注入,那就用註解注入的方式吧,這樣同樣實現效果的同時,也能讓注入語義更清晰一些

// org.springframework.beans.factory.support.AbstractBeanDefinition#getResolvedAutowireMode

public int getResolvedAutowireMode() {
	if (this.autowireMode == AUTOWIRE_AUTODETECT) {
		// Work out whether to apply setter autowiring or constructor autowiring.
		// If it has a no-arg constructor it's deemed to be setter autowiring,
		// otherwise we'll try constructor autowiring.
		Constructor<?>[] constructors = getBeanClass().getConstructors();
		for (Constructor<?> constructor : constructors) {
			if (constructor.getParameterCount() == 0) {
				return AUTOWIRE_BY_TYPE;
			}
		}
		return AUTOWIRE_CONSTRUCTOR;
	}
	else {
		return this.autowireMode;
	}
}

判斷的邏輯很簡單,如果存在空參構造函數,就認爲是想用byType的裝配模式,否則就是constructor的構造模式

由於Spring 已經將這種autowireMode標識爲Deprecated,且除了稍顯簡便也沒別的其他優勢,我們還是能不用則不用

AUTOWIRE_NO

默認的autowireMode就是不裝配,無特殊行爲,不作過多介紹

到了Spring 3.0,XML配置方式已經開始展現頹勢,官方推出了Annotation Config,搭配@Configuration、@Bean、@DependsOn等註解,作爲XML的等價配置方式,試圖取代XML的配置地位,從今日的結果看來,這一目的已經達成,新項目中基本不再出現舊的配置方式

簡單介紹一下Annotation Config如何配置autowireMode,以下兩種形式是等價的:

<bean id="fooService" class="com.example.demo.service.FooService" autowire="byName"/>

<bean id="barService" class="com.example.demo.service.BarService"/>
@Bean(autowire = Autowire.BY_NAME)
public FooService fooService() {
    return new FooService();
}

@Bean
public BarService BarService() {
    return new BarService();
}

@Bean註解有一個autowire屬性,取值類似於autowireMode,不過在此處它的取值只有三個:NO、BY_NAME、BY_TYPE,沒有CONSTRUCT、AUTODETECT,簡單想一下可知道,Annotation Config的配置方式不適合CONSTRUCT,以及可能包含CONSTRUCT方式的AUTODETECT。
無論XML配置還是Annotaion Config配置autowireMode,它們的底層原理都一樣,最終都是給AbstractBeanDefinitionautowireMode屬性賦值,因此在真正執行裝配時都是判斷BeanDefinition的autowireMode屬性值來決定裝配模式,默認取值: AUTOWIRE_NO

此外,上篇文章提到的AutowireCapableBeanFactory有很多細粒度控制Bean生命週期的方法,如autowire、autowireBeanProperties等帶有autowireMode參數,作用與上面介紹的是一樣的,最終都落實到AbstractBeanDefinitionautowireMode屬性上,也會將待裝配對象收集到pvs,最後調用applyPropertyValues方法執行裝配


以上便是傳統裝配模式的介紹,接下來將介紹現代註解驅動注入方式

Annotation-driven injection的方式,在目前的應用開發中被廣泛的運用,打開任意一個三層模型(Controller-Service-Dao)的Java Web項目,在各層總是能看到@Resouce、@Autowired、@Value、@Inject(javax.inject.Inject),甚至是自定義注入註解(AutowiredAnnotationBeanPostProcessor#setAutowiredAnnotationType提供這個能力,不需要太多工作量,很迅速地完成自定義),Spring賦予了這些註解DI的能力 ,總體上可作用於Field,Setter Method,Construct,Parameter,AnnotationType(不同註解作用的範圍並不一致),再加上註解自身能夠配置的屬性、與其它註解的配合(如@Qualifier)使用,能玩出的花樣真是不勝枚舉。他們之間的功能有重疊有交叉,不必要每一個都掌握,徒增精力,只掌握最小子集的註解又可涵蓋註解功能的總和,滿足任何場景的開發即可。故此,本節探討一下大概率使用最多的@Resouce、@Autowired在字段注入的場景

@Resouce

案例代碼如下:

@Service
public class FooService {
    @Resource
    private BarService barService;
}

@Service
public class BarService {}

Spring 處理@Resouce體現在Spring Bean Lifecycle的兩個階段

  1. 構建Bean實例之後,填充屬性之前,收集Bean裏面所有@Resouce相關的信息並緩存起來,注意,此階段是作信息收集之用,不作具體處理(CommonAnnotationBeanPostProcessor#postProcessMergedBeanDefinition)
  2. 在填充屬性的過程中,將待裝配的對象收集到pvs後,執行applyPropertyValues之前(執行傳統的屬性裝配之前),將第1階段收集到的信息利用起來,執行Annotation-driven injection(CommonAnnotationBeanPostProcessor#postProcessProperties)

先看第一階段,收集待注入的元信息

// org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#postProcessMergedBeanDefinition

@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
	super.postProcessMergedBeanDefinition(beanDefinition, beanType, beanName);
	// 收集待注入的元信息(@Resouce相關信息)
	InjectionMetadata metadata = findResourceMetadata(beanName, beanType, null);
	metadata.checkConfigMembers(beanDefinition);
}

private InjectionMetadata findResourceMetadata(String beanName, final Class<?> clazz, @Nullable PropertyValues pvs) {
	// ...(省略)
	// 構建待注入元信息,並放入緩存
	metadata = buildResourceMetadata(clazz);
	this.injectionMetadataCache.put(cacheKey, metadata);

	return metadata;
}

// 構建待注入元信息
private InjectionMetadata buildResourceMetadata(final Class<?> clazz) {
	List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
	Class<?> targetClass = clazz;

	do {
		final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();

		// 通過反射查看class所有field上是否含有@Resouce註解,找到後就封裝成ResourceElement
		ReflectionUtils.doWithLocalFields(targetClass, field -> {
			// ...(省略)
			else if (field.isAnnotationPresent(Resource.class)) {
				if (Modifier.isStatic(field.getModifiers())) {
					throw new IllegalStateException("@Resource annotation is not supported on static fields");
				}
				if (!this.ignoredResourceTypes.contains(field.getType().getName())) {
					currElements.add(new ResourceElement(field, field, null));
				}
			}
		});
		// ...(下邊是收集方法注入方式的元信息,省略)

		elements.addAll(0, currElements);
		targetClass = targetClass.getSuperclass();
	}
	while (targetClass != null && targetClass != Object.class);

	return new InjectionMetadata(clazz, elements);
}

通過源碼可以看到,最後待注入的元信息其實是ResourceElement,看着看一下ResourceElement的構造函數做了些什麼事情

// 從上面的buildResourceMetadata可以看出,member、ae都是field
public ResourceElement(Member member, AnnotatedElement ae, @Nullable PropertyDescriptor pd) {
	super(member, pd);
	Resource resource = ae.getAnnotation(Resource.class);
	String resourceName = resource.name();
	Class<?> resourceType = resource.type();
	this.isDefaultName = !StringUtils.hasLength(resourceName);
	
	// 處理resourceName
	if (this.isDefaultName) {
	    // 如果使用@Resource時未指定name,默認取member的name做爲resouceName
	    // 對於member爲field的case,取的field name
		resourceName = this.member.getName();
		
		//如果member是個方法,代表setter注入,那就把setXxx前的set去掉後將首字段小寫作爲resouceName,即xxx
		if (this.member instanceof Method && resourceName.startsWith("set") && resourceName.length() > 3) {
			resourceName = Introspector.decapitalize(resourceName.substring(3));
		}
	}
	else if (embeddedValueResolver != null) {
		// 如果手動指定resoureName了,就使用embeddedValueResolver.resolveStringValue去解析
		resourceName = embeddedValueResolver.resolveStringValue(resourceName);
	}
	
	// 處理resouceType
	if (Object.class != resourceType) {
	    // 如果手動指了定resouceType,需要檢查一下是不是瞎指定,比如field明明是個A,卻通過resouceType指定爲B type,那明顯會有問題
		checkResourceType(resourceType);
	}
	else {
		// No resource type specified... check field/method.
		// 如果未手動指定resourceType,則取member或者pd的類型
		resourceType = getResourceType();
	}
	this.name = (resourceName != null ? resourceName : "");
	this.lookupType = resourceType;
	String lookupValue = resource.lookup();
	this.mappedName = (StringUtils.hasLength(lookupValue) ? lookupValue : resource.mappedName());
	Lazy lazy = ae.getAnnotation(Lazy.class);
	this.lazyLookup = (lazy != null && lazy.value());
}

構造函數主要就是給name、lookupType、lookupValue等屬性賦值,其中name爲resourceName,lookupType爲resourceType,而且一般都不會爲空,即便我們沒有通過註解手動指定它們的值,也有默認的方式獲取resourceName跟resourceType,這也就是我們在多數場景直接打上註解就完事,而不指定具體參數的原因

這種設計原則值得借鑑與學習:如果我們需要給他人提供某項能力,出於優雅設計的原則,首先調研、考慮大多數場景怎樣使用,我們將這類應用的場景以代碼的形式固化下來,當發生API調用時不需要提供過多參數也可以正常運行,針對另外一小部分需要自定義的場景,通過提供一種optional的能力,允許用戶選填,選填項優先級高於固化代碼的方式,就能覆蓋100%的場景。這也是一種"約定優於配置(convention over configuration)"的思想實踐

另外,如果我們通過註解的屬性手動指定了name,會走resourceName = embeddedValueResolver.resolveStringValue(resourceName);這段邏輯,embeddedValueResolver是EmbeddedValueResolver類的實例,該類特別強大,不但能夠解析"佔位符",還能夠解析"Spring EL"表達式,簡言之,@Value裏能配置啥值,在這就能配啥值,而且解析的效果是一樣的。因此,如下的方式也是與上邊的方式等下的

// application.properties
quz = barService

// ----------
public class FooService {
    @Resource(name = "${quz}")
    private BarService barService;
}

Spring 的設計真是驚爲天人,考慮到各種我們開發過程中可能使用到的姿勢並且提供了能力,真是讓人佩服五體投地

再看第二階段,執行真正的注入邏輯

// org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#postProcessProperties

public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
	// 該方法上面出現過,第一次出現的時候是構建並且放入緩存,這是第二次出現 ,直接從緩存中獲取
	// ResourceElement是InjectionMetadata的子類,因此metadata其實是ResourceElement的實例
	InjectionMetadata metadata = findResourceMetadata(beanName, bean.getClass(), pvs);
	try {
		// 執行注入
		metadata.inject(bean, beanName, pvs);
	}
	catch (Throwable ex) {
		throw new BeanCreationException(beanName, "Injection of resource dependencies failed", ex);
	}
	return pvs;
}

// org.springframework.beans.factory.annotation.InjectionMetadata#inject

public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
	Collection<InjectedElement> checkedElements = this.checkedElements;
	Collection<InjectedElement> elementsToIterate =
			(checkedElements != null ? checkedElements : this.injectedElements);
	if (!elementsToIterate.isEmpty()) {
	    // 循環每一個待注入的元素,執行注入
		for (InjectedElement element : elementsToIterate) {
			if (logger.isTraceEnabled()) {
				logger.trace("Processing injected element of bean '" + beanName + "': " + element);
			}
			// 注入
			element.inject(target, beanName, pvs);
		}
	}
}

通過反射執行注入邏輯field.set(target, getResourceToInject(target, requestingBeanName));


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, getResourceToInject(target, requestingBeanName));
	}
	// ...(方法注入,省略)
}

接着看getResourceToInject(target, requestingBeanName),獲取待注入對象實例

// org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.ResourceElement#getResourceToInject

protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) {
    // 我們並沒有配置@Lazy註解,因此走`getResource(this, requestingBeanName)`邏輯
	return (this.lazyLookup ? buildLazyResourceProxy(this, requestingBeanName) :
			getResource(this, requestingBeanName));
}

protected Object getResource(LookupElement element, @Nullable String requestingBeanName)
		throws NoSuchBeanDefinitionException {
	// ...(省略)
	// resourceFactory是BeanFactory的實例,在構建CommonAnnotationBeanPostProcessor實例的時候通過BeanFactoryAware#setBeanFactory回調接口注入進來
	return autowireResource(this.resourceFactory, element, requestingBeanName);
}
// org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#autowireResource

protected Object autowireResource(BeanFactory factory, LookupElement element, @Nullable String requestingBeanName)
			throws NoSuchBeanDefinitionException {

	Object resource;
	Set<String> autowiredBeanNames;
	String name = element.name;

	// factory是DefaultListableBeanFactory的實例對象,因此也就是AutowireCapableBeanFactory的實例對象
	if (factory instanceof AutowireCapableBeanFactory) { // (if 1)
		AutowireCapableBeanFactory beanFactory = (AutowireCapableBeanFactory) factory;
		DependencyDescriptor descriptor = element.getDependencyDescriptor();
		// fallbackToDefaultTypeMatch默認值爲true
		if (this.fallbackToDefaultTypeMatch && element.isDefaultName && !factory.containsBean(name)) { // (if 2)
			autowiredBeanNames = new LinkedHashSet<>();
			// resouce爲解析出來的待注入實例對象
			resource = beanFactory.resolveDependency(descriptor, requestingBeanName, autowiredBeanNames, null);
			if (resource == null) {
				throw new NoSuchBeanDefinitionException(element.getLookupType(), "No resolvable resource object");
			}
		}
		else { // (else 1)
			// resouce爲解析出來的待注入實例對象
			resource = beanFactory.resolveBeanByName(name, descriptor);
			autowiredBeanNames = Collections.singleton(name);
		}
	}
	else { // (else 2)
		// 基本上不會考到這
		resource = factory.getBean(name, element.lookupType);
		autowiredBeanNames = Collections.singleton(name);
	}

	// factory是DefaultListableBeanFactory的實例對象,因此也就是ConfigurableBeanFactory的實例對象
	if (factory instanceof ConfigurableBeanFactory) { // (if 3)
		ConfigurableBeanFactory beanFactory = (ConfigurableBeanFactory) factory;
		for (String autowiredBeanName : autowiredBeanNames) {
			if (requestingBeanName != null && beanFactory.containsBean(autowiredBeanName)) {
				// 將依賴關係註冊到Spring中,是個雙向的描述
				// 即dependentBeanMap、dependenciesForBeanMap
				beanFactory.registerDependentBean(autowiredBeanName, requestingBeanName);
			}
		}
	}

	return resource;
}

factory是DefaultListableBeanFactory的實例對象,因此也就是AutowireCapableBeanFactory、ConfigurableBeanFactory的實例對象,所以if 1、if 3都會進去,if 1邏輯用於解析待注入的bean,if 3邏輯是維護依賴Bean與被依賴Bean之間的關係

if 2代碼塊中,有個名爲fallbackToDefaultTypeMatch的屬性,它表示的含義是:當未通過註解手動指定name(意味着使用defaultName,即字段名或屬性名),Spring根據defaultName上Ioc容器找不到Bean時,是否需要fallback,根據type到Ioc容器中找,默認值爲true

因此進入if 2塊需同時滿足以下三個條件:

  1. fallbackToDefaultTypeMatch = true(默認值是true)
  2. element.isDefaultName = true(不能通過註解指定name)
  3. factory.containsBean(name) = false(根據name在Ioc容器中找不到Bean)

否則,直接進入else 1的邏輯: resource = beanFactory.resolveBeanByName(name, descriptor);

fallbackToDefaultTypeMatch默認true,一般也不會改爲false,忽略這種情況不考慮,那進入else 1的情況有2個:

  1. 通過註解手動指定name,如@Resource(name = “quz”)
  2. IoC中包含beanName = name的Bean或者BeanDefinition

在本案例中,由於factory.containsBean(“barService”) = true,因此進入的是else 1的邏輯

網上有說法:

@Resouce默認根據name進行注入,如果找不到就根據type進行注入

其實這種說法不夠嚴謹,也不太準確,能成立的前提條件是:name是fieldName或者propertyName,即未通過註解進行明確指定。如若通過註解手動指定name,就會走else 1邏輯,壓根不會根據type進行注入

由於name、type都可以手動指定或不指定,因此出現4種排列組合的情況:

  1. 不指定name與type: 通過fileName或propertyName判斷Ioc中是否存在,存在進入else 1 ,不存在就通過type去找,找不到或找到多個都拋異常(if 2)
  2. 僅指定name: 通過name找唯一的Bean,找不到拋異常(else 1)
  3. 僅指定type: 通過type找唯一的Bean,找不到或找到多個都拋異常(if 2)
  4. 指定name、指定type: 通過name找唯一的Bean,找不到拋異常(else 1)

注: 不建議把這4個排列組合的結論記下來,而是記住原理,遇到問題能夠根據原理分析出來原因並加以解決

if 2、else 1都是將待注入的Bean解析出來,所謂解析就是在Spring容器中完成Bean的構造、屬性填充、初始化等,然後返回一個能用的Bean。由於解析Bean是一個比較複雜的過程,涉及的知識點很多,不在本篇討論的重點,暫且按下不表。只要知道它能返回一個Bean即可,接着,拿着這個返回結果(待注入對象)就可以完成注入

@Autowired

Spring 處理@Autowired同樣體現在Spring Bean Lifecycle的兩個階段,與@Resouce處理方式如出一轍,甚至它們之間的代碼有大部分都是相似的,因此熟悉@Resouce的處理流程之後,再來看@Autowired的處理代碼,簡直不能再輕鬆

第一階段,收集待注入的元信息(InjectionMetadata)

AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition -> findAutowiringMetadata -> buildAutowiringMetadata

第二階段,執行真正的注入邏輯

AutowiredAnnotationBeanPostProcessor#postProcessProperties -> findAutowiringMetadata -> InjectionMetadata#inject -> AutowiredFieldElement#inject
// org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject

protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
	Field field = (Field) this.member;
	Object value;
	if (this.cached) {
		value = resolvedCachedArgument(beanName, this.cachedFieldValue);
	}
	else {
		DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
		desc.setContainingClass(bean.getClass());
		Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
		Assert.state(beanFactory != null, "No BeanFactory available");
		TypeConverter typeConverter = beanFactory.getTypeConverter();
		try {
			// value爲解析出來的待注入實例對象
			value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
		}
		catch (BeansException ex) {
			throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
		}
		// ...(省略)
	}
	if (value != null) {
		ReflectionUtils.makeAccessible(field);
		field.set(bean, value);
	}
}

還是同樣的配方,還是熟悉的味道,不過過程更簡單了,因爲沒有name、type的屬性,而是將field包裝成DependencyDescriptor,通過beanFactory.resolveDependency根據類型解析出來待注入的實例對象,然後通過反射直接注入

思考

瞭解到傳統裝配模式現代註解驅動注入方式的區別之後,我們在具體開發過程中,該如何抉擇呢?很顯然,Spring的方式是在引導我們向Annotation-driven Injection靠攏,也就是在正常的業務代碼中應該使用現代註解驅動注入的方式,這種方式已經是大家約定俗成的習慣用法,是行業共識,沒有太多的勾通成本。實際上,在Annotation Config中使用autowireMode也是不被建議的,從Spring 5.1開始,@Bean註解的autowire屬性已經被標識爲Deprecated,文檔上提到這種方式已經被@Autowired註解所取代

那麼是否意味着traditional autowiring就應該被打入冷宮,不應該使用了呢?其實不是的,至少從Spring 5.1看來,AbstractBeanDefinition的autowireMode還沒有被標識爲過期,AutowireCapableBeanFactory的細粒度生命週期方法帶有autowireMode參數也沒有被標識爲過期,它們還在一些場合——爲第三方框架賦能的場景發揮着光和熱

試想一下,如果我是一個第三方框架的開發者,開發的框架想與Spring結合,想借助它的一些能力,但是又不想與Spring結合得太過緊密,而是僅僅做爲DI的可選項之一,那麼設計上就要考慮松耦合,不能嵌入太多Spring相關的註解或者代碼,而是獨立一個-spring的模塊,在該模塊中引入AutowireCapableBeanFactory,根據場景配置不同的autowireMode,使之與框架自身需要DI的場景、對象進行結合,而框架的實例對象看起來與Spring毫不相關,只有這樣,才能與Spring進行解耦合

舉個Dubbo的例子,在Dubbo DI過程,調用的是Object object = objectFactory.getExtension(pt, property);。objectFactory是一個AdaptiveExtensionFactory(自適應的DI工廠,是個composite模式),裏邊維護了一個ExtensionFactory(真正的工廠類)集合,該集合包含兩個工廠,一個依靠Dubbo自身的SPI機制加載元素(SpiExtensionFactory),另一個直接從Spring獲取元素(SpringExtensionFactory)

public class AdaptiveExtensionFactory implements ExtensionFactory {
	private final List<ExtensionFactory> factories;
	// ...(省略)

    @Override
    public <T> T getExtension(Class<T> type, String name) {

        for (ExtensionFactory factory : factories) {
            T extension = factory.getExtension(type, name);
            // 任意一個工廠獲取到元素,就立即返回
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }

}
public class SpiExtensionFactory implements ExtensionFactory {
    @Override
    public <T> T getExtension(Class<T> type, String name) {
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
            if (!loader.getSupportedExtensions().isEmpty()) {
                return loader.getAdaptiveExtension();
            }
        }
        return null;
    }
}

public class SpringExtensionFactory implements ExtensionFactory {
	// ...(省略)
    
    @Override
    @SuppressWarnings("unchecked")
    public <T> T getExtension(Class<T> type, String name) {
        for (ApplicationContext context : contexts) {
        // 從Spring的ApplicationContext裏獲取Bean
            if (context.containsBean(name)) {
                Object bean = context.getBean(name);
                if (type.isInstance(bean)) {
                    return (T) bean;
                }
            }
        }
        return null;
    }
}

藉助Spring能力,從ApplicationContext裏獲取到需要的Bean之後,裝配到Dubbo框架的實例對象中,而完全看不出Spring的痕跡,很好地與Spring解耦

總結

本文介紹了傳統裝配模式與現代註解驅動注入方式之間的區別,先是明白了傳統裝配模式中的byNamebyType@Resouce@Autowired等註解之間並無任何的聯繫,在底層處理上它們是兩套邏輯,不可"見名知義"而混問一談。接着介紹了Annotation-driven injection的方式,從中挑選了field injection進行了詳細過程的分析,並澄清了@Resouce一個網上不嚴謹的說法。最後,對於這兩種方式的使用進行了一個思考,對在什麼場合使用什麼方式能更瞭然於胸


導讀: AutowireCapableBeanFactory探密(1)——爲第三方框架賦能

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章