Spring 原始碼分析之 bean 依賴注入原理(注入屬性)

小小木發表於2019-04-07

     最近在研究Spring bean 生命週期相關知識點以及原始碼,所以打算寫一篇 Spring bean生命週期相關的文章,但是整理過程中發現涉及的點太多而且又很複雜,很難在一篇文章中把Spring bean 的生命週期講清楚,所以最後決定分解成幾個模組來寫,最後在寫一篇文章把各個內容串聯起來,這樣可以講的更細更深入不會猶豫篇幅而講的太籠統。bean 生命週期所涉及的主要流程如下圖所示。

Spring 原始碼分析之 bean 依賴注入原理(注入屬性)
     在上篇文章寫了有bean例項建立相關的內容,感興趣的朋友可以去看看Spring 原始碼分析之 bean 例項化原理這篇文章。
     本文想寫bean 生命週期的第二階段 bean 的依賴注入(注入屬性)部分按下面幾個步驟來講解。

  • Spring容器與依賴注入
  • 什麼時候會觸發依賴注入?
  • 關於依賴注入與屬性注入的幾點說明
  • 解析Spring 依賴注入原始碼部分
  • 總結

一. Spring容器與依賴注入

    Spring最有名的高階特性非ioc莫屬了,雖然ioc 不是本次要討論的重點,但還是有必要說一下。對於Spring的ioc我不想過多教科書式的解釋這名次,我也相信每個使用Spring的程式設計師都有自己的理解,只是有時很難把自己的理解清楚的解釋給別人而已。下面我說說我自己的理解,有說錯的地方歡迎大家指正。Spring ioc 至少要具備一下兩點功能:

準備Bean 整個生命週期需要的資料
這一步是Spring 容器啟動的時候會 定位我們的配置檔案,載入檔案,並解析成Bean的定義檔案BeanDefinition來為下一步作準備,這個BeanDefinition會貫穿Spring 啟動初始化的整個流程,非常重要,因為他是資料基礎。

管理Bean的整個生命週期

  • 需要具備建立一個Bean的功能
  • 需要具備根據Bean與Bean之間的關係依賴注入功能(本次要講的內容)
  • 需要能夠執行初始化方法以及銷燬方法

有了以上幾個功能之後Spring ioc 就能夠控制bean的流程了,這不控制反轉了麼。而我們只需用註解或者配置檔案配置bean的特性以及依賴關係即可。下面說一下有關ApplicationContext 和 BeanDefinition:

1. 核心容器ApplicationContext

    上述這些功能都可以由Spring容器(比如 ApplicationContext)來實現,Spring啟動時會把所有需要的bean掃描並註冊到容器裡,在這個過程當中Spring會根據我們定義的bean之間的依賴關係來進行注入,依賴關係的維護方式有兩種即XML配置檔案或者註解,Spring啟動時會把這些依賴關係轉化成Spring能夠識別的資料結構BeanDefinition,並根據它來進行bean的初始化,依賴注入等操作。下面看看一個簡單的Spring容器如下圖:

Spring 原始碼分析之 bean 依賴注入原理(注入屬性)
Spring 依賴注入的實現是由像ApplicationContext這種容器來實現的,右邊的Map裡儲存這bean之間的依賴關係的定義BeanDefinition,比如OrderController依賴OrderService這種,具體定義下面介紹。

結論:BeanDefinition提供了原材料資料基礎,而ApplicationContext 提供了流程的設計與實現的演算法

2. Bean依賴關係的定義

    我們需要為Spring容器提供所有bean的定義以及bean之間的依賴關係,從而進行bean的依賴注入通常有兩種方式,XML配置或者註解,不管是那種最終都會解析成BeanDefinition。

通過XML配置Bean依賴關係

<beans xmlns="http://www.springframework.org/schema/beans">
    <!-- orderDao 不需要任何的依賴 -->
    <bean id="orderDao" class="spring.DI.OrderDao"/>
    <!-- orderService 需要依賴orderDao -->
    <bean id="orderService" class="spring.DI.OrderService">
        <property name="orderDao" ref="orderDao"/>
    </bean>
    <!-- orderController orderService 也間接依賴了orderDao -->
    <bean id="orderController" class="spring.DI.OrderController">
        <property name="orderService" ref="orderService"/>
    </bean>
</beans>
複製程式碼
public class OrderController {

    private OrderService orderService;

    public OrderService getOrderService() {
        return orderService;
    }

    public void setOrderService(OrderService orderService) {
        this.orderService = orderService;
    }
}
複製程式碼

這種注入方式叫做set 方法注入,只需xml配置 加上對引用的bean的get set方法即可

通過註解定義置Bean依賴關係

<context:component-scan base-package="xxx.yyy"/>
複製程式碼
@Controller
public class OrderController {
    
    @Autowired
    private OrderService orderService;

    public OrderController() {
    }
}
複製程式碼
@Service
public class OrderService {

    @Autowired
    private OrderDao orderDao;

    public OrderService() {

    }
}
複製程式碼
@Repository
public class OrderDao {

    public OrderDao() {
    }

}
複製程式碼

Spring 啟動時會把我們的定義資訊轉化成Spring能看懂的BeanDefinition,然後就可以由容器來建立bean以及依賴注入了,具體依賴注入的時候對於配置檔案和註解的處理手段還不同,這個一會兒在解釋。

二. 什麼時候會觸發依賴注入?

  • Spring 容器啟動初始化的時候(所有單例非懶載入的bean)
  • 懶載入(lazy-init)的bean 第一次進行getBean的時候

1.Spring 容器啟動初始化的時候

ApplicationContext context = new ClassPathXmlApplicationContext("spring-beans.xml");
複製程式碼
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)throws BeansException {
    super(parent);
    setConfigLocations(configLocations);
    if (refresh) {
        // 容器初始化入口
        refresh();
    }
}
複製程式碼
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        prepareRefresh();
        // Prepare the bean factory for use in this context.
        prepareBeanFactory(beanFactory);
        // Allows post-processing of the bean factory in context subclasses.
        postProcessBeanFactory(beanFactory);
        // Invoke factory processors registered as beans in the context.
        invokeBeanFactoryPostProcessors(beanFactory);
        // Register bean processors that intercept bean creation.
        registerBeanPostProcessors(beanFactory);
        // Instantiate all remaining (non-lazy-init) singletons.
        // 初始化所有非 懶載入的bean!!!!
        finishBeanFactoryInitialization(beanFactory);
        // Last step: publish corresponding event.
        finishRefresh();
    }
  }
複製程式碼

finishBeanFactoryInitialization(beanFactory);// 初始化所有非 懶載入的bean!!!

protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
   	// Stop using the temporary ClassLoader for type matching.
   	beanFactory.setTempClassLoader(null);
   	// 此處省略多行與本次無關程式碼
   	// Instantiate all remaining (non-lazy-init) singletons.
   	beanFactory.preInstantiateSingletons();
   }
複製程式碼
public void preInstantiateSingletons() throws BeansException {
   // 所有beanDefinition集合
   List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames);
   // 觸發所有非懶載入單例bean的初始化
   for (String beanName : beanNames) {
      RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
      // 判斷是否是懶載入單例bean,如果是單例的並且不是懶載入的則在Spring 容器
      if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
          // 判斷是否是FactoryBean
         if (isFactoryBean(beanName)) {
             // 對FactoryBean的處理
         }else {
             // 如果是普通bean則進行初始化依賴注入,此 getBean(beanName)接下來觸發的邏輯跟
             // context.getBean("beanName") 所觸發的邏輯是一樣的
            getBean(beanName);
         }
      }
   }
}
複製程式碼
@Override
public Object getBean(String name) throws BeansException {   
    return doGetBean(name, null, null, false);
}
複製程式碼

2.懶載入(lazy-init)的bean 第一次進行getBean

懶載入的bean 第一次進行getBean的操作呼叫的也是同一個方法

@Override
public Object getBean(String name) throws BeansException {   
    return doGetBean(name, null, null, false);
}
複製程式碼

doCreateBean是依賴注入的入口,也是我們本次要談的核心函式。該方法具體實現在AbstractAutowireCapableBeanFactory類,感興趣的朋友可以進去看看呼叫鏈。下面才剛剛開始進入依賴注入原始碼階段。

三. 關於依賴注入與屬性注入的幾點說明

依賴注入其實是屬性注入的一種特殊型別,他的特殊之處在於他要注入的是Bean,同樣由Spring管理的Bean,而不是其他的引數,如String,List,Set,Array這種。

Spring 原始碼分析之 bean 依賴注入原理(注入屬性)

Spring 原始碼分析之 bean 依賴注入原理(注入屬性)
普通的屬性的值用 value (型別包括 String list set map ...)

Bean型別的屬性的引用 ref,這種注入屬於依賴注入

四. 解析Spring 依賴注入原始碼部分

  • 依賴注入實現的入口
  • 註解形式注入的原始碼
  • xml 配置形式注入的原始碼

1. 依賴注入實現的入口

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
	//第一步 建立bean例項 還未進行屬性填充和各種特性的初始化
	BeanWrapper instanceWrapper = null;
	if (instanceWrapper == null) {
		instanceWrapper = createBeanInstance(beanName, mbd, args);
	}
	final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
	Class<?> beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);
	Object exposedObject = bean;
	try {
	    // 第二步 進行依賴注入(注入屬性)
	    populateBean(beanName, mbd, instanceWrapper);
	    if (exposedObject != null) {
	     // 第三步  執行bean的初始化方法
		exposedObject = initializeBean(beanName, exposedObject, mbd);
	    }
	}catch (Throwable ex) {
	 //  拋相應的異常
	}
	return exposedObject;
}

複製程式碼

我們這裡需要關注的是第二步關於依賴注入這一塊,下面這行程式碼

populateBean(beanName, mbd, instanceWrapper);
複製程式碼
protected void populateBean(String beanName, RootBeanDefinition mbd, BeanWrapper bw) {
        // 所有的屬性
	PropertyValues pvs = mbd.getPropertyValues();
	// 這裡是處理自動裝配型別的, autowire=byName 或者byType。如果不配置不走這個分支,xml或註解都可配 
	if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME ||
			mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {
		pvs = newPvs;
	}
	// 後處理器是否已經準備好(後處理器會處理已@Autowired 形式來注入的bean, 有一個  
	// 子類AutowiredAnnotationBeanPostProcessor來處理@Autowired注入的bean)
	boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();
	// 是否需要依賴檢查
	boolean needsDepCheck = (mbd.getDependencyCheck() != RootBeanDefinition.DEPENDENCY_CHECK_NONE);
	if (hasInstAwareBpps || needsDepCheck) {
		PropertyDescriptor[] filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
		if (hasInstAwareBpps) {
			for (BeanPostProcessor bp : getBeanPostProcessors()) {
				if (bp instanceof InstantiationAwareBeanPostProcessor) {
					InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor)bp;
					// 這裡會處理對註解形式的注入 重點!!!!
					pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
					if (pvs == null) {
						return;
					}
				}
			}
		}
	}
    // 注入引數的方法(註解的Bean的依賴注入除外)
	applyPropertyValues(beanName, mbd, bw, pvs);
}
複製程式碼

2. 註解形式注入的原始碼

// 後處理器是否已經準備好(後處理器會處理已@Autowired 形式來注入的bean, 有一個  
// 子類AutowiredAnnotationBeanPostProcessor來處理@Autowired注入的bean)
boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();
// 是否需要依賴檢查
boolean needsDepCheck = (mbd.getDependencyCheck() != RootBeanDefinition.DEPENDENCY_CHECK_NONE);
if (hasInstAwareBpps || needsDepCheck) {
	PropertyDescriptor[] filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
	if (hasInstAwareBpps) {
		for (BeanPostProcessor bp : getBeanPostProcessors()) {
			if (bp instanceof InstantiationAwareBeanPostProcessor) {
				InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor)bp;
				// 這裡會處理對註解形式的注入,比如 @Autowired註解 由類AutowiredAnnotationBeanPostProcessor來處理
				pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
				if (pvs == null) {
					return;
				}
			}
		}
	}
}
複製程式碼
pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
複製程式碼

Spring 原始碼分析之 bean 依賴注入原理(注入屬性)

@Override
public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
        // 這裡定義了把誰注入到哪裡
	InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
	try {  
	   // 進行注入
	   metadata.inject(bean, beanName, pvs);
	}catch (Throwable ex) {
		throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
	}
	return pvs;
}
複製程式碼

InjectionMetadata在這個類裡頭封裝了依賴的bean與被依賴的bean的資訊,比如orderCcontroller 依賴orderService,需要把orderService 注入到orderController。下面貼一下我debug的圖片

Spring 原始碼分析之 bean 依賴注入原理(注入屬性)
injectedElements 就是所有需要被注入的bean

Spring 原始碼分析之 bean 依賴注入原理(注入屬性)

protected void inject(Object target, String requestingBeanName, PropertyValues pvs) throws Throwable {
	if (this.isField) {
		Field field = (Field) this.member;
		ReflectionUtils.makeAccessible(field);
		field.set(target, getResourceToInject(target, requestingBeanName));
	}else {
		if (checkPropertySkipping(pvs)) {
		   return;
		}
		try {
		   Method method = (Method) this.member;
		   ReflectionUtils.makeAccessible(method);
		   method.invoke(target, getResourceToInject(target, requestingBeanName));
		}catch (InvocationTargetException ex) {
		  throw ex.getTargetException();
		}
	}
}
複製程式碼

3. xml 配置形式注入的原始碼

applyPropertyValues(beanName, mbd, bw, pvs);
複製程式碼

這個步驟主要做的就是把屬性轉換成相對應的類的屬性型別,並最後注入到依賴的bean裡頭,由一下步驟組成:

  • 判斷是否已轉換
  • 進行轉換
  • 注入到bean
protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) {
        MutablePropertyValues mpvs = null;
        List<PropertyValue> original;
        if (pvs instanceof MutablePropertyValues) {
            mpvs = (MutablePropertyValues) pvs;
            // 判斷是否已轉換,已經轉換了則return
            if (mpvs.isConverted()) {
                // Shortcut: use the pre-converted values as-is.
                bw.setPropertyValues(mpvs);
                return;
            }
            original = mpvs.getPropertyValueList();
        }

        TypeConverter converter = getCustomTypeConverter();
        BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this, beanName, mbd, converter);

        // Create a deep copy, resolving any references for values.
        List<PropertyValue> deepCopy = new ArrayList<PropertyValue>(original.size());
        boolean resolveNecessary = false;
        for (PropertyValue pv : original) {
            if (pv.isConverted()) {
                deepCopy.add(pv);
            } else {
                // 屬性名 如(name,orderService)
                String propertyName = pv.getName();
                // 未轉換前的值,稍後貼出debug時的圖
                Object originalValue = pv.getValue();
                // 轉換後的值,進行轉換處理(重要!!!!)
                Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
                Object convertedValue = resolvedValue;
                // 
                if (resolvedValue == originalValue) {
                    if (convertible) {
                        pv.setConvertedValue(convertedValue);
                    }
                    deepCopy.add(pv);
                } else if (convertible && originalValue instanceof TypedStringValue &&
                        !((TypedStringValue) originalValue).isDynamic() &&
                        !(convertedValue instanceof Collection || ObjectUtils.isArray(convertedValue))) {
                    pv.setConvertedValue(convertedValue);
                    deepCopy.add(pv);
                } else {
                    resolveNecessary = true;
                    deepCopy.add(new PropertyValue(pv, convertedValue));
                }
            }
        }
        // 轉換完成
        if (mpvs != null && !resolveNecessary) {
            mpvs.setConverted();
        }
        // 這裡就是進行屬性注入的地方,跟上面的inject方法類似
        try {
            bw.setPropertyValues(new MutablePropertyValues(deepCopy));
        } catch (BeansException ex) {
            throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Error setting property values", ex);
        }
    }
複製程式碼

下面介紹上述步驟中的兩個核心的流程:

  • 進行轉換操作,生成最終需要注入的型別的物件
  • 進行注入操作

1.進行轉換操作,生成最終需要注入的型別的物件

這個方法會返回一個我們最終要注入的一個屬性對應類的一個物件

Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
複製程式碼

根據引數型別做具體的轉換處理,引數型別包括 1.Bean 2.Array 3.List 4.Set 5.Map 6.String 等等。


public Object resolveValueIfNecessary(Object argName, Object value) {
     // Bean 型別
    if (value instanceof RuntimeBeanReference) {
        RuntimeBeanReference ref = (RuntimeBeanReference) value;
        return resolveReference(argName, ref);
    }
    else if (value instanceof ManagedArray) {
        // 處理陣列
        return resolveManagedArray(argName, (List<?>) value, elementType);
    }
    else if (value instanceof ManagedList) {
        // 處理list
        return resolveManagedList(argName, (List<?>) value);
    }
    else if (value instanceof ManagedSet) {
        // 處理set
        return resolveManagedSet(argName, (Set<?>) value);
    }
    else if (value instanceof ManagedMap) {
        // 處理map
        return resolveManagedMap(argName, (Map<?, ?>) value);
    }
    else if (value instanceof TypedStringValue) {
    // 處理字串
    }
    else {
        return evaluate(value);
    }
}
複製程式碼
private Object resolveReference(Object argName, RuntimeBeanReference ref) {
    try {
	String refName = ref.getBeanName();
	refName = String.valueOf(doEvaluate(refName));
	if (ref.isToParent()) {
		if (this.beanFactory.getParentBeanFactory() == null) {
			throw new BeanCreationException("");
		}
		return this.beanFactory.getParentBeanFactory().getBean(refName);
	}else {
		Object bean = this.beanFactory.getBean(refName);
		this.beanFactory.registerDependentBean(refName, this.beanName);
		return bean;
	}
    }catch (BeansException ex) {throw new BeanCreationException(this.beanDefinition.getResourceDescription(), this.beanName,
				"Cannot resolve reference to bean '" + ref.getBeanName() + "' while setting " + argName,ex);
    }
}
複製程式碼
Object bean = this.beanFactory.getBean(refName);
複製程式碼
this.beanFactory.getParentBeanFactory().getBean(refName);
複製程式碼

從sprig ioc 容器的雙親中獲取bean(被依賴的bean),假如orderCcontroller依賴orderService,則從容器中獲取orderService。這裡有個關鍵點,也就是這個獲取bean的過程也是一個依賴注入的過程,換句話說依賴注入是個遞迴的過程!!!!!!知道被依賴的bean不依賴任何bean。

  • orderCcontroller 依賴 orderService 的操作會觸發 orderService 依賴 orderDao的操作

2.進行注入操作

這一步是通過Java的反射機制根據set 方法把屬性注入到bean裡。

bw.setPropertyValues(new MutablePropertyValues(deepCopy));
複製程式碼
protected void setPropertyValue(AbstractNestablePropertyAccessor.PropertyTokenHolder tokens, PropertyValue pv) throws BeansException {
    String propertyName = tokens.canonicalName;
    String actualName = tokens.actualName;
    if (tokens.keys != null) {
     // 處理集合型別
    }
    else {
        // 對非集合型別的處理
        AbstractNestablePropertyAccessor.PropertyHandler ph = getLocalPropertyHandler(actualName);
        Object oldValue = null;
        try {
            Object originalValue = pv.getValue();
            Object valueToApply = originalValue;
            if (!Boolean.FALSE.equals(pv.conversionNecessary)) {
                if (pv.isConverted()) {
                    valueToApply = pv.getConvertedValue();
                }else {
                    if (isExtractOldValueForEditor() && ph.isReadable()) {
                        try {
                            oldValue = ph.getValue();
                        }catch (Exception ex) {}
                    }
                    valueToApply = convertForProperty(propertyName, oldValue, originalValue, ph.toTypeDescriptor());
                }
                pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue);
            }
             // 通過反射注入
            ph.setValue(object, valueToApply);
            }
        }
    }
複製程式碼
 public void setValue(final Object object, Object valueToApply) throws Exception {
    final Method writeMethod = this.pd.getWriteMethod();
    if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers()) && !writeMethod.isAccessible()) {
        if (System.getSecurityManager() != null) {
            AccessController.doPrivileged(new PrivilegedAction<Object>() {
                @Override
                public Object run() {
                    writeMethod.setAccessible(true);
                    return null;
                }
            });
        } else {
            writeMethod.setAccessible(true);
        }
    }
    final Object value = valueToApply;
    if (System.getSecurityManager() != null) {
    } else {
        // 通過反射 用set 方法注入屬性
        writeMethod.invoke(getWrappedInstance(), value);
    }
}
複製程式碼

總結

本文主要寫了一下幾點:

1.依賴注入這個步驟是整個Spring ioc 的一部分以及一個功能
2.依賴注入是屬性注入的一種,區別在於這個屬性是由Spring管理的bean
3.觸發依賴注入的時機
4.兩種依賴注入方式 配置檔案以及註解
5.原始碼部分

歡迎大家提出改進點,小弟入行不久功力淺。

參考:

《Spring 技術內幕》 《Spring 原始碼深度剖析》



其他文章

【Spring面試題】Spring 為啥預設把bean設計成單例的?

【Redis面試題】Redis的字串是怎麼實現的?

Spring 原始碼分析之 bean 例項化原理

Spring原始碼分析之 lazy-init 實現原理

相關文章