第三章-在Spring中引入IOC和DI
控制反轉(zhuǎn)的概念:
最早自己對于控制反轉(zhuǎn)的理解概括為一句話:IoC的核心以及本質(zhì)其實是DI涌韩,同時IoC只是在倡導(dǎo)提供一種優(yōu)質(zhì)的方案來將bean的控制權(quán)移交給Spring容器干旁,讓開發(fā)人員著重關(guān)注業(yè)務(wù)邏輯。書中的原話精煉:IoC的核心是DI怜跑,旨在提供一種更簡單的機制來設(shè)置組件的依賴項,并在整個生命周期中管理這些依賴項缩多,需要某些依賴項的組件通常被稱為依賴對象,或者在IOC的情況下被稱為目標對象拆又,通常IOC可以分解為兩種子類型:依賴注入和依賴查找,這些子類型被進一步分解為IoC的服務(wù)的具體實現(xiàn)温兼,對比兩個理解可以發(fā)現(xiàn)我原來理解的IoC比較片面秸滴,同時暴露出了自己的知識碎片,所以在這里整合一下期望能體系化的理解IoC募判;
依賴注入荡含,通常的表現(xiàn)為構(gòu)造函數(shù)注入和setter注入,這也是推薦的一種IoC方式届垫;
依賴查找释液,在文中作者對其進行再次分類分別為依賴拉取(DL)和上下文依賴查找(CDL),關(guān)于DL,適用于早起的JNDI以及在EJB2.1的版本中非常流行装处,DL是通過從注冊表中提取依賴項的方式來進行進行DI的(即通過JNDI API來查找EJB組件)误债,CDL在某些方面與DL類似,CDL的查找是針對管理資源的容器執(zhí)行的,在Spring中一個典型的依賴查找的場景就是通過Sping的ApplicationContext接口來getBean妄迁。
注入和查找寝蹈,選擇使用哪種類型的IoC通常是由容器決定的,例如在EJB2.1或更早的版本中登淘,則必須使用查找式的IoC通過JNDI從JEE容器中獲取EJB箫老,Spring中,除了初始bean的查找黔州,組件及其依賴項始終是使用注入式的IoC連接在一起的耍鬓,所以目前常用以及推薦的IoC方式一定是注入的方式阔籽。
tips:IoC的核心其實是DI,IoC提供了一種簡便的方式來對依賴對象的整個生命周期做管理牲蜀,關(guān)注DI其實著重關(guān)注的點是依賴項的管理仿耽,所以演化出兩種IoC的方式依賴注入和依賴查找,在原來的理解中沒有意識到依賴管理這么一說,這里糾正一下
-
Spring中的控制反轉(zhuǎn):
控制反轉(zhuǎn)一直以來都是Spring的重要組成部分各薇,Spring的核心就是Di在Spring中除了Spring初始化的時候后一些必要的基礎(chǔ)組件需要通過依賴查找的方式來進行對象管理的,其他任何對象包含其依賴項都是通過依賴注入的方式來進行對象管理的君躺。
Spring支持構(gòu)造函數(shù)注入和setter注入兩種基礎(chǔ)注入方式峭判,并且支持標準的IoC功能集,同時提供了大量的附加功能來簡化我們的開發(fā)(其實還有提到spEL表達式注入棕叫,屬性注入)林螃。
-
Spring中的依賴注入:
BeanFactory And FactoryBean ?
之前一直不太理解為什么Spring中會同時存在BeanFactory和FactoryBean這兩個接口,不理解的地方就在于現(xiàn)有的水平下想不到當時設(shè)計這兩個接口的人是基于什么樣的場景和什么樣的問題來進行設(shè)計的俺泣,所以來稍微探索求證一下:
首先是FactoryBean疗认,按照命名規(guī)范來看這個接口應(yīng)該標識了一類bean,那么我們先看看源碼(此處源碼版本為Spring5.x):
//Spring 3.0開始支持泛型
public interface FactoryBean<T> {
@Nullable
T getObject() throws Exception;
@Nullable
Class<?> getObjectType();
default boolean isSingleton() {
return true;
}
}
可以看到該接口非常簡單伏钠,僅僅只有三個方法横漏,先簡單介紹一下:
- T getObject() 該方法需要返回一個T類型實例,并且我們通常在使用BeanFactory來進行g(shù)etBean時熟掂,如果你get的bean類型是一個實現(xiàn)了factoryBean的類缎浇,那么Spring將會調(diào)用該類的getObject方法。
- Class<?> getObjectType() 該方法返回一個getObject中實例的類類型赴肚。
-
boolean isSingleton() 該方法用來告知Spring通過factoryBean創(chuàng)建的實例作用域是Singleton還是prototype素跺,也就是告訴容器該實例是否為單例bean,如果返回為true誉券,那么該實例將被放到Spring的單實例緩存池中指厌,否則在getObject時將新建一個實例。
趁熱打鐵踊跟,我們再找一個實現(xiàn)了FactoryBean的子類來看看具體的應(yīng)用踩验,這里參考mybatis-spring:3.1 中的SqlSessionFactoryBean,由于該類涉及到了mybatis的源碼琴锭,在這里我們只關(guān)注如上三個方法的具體實現(xiàn)即可晰甚,源碼片段如下:
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
//.........此處省略一萬字
@Override
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
"Property 'configuration' and 'configLocation' can not specified with together");
this.sqlSessionFactory = buildSqlSessionFactory();
}
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
@Override
public Class<? extends SqlSessionFactory> getObjectType() {
return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass();
}
@Override
public boolean isSingleton() {
return true;
}
}
從上面我們可以看到在mybatis 的SqlSessionFactoryBean中,mybatis在getObject方法里面進行了sqlSessionFactory依賴的初始化决帖,在getObjectType中進行了默認類型的返回厕九,總結(jié)為一句話就是,F(xiàn)actoryBean類型的bean在Spring初始化時顯得更加靈活地回,可以進行更多的自定義配置以及需求扁远,這有效的減少了復(fù)雜bean的實例化代價俊鱼,如果沒有FactoryBean,那么我們在某些場景下將面臨及其繁瑣的依賴配置畅买。
我們再回過頭來看看BeanFactory并闲,源碼如下:
public interface BeanFactory {
String FACTORY_BEAN_PREFIX = "&";
Object getBean(String name) throws BeansException;
<T> T getBean(String name, @Nullable Class<T> requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
boolean containsBean(String name);
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, @Nullable Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
@Nullable
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
String[] getAliases(String name);
}
同樣,我們先從規(guī)范的名字入手谷羞,BeanFactory以Factory結(jié)尾應(yīng)該是一個工廠類帝火,我們從官方提供的Api中找到下面一段描述:
The root interface for accessing a Spring bean container. This is the basic client view of a bean container; further interfaces such as ListableBeanFactory and ConfigurableBeanFactory are available for specific purposes.
大概意思就說BeanFactory是Spring容器的一個基礎(chǔ)組件,進一步的實現(xiàn)例如ListableBeanFactory湃缎,ConfigurableBeanFactory可以對BeanFactory做進一步的拓展犀填,總結(jié)就是BeanFactory是IoC的一個核心接口,它提供基礎(chǔ)的bean管理以及創(chuàng)建功能嗓违。
引用書中的原話為:Spring的依賴注入容器的核心是**BeanFactory**接口九巡,BeanFactory負載管理組件,包括依賴項以及它們的生命周期并可以在BeanFactory中識別自己的bean蹂季,為每個bean分配一個ID冕广、一個名稱或者兩者兼具,一個bean也可以在沒有任何ID或名稱(匿名bean)的情況下被實例化偿洁,或者作為另一個bean的內(nèi)部bean被實例化撒汉,每個bean至少有一個名稱,多個名稱用逗號隔開父能,第一個名稱后面的名稱都被認為是同一個bean的別名神凑,可以使用BeanFactory用beanID去檢索一個bean,并建立依賴關(guān)系
簡單描述一下BeanFactory的方法(部分方法重載太多何吝,忽略參數(shù)):
- T getBean() 根據(jù)bean的名稱類型等獲取bean實例溉委。
- boolean containsBean(String name) 根據(jù)bean的名稱去判斷工廠中是否存在相同的bean實例,存在則返回true
- boolean isSingleton(String name) 該方法和FactoryBean功能不太相同爱榕,該方法返回的是getBean中返回bean的作用域信息瓣喊,是否是單例的bean,如果是則返回true
- boolean isPrototype(String name) 該方法和isSingleton類似黔酥,返回getBean方法返回bean的作用域信息藻三,返回作用域是否是prototype的bean,如果是則返回true
- boolean isTypeMatch() 該方法用來判斷指定name的bean是否滿足特定的類型跪者,如果滿足則返回true
- Class<?> getType(String name) 該方法用來獲取指定name的bean的類型
- String[] getAliases(String name) 該方法用來獲取指定name的bean在實例池中的所有別名
關(guān)于BeanFactory的默認實現(xiàn)我們可以先看一下Spring源碼內(nèi)的ListableBeanFactory接口源碼:
public interface ListableBeanFactory extends BeanFactory {
boolean containsBeanDefinition(String beanName);
int getBeanDefinitionCount();
String[] getBeanDefinitionNames();
String[] getBeanNamesForType(ResolvableType type);
String[] getBeanNamesForType(@Nullable Class<?> type);
String[] getBeanNamesForType(@Nullable Class<?> type, boolean includeNonSingletons, boolean allowEagerInit);
<T> Map<String, T> getBeansOfType(@Nullable Class<T> type) throws BeansException;
<T> Map<String, T> getBeansOfType(@Nullable Class<T> type, boolean includeNonSingletons, boolean allowEagerInit)
throws BeansException;
String[] getBeanNamesForAnnotation(Class<? extends Annotation> annotationType);
Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType) throws BeansException;
@Nullable
<A extends Annotation> A findAnnotationOnBean(String beanName, Class<A> annotationType)
throws NoSuchBeanDefinitionException;
}
可以看到ListableBeanFactory接口繼承了BeanFactory棵帽,同時ListableBeanFactory對BeanFactory進行了拓展,也就是說相對于FactoryBean來說BeanFactory更像一個標準化的IoC實現(xiàn)渣玲,而FactoryBean更像是對bean做了一層裝飾和代理逗概,通過FactoryBean可以在bean初始化的時候進行一些自定義的配置以及開發(fā),更加的靈活忘衍。
用現(xiàn)在比較流行的話來說逾苫,BeanFactory提供了操作管理bean的能力卿城,而BeanFactory則為bean提供了裝飾以及增強的能力。
ApplicationContext嵌套
Spring支持ApplicationConext的層次結(jié)構(gòu)铅搓,因此一個上下文(以及相關(guān)的BeanFactory)被認為是另一個上下文的父級瑟押,通過ApplicationContexts嵌套,能夠?qū)⑴渲梅指畛刹煌奈募ㄊ遣皇怯蟹N似曾相識的感覺)星掰,在嵌套ApplicationContext實例時多望,Spring允許子上下文中的bean引用父上下文中的bean,下面是用GenericXmlApplicationContext實現(xiàn)的一個簡單的嵌套方法,(GenericXmlApplicationContext類實現(xiàn)了ApplicationContext接口氢烘,并提供了可以通過XML文件中定義的配置去啟動Spring的ApplicationContext的能力)便斥,核心是子ApplicationContext的setParent()方法:
public static void main(String[] args) {
GenericXmlApplicationContext parent = new GenericXmlApplicationContext();
parent.load("classpath:spring/applicationContext.xml");
parent.refresh();
GenericXmlApplicationContext child = new GenericXmlApplicationContext();
child.load("classpath:spring/applicationContext-dao.xml");
child.setParent(parent);
child.refresh();
child.close();
parent.close();
}
上面模擬了一個在項目中經(jīng)常會遇到的情況,通常會有一個基礎(chǔ)的applicationContext.xml文件然后更細粒度的可能會劃分出applicationContext-dao.xml 威始,applicationContext-service.xml等等配置文件,其實Spring也是通過ApplicationContext嵌套來處理的像街。
tips:看完Spring關(guān)于ApplicationContext嵌套的處理方式黎棠,是不是突然回答了為什么有的Spring bean配置只需要挪一下位置就可以生效,換個位置就會報錯的問題了镰绎?
實例bean作用域(Spring5.x)
- 單例作用域:默認為單例作用域脓斩,每個Spring IoC容器只會創(chuàng)建一個對象。(其實所謂的Spring的單實例緩存池本質(zhì)上是一個Map畴栖,具體分析后面講DefaultListableBeanFactory的時候再回頭看)
- 原型作用域:當應(yīng)用程序請求時随静,Spring將創(chuàng)建一個新的實例
- 請求作用域:用于Web應(yīng)用程序,當為Web應(yīng)用程序使用SpringMVC時吗讶,首先針對每個HTTP請求實例化帶有請求作用域的bean燎猛,然后在請求完成時銷毀
- 會話作用域:用于Web應(yīng)用程序,當為Web應(yīng)用程序使用SpringMVC時照皆,首先針對每個HTTP會話實例化帶有會話作用域的bean重绷,然后在會話結(jié)束時銷毀
- 全局會話作用域:用于基于Portlet的Web應(yīng)用程序,帶有全局會話作用域的bean可以在同一個SpringMVC驅(qū)動的門戶應(yīng)用程序的所有Porlet之間共享
- 線程作用域:當一個新線程請求bean實例時膜毁,Spring將創(chuàng)建一個新的bean實例昭卓,而對于同一個線程,返回相同的bean實例瘟滨,線程作用域默認情況下未注冊(此處應(yīng)該大量使用到了ThreadLocal 后面求證)
- 自定義作用域:可以通過實現(xiàn)org.springframework.beans.factory.config.Scope接口創(chuàng)建自定義作用域候醒,并在Spring配置中注冊自定義作用域(如果是XML配置,可以使用org.springframework.bean.factory.config.CustomScopeConfigurer類)