spring基礎(二)---IoC簡介
是什麼
IOC全稱: Inverse of Control, 控制反轉,所以重點就是,什麼是控制,怎樣反轉
* 誰控制誰,控制了什麼
舉個例子,我們先來看傳統的程式設計
client類中程式碼:
public static void main(String[] args) {
UserManager userManager=new UserManagerImpl();//依賴寫死在這裡
userManager.addUser("張颯","123");
}
UserManager
public interface UserManager {
public void addUser(String username,String password);
}
UserManagerImpl
public class UserManagerImpl implements UserManager{
@Override
public void addUser(String username,String password){
//由我們的應用程式負責服務定位
UserDao userDao=new UserDaoMySqlImpl();
userDao.addUser(username,password);
}
}
很明顯,UserDao介面有很多不同的實現,有mysql的,也有oracle的.如果我們想讀取不同資料來源的資料,那麼就得有不同的UserManagerImpl類,而UserManagerImpl,即依賴於介面UserDao,有同時依賴實現,所以它需要在編譯階段,就確定使用哪種方式,這樣就缺少靈活性了.而且程式碼量也增多.如果把現在呼叫哪種型別的userDao,交給client決定呢?
上面程式碼也寫了,是通過應用程式直接負責服務的定位的,就是去找我想要的物件.這個是正轉, 反轉是什麼, 是容器來幫忙建立及注入依賴物件.容器找到之後,就把你要的物件,給你,所以物件就事被動的接收依賴物件,就是所謂的反轉了.
上面是不是有點像: 是自己直接找房子, 還是通過中介找房子?
現在我們把上面的程式碼改造,改造成spring管理的專案
這段程式碼很熟悉吧.
public static void main(String[] args) {
/*IOC容器中拿介面*/
BeanFactory factory=new ClassPathXmlApplicationContext("applicationContext.xml");
UserManager userManager=(UserManager)factory.getBean("userManager");
userManager.addUser("張颯","123");
}
來分析一下它.首先是applicationContext.xml
java自帶了多種xml名稱空間,通過這些名稱空間可以配置Spring容器
beans: 支援宣告Bean和裝配Bean,是spring最核心也是最原始的名稱空間
元素是spring最基本的配置單元,通過該元素spring將建立一個物件,這裡建立了一個由spring容器管理的名字叫userDaoMySql的bean.id屬性定義了Bean的名字,也作為該Bean在Spring容器中的引用.
當Spring容器載入該Bean時,Spring將使用預設的構造器來例項化userDaoMySql Bean;
<bean id="userDaoMySql" class="com.tgb.kwy.dao.UserDaoMySqlImpl"/>
<bean id="userDaoOracle" class="com.tgb.kwy.dao.UserDaoOracleImpl"/>
<bean id="userManager" class="com.tgb.kwy.manager.UserManagerImpl">
<!--<constructor-arg ref="userDaoMySql"/><!–建構函式方式–>-->
<!--通常javaBean是私有的,同時擁有一組存取器方法,以setXXX()和getXXX()形式存在.-->
<property name="userDao" ref="userDaoMySql"></property>
</bean>
上面看到了一個註釋, Spring中,我們可以使用元素配置bean的屬性,在許多方面都與類似,只不過一個是通過構造器引數來注入值,另一個是通過呼叫屬性的setter方法來注入值
一旦UserManagerImpl被例項化,Spring就會呼叫元素所指定屬性的setter方法為該屬性注入值.
UserManagerImpl
public class UserManagerImpl implements UserManager {
private UserDao userDao;
/*建構函式方式*/
/*public UserManagerImpl(UserDao userDao) {
this.userDao = userDao;
}
*/
/*set方式*/
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void addUser(String username,String password){
//由我們的應用程式負責服務定位
// UserDao userDao=new UserDaoMySqlImpl();
userDao.addUser(username,password);
}
}
在最上面的配置檔案applicationContext.xml中,我們可以看到,如果想變換userDao的實現類,那麼就我就改userManager中的ref=”userDaoOracle”即可.
所以控制反轉: 物件的建立權,銷燬都交給Spring來完成.
依賴注入:Spring主動建立被呼叫類的物件,然後把這個物件注入到我們自己的類中,使我們能使用它.依賴注入是通過反射實現注入的
附:上面的例子,是把配置全寫在xml裡面了,但是在實際專案中,更多的是掃描包,用註解的方式注入bean的實現.當需要修改的時候, 就把bean實現路徑改了就成.
關於IOC是什麼,我覺得這篇部落格寫的很好:
http://jinnianshilongnian.iteye.com/blog/1413846?page=2
原始碼分析
下面來了解一下Spring在這個過程中,是怎麼做的?(進入原始碼閱讀模式)
SpringIoC兩個主要的容器系列,一個是BeanFactory介面的簡單容器,實現了基本的功能.另一個就是ApplicationContext應用上下文,是容器的高階形態存在
BeanFactory是一個介面類,
DefaultListTableBeanFactory.XmlBeanFactory.ApplicationContext等都是具體的實現
public interface BeanFactory {
//工廠Bean的轉義定義,因為如果使用bean的名字檢索IOC容器得到的物件是工廠Bean生成的物件,
//如果需要得到工廠Bean本身,需要使用轉義的名字來向IOC容器檢索
String FACTORY_BEAN_PREFIX = "&";
//根據bean的名字,在IOC容器中得到bean例項,這個IOC容器就象一個大的抽象工廠,使用者可以根據名字得到需要的bean
//在Spring中,Bean和普通的JAVA物件不同在於:
//Bean已經包含了我們在Bean定義資訊中的依賴關係的處理,同時Bean是已經被放到IOC容器中進行管理了,有它自己的生命週期
Object getBean(String name) throws BeansException;
//根據bean的名字和Class型別來得到bean例項,增加了型別安全驗證機制
Object getBean(String name, Class requiredType) throws BeansException;
//提供對bean的檢索,看看是否在IOC容器有這個名字的bean
boolean containsBean(String name);
//根據bean名字得到bean例項,並同時判斷這個bean是不是單例,在配置的時候,預設的Bean被配置成單例形式,如果不需要單例形式,需要使用者在Bean定義資訊中標註出來,這樣IOC容器在每次接受到使用者的getBean要求的時候,會生成一個新的Bean返回給客戶使用 - 這就是Prototype形式
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
//得到bean例項的Class型別
Class getType(String name) throws NoSuchBeanDefinitionException;
//得到bean的別名,如果根據別名檢索,那麼其原名也會被檢索出來
String[] getAliases(String name);
}
我們用的最多的就是getBean方法了. 在BeanFactory中,只對IOC容器的基本行為做了定義,具體的實現,工廠怎麼生產物件的,我們得看具體的實現類.如果讀取xml,可以使用XmlBeanFactory.使用方式:
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource res = resolver.getResource("classpath:applicationContext.xml");
BeanFactory bf = new XmlBeanFactory(res);
xmlBeanFactory先通過Resource裝載了Spring配置資訊,然後啟動IOC容器,就能通過getBean方法從IOC容器獲取Bean了.但是通過BeanFactory啟動IOC容器時,不會初始化配置檔案中定義的Bean,而是在首次呼叫時
不過上面我們也提到了ApplicationContext.我們在上面的例子可以看到. 讀取xml的時候, 採用的ClassPathXmlApplicationContext,而這個類就是繼承了ApplicationContext
ClassPathXmlApplicationContext:預設從類路徑載入配置檔案
FileSystemXmlApplicationContext:預設從檔案系統中裝載配置檔案
如果配置檔案放置在類路徑下,使用者可以優先使用 ClassPathXmlApplicationContext 實現類:就像我們上面的例子:
BeanFactory factory=new ClassPathXmlApplicationContext("applicationContext.xml");
如果配置檔案放置在檔案系統的路徑下,則可以優先考慮使用 FileSystemXmlApplicationContext 實現類;
小結
- BeanFactory是Spring框架的基礎設施,面向Spring本身
- ApplicationContext面向使用Spring框架的開發者,幾乎所有的應用場合我們都直接使用ApplicationContext,而非底層的BeanFatory
- BeanFactory在初始化容器時,並未例項化Bean,直到第一次訪問某個Bean時才例項目標Bean
- 而ApplicationContext則在初始化應用上下文時就例項化所有單例項的Bean。
IoC初始化過程
Resource定位
- 在看原始碼的時候,我們可以看到ClassPathXmlApplicationContext通過繼承AbstractApplicationContext,具備了ResourceLoader讀入以Resource定義的BeanDefinition的能力,因為AbstractApplicationContext繼承了DefaultResourceLoader;
- resourceLoader從儲存介質中載入Spring配置資訊,並使用Resource表示這個配置檔案的資源.
- 常見的resource資源型別如下:
FileSystemResource:以檔案的絕對路徑方式進行訪問資源,效果類似於Java中的File;
ClassPathResourcee:以類路徑的方式訪問資源,效果類似於this.getClass().getResource(“/”).getPath();
ServletContextResource:web應用根目錄的方式訪問資源,效果類似於request.getServletContext().getRealPath(“”);
UrlResource:訪問網路資源的實現類。例如file: http: ftp:等字首的資源物件;
ByteArrayResource: 訪問位元組陣列資源的實現類。
定位好的資源載入BeanDefinition
- IOC容器對於bean的管理和依賴注入功能實現,是通過對其持有的BeanDefinition進行各種相關操作來完成的.
- BeanDefinitionReader讀取Resource所指向的配置檔案資源,然後解析這個配置檔案,配置檔案中每一個就被解析成一個BeanDefinition物件(至於怎麼解析的, 我感覺自己理解的不太好, 就不寫了)
- 在這個時刻,依賴注入還沒發生呢
將BeanDefinition註冊到容器
- 上面提到了,獲取所有的BeanDefinition, 那麼這些BeanDefinition就存在BeanDefinitionRegistry中, 它通過hashMap來儲存所有的BeanDefinition.key就是bean的名字.所以在配置檔案中bean id 是不允許重複的.
- 完成了註冊,就完成IOC容器的初始化過程了.這些BeanDefinition就能被容器使用了.容器作用就是對這些資訊進行處理和維護.
依賴注入
上面我們有提到初始化的時候, 依賴注入還沒發生. 真正發生的時刻,是使用者第一次向IOC容器要Bean時. 這個是預設的配置,但是如果你在配置檔案中通過lazy-init屬性,讓容器完成對Bean的例項化,就是相當於在初始化過程中完成的了.
- 依賴注入的入口,就是getBean()方法, 呼叫者通過getBean(beanName)向容器請求一個Bean時,容器將根據配置情況呼叫InstantiationStrategy進行bean例項化,使用BeanWrapper完成bean屬性設定工作
- 簡單理解:通過反射,例項化一個類時,通過反射呼叫set方法把hashmap中類屬性注入到類中
IoC裝配bean
* 關於bean作用域,這裡多說一下: spring關於單例,僅限於Spring上下文的範圍內,不像真正的單例, 在每個類載入器中保證只有一個例項. Spring的單例Bean只能保證在每個應用上下文中只有一個Bean的例項.還是可以通過傳統的方式例項化同一個Bean. 甚至可以定義幾個宣告來例項化同一個bean
* 關於bean裝配, spring容器預設是禁用註解裝配的.所以在使用基於註解的自動裝配前,我們需要在spring配置中啟用它.最簡單的啟用方式: 這個元素告訴Spring我們打算使用基於註解的自動裝配, 但是這種方式,還是需要我們使用元素顯示定義Bean. 另一種更常用的是. 這個除了完成一樣的工作.還執行spring自動檢測Bean和定義Bean. 不再使用元素,Spring應用中的大多數Bean都能夠實現定義和裝配
* 怎麼知道哪些類需要註冊為Spring Bean?
* 就是上圖中元件註解的幾種方式了.Spring掃描到標註了註解的包時,會自動註冊為SpringBean
bean生命週期
- Spring對Bean進行例項化
- Spring將值和Bean的引用注入進Bean對應的屬性中
- 如果Bean實現了BeanNameAware介面,Spring將Bean的ID傳遞給set-BeanName()介面方法
- 如果Bean實現了BeanFactoryAware介面,Spring將呼叫setBeanFactory()介面方法,將BeanFactory容器例項傳入
- 如果Bean實現了ApplicationContextAware介面,Spring將呼叫setApplicationContext()介面方法,將應用上下文的引用傳入
- 如果Bean實現了BeanPostProcessor介面,Spring將呼叫他們的post-ProcessBeforeInitialization()介面方法
- 如果Bean實現了InitaialzingBean介面,Spring將呼叫它們的after-PropertiesSet()介面方法.類似地,如果Bean使用intitle-method聲明瞭初始化方法,該方法也會被呼叫
- 如果Bean實現了BeanPostProcessor介面,Spring將呼叫他們的post-ProcessAfterInitialization()方法
- 此時此刻,bean已經準備就緒,可以被應用程式使用了.它們將一直駐留在應用上下文中,直到該應用上下文被銷燬
- 如果Bean實現了DisposableBean介面,Spring將呼叫它的destroy()介面方法,同樣,如果Bean使用destroy-method聲明瞭銷燬方法,該介面也會被呼叫.
參考: 眾多部落格,spring技術內幕書