回顧
上篇,介紹了AutowireCapableBeanFactory
的作用:讓Spring管理的Bean去裝配和填充那些不被Spring託管的Bean,爲第三方框架賦能。其中,介紹AutowireCapableBeanFactory
接口時簡單提了一下該接口定義了6個屬性,其中有5個(AUTOWIRE_*)是與autowireMode
相關的常量值定義,用於描述該用何種方式來進行裝配Bean
正文
這裏有個容易讓人誤解的點,就是關於autowireMode
的各個常量的命名。其中有兩個常量定義分別是int AUTOWIRE_BY_NAME = 1
、int 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
定義案例需要用到的類FooService
、BarService
,其中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方法的緣由
簡單看一下源碼中,處理byName
、byType
的地方,依然是在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
,分別進入autowireByName
、autowireByType
方法,將待裝配的對象收集到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
byName
、byType
都是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
該裝配模式是byType
和constructor
的混合體,從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,它們的底層原理都一樣,最終都是給AbstractBeanDefinition
的autowireMode
屬性賦值,因此在真正執行裝配時都是判斷BeanDefinition的autowireMode屬性值來決定裝配模式,默認取值: AUTOWIRE_NO
此外,上篇文章提到的AutowireCapableBeanFactory
有很多細粒度控制Bean生命週期的方法,如autowire、autowireBeanProperties
等帶有autowireMode
參數,作用與上面介紹的是一樣的,最終都落實到AbstractBeanDefinition
的autowireMode
屬性上,也會將待裝配對象收集到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的兩個階段
- 構建Bean實例之後,填充屬性之前,收集Bean裏面所有@Resouce相關的信息並緩存起來,注意,此階段是作信息收集之用,不作具體處理(CommonAnnotationBeanPostProcessor#postProcessMergedBeanDefinition)
- 在填充屬性的過程中,將待裝配的對象收集到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塊需同時滿足以下三個條件:
- fallbackToDefaultTypeMatch = true(默認值是true)
- element.isDefaultName = true(不能通過註解指定name)
- factory.containsBean(name) = false(根據name在Ioc容器中找不到Bean)
否則,直接進入else 1的邏輯: resource = beanFactory.resolveBeanByName(name, descriptor);
fallbackToDefaultTypeMatch
默認true,一般也不會改爲false,忽略這種情況不考慮,那進入else 1的情況有2個:
- 通過註解手動指定name,如@Resource(name = “quz”)
- IoC中包含beanName = name的Bean或者BeanDefinition
在本案例中,由於factory.containsBean(“barService”) = true,因此進入的是else 1的邏輯
網上有說法:
@Resouce默認根據name進行注入,如果找不到就根據type進行注入
其實這種說法不夠嚴謹,也不太準確,能成立的前提條件是:name是fieldName或者propertyName,即未通過註解進行明確指定。如若通過註解手動指定name,就會走else 1邏輯,壓根不會根據type進行注入
由於name、type都可以手動指定或不指定,因此出現4種排列組合的情況:
- 不指定name與type: 通過fileName或propertyName判斷Ioc中是否存在,存在進入else 1 ,不存在就通過type去找,找不到或找到多個都拋異常(if 2)
- 僅指定name: 通過name找唯一的Bean,找不到拋異常(else 1)
- 僅指定type: 通過type找唯一的Bean,找不到或找到多個都拋異常(if 2)
- 指定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解耦
總結
本文介紹了傳統裝配模式與現代註解驅動注入方式之間的區別,先是明白了傳統裝配模式中的byName
、byType
與@Resouce
、@Autowired
等註解之間並無任何的聯繫,在底層處理上它們是兩套邏輯,不可"見名知義"而混問一談。接着介紹了Annotation-driven injection的方式,從中挑選了field injection進行了詳細過程的分析,並澄清了@Resouce一個網上不嚴謹的說法。最後,對於這兩種方式的使用進行了一個思考,對在什麼場合使用什麼方式能更瞭然於胸