1. 程式人生 > >Spring Ioc容器依賴注入

Spring Ioc容器依賴注入

1、時序圖

IOC容器的依賴注入是建立在資料BeanDefinition準備好的前提之下的。依賴注入的發生有兩種情況:系統第一次向容器索要bean的時候;bean在配置的時候設定了Lazy-init屬性,該屬性會讓容器完成預例項化,預例項化就是一個依賴注入的過程。Bean的依賴注入看下DefaultListableBeanFactory的基類AbstractBeanFactory的getBean裡面的實現來看下bean依賴注入的全流程。IOC依賴注入的時序圖如下所示:


圖10、spring ioc容器bean注入時序圖

在時序圖中可以看到依賴注入主要有兩個階段:instantiate即bean的例項化;populateBean即對bean所依賴的引用以及屬性進行初始化。

2、instantiate

<pre name="code" class="java">public Object instantiate(RootBeanDefinition beanDefinition, String beanName, BeanFactory owner) {
		// Don't override the class with CGLIB if no overrides.
		if (beanDefinition.getMethodOverrides().isEmpty()) {
			Constructor<?> constructorToUse;
			synchronized (beanDefinition.constructorArgumentLock) {
				constructorToUse = (Constructor<?>) beanDefinition.resolvedConstructorOrFactoryMethod;
				if (constructorToUse == null) {
					final Class clazz = beanDefinition.getBeanClass();
					if (clazz.isInterface()) {
						throw new BeanInstantiationException(clazz, "Specified class is an interface");
					}
					try {
						if (System.getSecurityManager() != null) {
							constructorToUse = AccessController.doPrivileged(new PrivilegedExceptionAction<Constructor>() {
								public Constructor run() throws Exception {
									return clazz.getDeclaredConstructor((Class[]) null);
								}
							});
						}
						else {
							constructorToUse =	clazz.getDeclaredConstructor((Class[]) null);
						}
						beanDefinition.resolvedConstructorOrFactoryMethod = constructorToUse;
					}
					catch (Exception ex) {
						throw new BeanInstantiationException(clazz, "No default constructor found", ex);
					}
				}
			}
			return BeanUtils.instantiateClass(constructorToUse);
		}
		else {
			// Must generate CGLIB subclass.
			return instantiateWithMethodInjection(beanDefinition, beanName, owner);
		}
	}


程式碼層次比較清晰,beanDefinition是從之前載入的BeanDefinition中獲取的根節點資料,一種是使用BeanUtils進行例項化,其實就是反射機制進行例項化,找出類的建構函式,再使用建構函式ctor.newInstance(args)例項化一個物件。另外一種就是使用CGLIB進行例項化,CGLIB構造一個例項的基本實現流程如下:

public Object instantiate(Constructor ctor, Object[] args) {
			Enhancer enhancer = new Enhancer();
			enhancer.setSuperclass(this.beanDefinition.getBeanClass());
			enhancer.setCallbackFilter(new CallbackFilterImpl());
			enhancer.setCallbacks(new Callback[] {
					NoOp.INSTANCE,
					new LookupOverrideMethodInterceptor(),
					new ReplaceOverrideMethodInterceptor()
			});
			return (ctor == null) ? 
					enhancer.create() : 
					enhancer.create(ctor.getParameterTypes(), args);
		}

首先生成一個Enhancer物件,在使用enhancer對基類、回撥方法進行設定,最後使用cglib的create生成一個bean物件。這裡簡單闡述一下反射進行例項化的一個流程。

ClassInstance ci05 = null;
        //額外的思考 在第二種類例項化的方式中有沒有一種方法實現有引數的構造方式
        //獲得類的構造資訊
        Constructor[] ctor = Class.forName("ClassInstance").getDeclaredConstructors();
        //找到我們需要的構造方法
        for(int i=0;i<ctor.length;i++ ){
            Class[] cl = ctor[i].getParameterTypes();
            if(cl.length == 1){
                //例項化物件
                ci05 = (ClassInstance) Class.forName("ClassInstance").getConstructor(cl).newInstance(new Object[]{"05"});
            }
        }
        ci05.fun();

基本流程就是上述。類例項化都是在jvm裡面進行的,所以例項化的步驟是如下的:

1、  類載入到jvm;

2、  Jvm對類進行連結;

3、  Jvm例項化物件。

上述程式碼流程裡面Class.forName("ClassInstance")這個的作用就是前兩步功能;第三步就是ctor. newInstance(args),這樣就是完成了一個類例項化的過程,一般我們在系統裡面new關鍵字就已經將上述三個步驟全部涵蓋了。

在Spring IOC例項化的過程中還有一個地方需要注意的:

protected <T> T doGetBean(
			final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
			throws BeansException {

		final String beanName = transformedBeanName(name);
		Object bean;

		// Eagerly check singleton cache for manually registered singletons.
		Object sharedInstance = getSingleton(beanName);
		if (sharedInstance != null && args == null) {
			if (logger.isDebugEnabled()) {
				if (isSingletonCurrentlyInCreation(beanName)) {
					logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
							"' that is not fully initialized yet - a consequence of a circular reference");
				}
				else {
					logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
				}
			}
			bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
		}

		else {
		....
		}

值得注意的地方就是if (sharedInstance != null && args == null) 這段程式碼的判斷是看這個bean是從beanFactory裡面獲取(false)還是從factoryBean來生產(true)FactoryBean.getObject(),FactoryBean是一個工廠模式。Beanfactory是所有bean的出處,即使FactoryBean也是從Beanfactory來的,那麼FactoryBean是做什麼的呢?我們看下它類的繼承關係就比較容易理解:

圖11、FacotryBean的類關係圖

也就是說FactoryBean其實是各類特定型別beanfactory的工廠,說起來有些拗口,其實就是裡面出來的都是一個factory,例如RMI,JNDI,PROXY等等型別的factory。就可以由其生產。它的設計模式是一個工廠方法。

3、populateBean

populateBean就是bean相關屬性以及依賴類的載入,這裡會觸發相關bean的依賴注入。

在這個過程中也有兩個階段,如下程式碼所示:
List<PropertyValue> deepCopy = new ArrayList<PropertyValue>(original.size());
		boolean resolveNecessary = false;
		for (PropertyValue pv : original) {
			if (pv.isConverted()) {
				deepCopy.add(pv);
			}
			else {
				String propertyName = pv.getName();
				Object originalValue = pv.getValue();
				Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
				Object convertedValue = resolvedValue;
				boolean convertible = bw.isWritableProperty(propertyName) &&
						!PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName);
				if (convertible) {
					convertedValue = convertForProperty(resolvedValue, propertyName, bw, converter);
				}
				// Possibly store converted value in merged bean definition,
				// in order to avoid re-conversion for every created bean instance.
				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();
		}

		// Set our (possibly massaged) deep copy.
		try {
			bw.setPropertyValues(new MutablePropertyValues(deepCopy));
		}
		catch (BeansException ex) {
			throw new BeanCreationException(
					mbd.getResourceDescription(), beanName, "Error setting property values", ex);
		}

第一個階段就是resolveValueIfNecessary,這個就是將改類相關的依賴類找出來,這個有很多型別,list,set,map以及object等等,最終模式都是迴歸到resolveReference上來,這個功能就是最終使用beanFacotry.getBean()來完成bean的依賴例項化。

第二個階段就是setPropertyValues,這個過程就是將bean的set和get方法讀出來,將這些類set進去或是read出來。對於在xml中手動配置的bean ref這個比較容易理解,而對於系統自動依賴注入的方式這裡簡單介紹下:

Spring不但支援自己定義的@Autowired註解,還支援幾個由JSR-250規範定義的註解,它們分別是@Resource、@PostConstruct以及@PreDestroy。

  @Resource的作用相當於@Autowired,只不過@Autowired按byType自動注入,而@Resource預設按 byName自動注入罷了。@Resource有兩個屬性是比較重要的,分是name和type,Spring將@Resource註解的name屬性解析為bean的名字,而type屬性則解析為bean的型別。所以如果使用name屬性,則使用byName的自動注入策略,而使用type屬性時則使用byType自動注入策略。如果既不指定name也不指定type屬性,這時將通過反射機制使用byName自動注入策略。

  @Resource裝配順序

  1. 如果同時指定了name和type,則從Spring上下文中找到唯一匹配的bean進行裝配,找不到則丟擲異常

  2. 如果指定了name,則從上下文中查詢名稱(id)匹配的bean進行裝配,找不到則丟擲異常

  3. 如果指定了type,則從上下文中找到型別匹配的唯一bean進行裝配,找不到或者找到多個,都會丟擲異常

4. 如果既沒有指定name,又沒有指定type,則自動按照byName方式進行裝配;如果沒有匹配,則回退為一個原始型別進行匹配,如果匹配則自動裝配;

@Autowired 與@Resource的區別:

1、 @Autowired與@Resource都可以用來裝配bean. 都可以寫在欄位上,或寫在setter方法上。

2、 @Autowired預設按型別裝配(這個註解是屬業spring的),預設情況下必須要求依賴物件必須存在,如果要允許null值,可以設定它的required屬性為false,如:@Autowired(required=false) ,如果我們想使用名稱裝配可以結合@Qualifier註解進行使用,如下:

@Autowired()

@Qualifier("baseDao")

private BaseDaobaseDao;

3、@Resource(這個註解屬於J2EE的),預設安裝名稱進行裝配,名稱可以通過name屬性進行指定,如果沒有指定name屬性,當註解寫在欄位上時,預設取欄位名進行安裝名稱查詢,如果註解寫在setter方法上預設取屬性名進行裝配。當找不到與名稱匹配的bean時才按照型別進行裝配。但是需要注意的是,如果name屬性一旦指定,就只會按照名稱進行裝配。

@Resource(name="baseDao")

private BaseDao baseDao;

4、IOC依賴注入設計模式

在上面講到FactoryBean採用的是工廠方法,我們在這裡講述下改設計模式:

在FactoryBean的設計中Create就是FactoryBean,而ConcreateCreate就相當於HttpInvokerProxyFactoryBean,FactoryMethod就是getObject(),獲取到的object其實就是一個產品,例如ProxyFactory,而這個proxyFacory就可以生產中各類的Proxy出來,這個ProxyFactory就相當於ConcreateProduct了。

工廠方法的優點:

首先,良好的封裝性,程式碼結構清晰。一個物件建立是有條件約束的,如一個呼叫者需要一個具體的產品物件,只要知道這個產品的類名(或約束字串)就可以了,不用知道建立物件的艱辛過程,減少模組間的耦合。 

其次,工廠方法模式的擴充套件性非常優秀。在增加產品類的情況下,只要適當地修改具體的工廠類或擴充套件一個工廠類,就可以完成“擁抱變化”。

再次,遮蔽產品類。這一特點非常重要,產品類的實現如何變化,呼叫者都不需要關心,它只需要關心產品的介面,只要介面保持不表,系統中的上層模組就不要發生變化,因為產品類的例項化工作是由工廠類負責,一個產品物件具體由哪一個產品生成是由工廠類決定的。在資料庫開發中,大家應該能夠深刻體會到工廠方法模式的好處:如果使用JDBC連線資料庫,資料庫從MySql切換到Oracle,需要改動地方就是切換一下驅動名稱(前提條件是SQL語句是標準語句),其他的都不需要修改,這是工廠方法模式靈活性的一個直接案例。

最後,工廠方法模式是典型的解耦框架。高層模組值需要知道產品的抽象類,其他的實現類都不用關心,符合迪米特原則,我不需要的就不要去交流;也符合依賴倒轉原則,只依賴產品類的抽象;當然也符合里氏替換原則,使用產品子類替換產品父類。