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語句是標準語句),其他的都不需要修改,這是工廠方法模式靈活性的一個直接案例。
最後,工廠方法模式是典型的解耦框架。高層模組值需要知道產品的抽象類,其他的實現類都不用關心,符合迪米特原則,我不需要的就不要去交流;也符合依賴倒轉原則,只依賴產品類的抽象;當然也符合里氏替換原則,使用產品子類替換產品父類。