IoC-spring 的灵魂(带你轻松理解IOC思想及bean对象的生成过程)
在理解任何技術之前,我都會問自己一個問題:它的產生是為了解決什么樣的問題,以及如何解決這些問題?希望你能在本篇文章中找到答案……
(由于大家對Ioc應該是經常使用了,所以這里不會告訴你應該怎么樣使用,重要的是理解思想原理,理解過程)
一、IOC的概念
IoC可以說是spring最核心的部分,是spring家族任意組件的基本。Ioc 本身并不能算為一種技術,而是一種思想,它使你從繁瑣的對象交互中解脫出來,而專注于對象本身,更進一步突出面向對象。
我們先來回答文章開頭問題的上半部分:
我們假設一個場景:Person(人)每天都要吃早餐(食物)。我們可以用如下程序表示
在我們吃飯之前必須先new food()(做飯),要不然就吃不上。
Ioc 會怎么樣做呢
它會在你吃的時候將食物準備好,不需要你自己做飯。因為它認為:吃飯的人不應該身兼廚師的角色。
借用《spring 揭秘》中的漫畫再說明一下吧(因為我不會畫吃飯的漫畫)。它的意思是:穿衣服出門。如果不使用Ioc,你就得自己去取衣服穿上。用了IOC,已經有美女給你拿過來并幫你穿上(有沒有一種大款的感覺)。IOC就是讓你當大款,你只需要發揮自己的特長掙錢就可以了,其它的讓小秘來。
其實上面就是IOC的核心思想,也就是它要解決的問題:讓你脫離對依賴對象的維護,只需要隨用隨取,不需要關心依賴對象的任何過程。(是不是感覺特別簡單)
二、IOC的技術實現方式
接下來的問題是如何將依賴的對象準備好呢(依賴注入),常用的有兩種方式:構造方法注入和setter注入(雖然大家都很熟悉了,但還請原諒我再說一下)
構造器注入,它就代表了當Person這個對象生成時,就準備好了:即無論你吃不吃飯,飯就在那里,不離不棄
setter注入,有所不同:俺不是那么隨便的食物,你得喊我(set)俺才過來,有種悶騷的感覺。反正我就喜歡這種……
public void setFood(Food food) {this.food = food; }復制代碼但無論前提哪一種注入方法,你總得有小秘來執行吧!!!so,你只需要默默地躺在那來享受,小秘帶來百般絕技!!!
三、IOC容器
小秘絕技雖然精彩,但要實現卻并不那么容易。它需要一系列技術要實現。首先它需要知道服務的對象是誰,以及需要為服務對象提供什么樣的服務。提供的服務指:要完成對象的構建(即把飯做好),將其送到服務對象即完成對象的綁定(即把飯端到我面前)。
上面的話別看糊涂了,再聲明一下,Ioc需要實現兩個技術:
- 對象的構建
- 對象的綁定
對于這兩個方面技術的實現具有很多的方式:硬編碼(Ioc 框架都支持),配置文件(我們的重點),注解(最潔的方式)。但無論哪種方式都是在Ioc容器里面實現的(我們可以理解為一個大池子,里面躺著各種各樣的對象,并能通過一定的方式將它們聯系起來)
spring提供了兩種類型的容器,一個是BeanFactory,一個是ApplicationContext(可以認為是BeanFactory的擴展),下面我們將介紹這兩種容器如何實現對對象的管理。
3.1 BeanFactory
如果沒有特殊指定,默認采用延
遲初始化策略(lazy-load)。只有當客戶端對象需要訪問容器中的某個受管對象的時候,才對 該受管對象進行初始化以及依賴注入操作。所以,相對來說,容器啟動初期速度較快,所需 要的資源有限。對于資源有限,并且功能要求不是很嚴格的場景,BeanFactory是比較合適的 IoC容器選擇。
我們先來看一下BeanFactory類的關系圖(如下所示)
有三個很重要的部分:
- BeanDefinition 實現Bean的定義(即對象的定義),且完成了對依賴的定義
- BeanDefinitionRegistry ,將定義好的bean,注冊到容器中(此時會生成一個注冊碼)
- BeanFactory 是一個bean工廠類,從中可以取到任意定義過的bean
最重要的部分就是BeanDefinition,它完成了Bean的生成過程。一般情況下我們都是通過配置文件(xml,properties)的方式對bean進行配置,每種文件都需要實現BeanDefinitionReader,因此是reader本身現了配置文字 到bean對象的轉換過程。當然我們自己也可以實現任意格式的配置文件,只需要自己來實現reader即可。
Bean的生成大致可以分為兩個階段:容器啟動階段和bean實例化階段
容器啟動階段: - 加載配置文件(通常是xml文件)
- 通過reader生成beandefinition
- beanDefinition注冊到beanDefinitionRegistry
bean實例化階段:
當某個bean 被 getBean()調用時
bean需要完成初時化,以及其依賴對象的初始化
如果bean本身有回調,還需要調用其相應的回調函數
從 上面我們也可以知道,beanDefinition(容器啟動階段)只完成bean的定義,并未完成初始化。初始是通過beanFactory的getBean()時才進行的。
Spring Ioc在初始化完成之后,給了我們提供一些方法,讓我們來改變一些bean的定義
org.springframework.beans.factory.config.PropertyPlaceholderConfigurer:使我們可能通過配置文件的形式,配置一些參數
PropertyOverrideConfigurer :則可以覆蓋原本的bean參數
CustomEditorConfigurer :則提供類型轉換支持(配置文件都是string,它需要知道轉換成何種類型)
Bean的初始化過程:
如果你認為實例化的對象就是通過我們定義的類new 出來的,那就大錯特錯了,其實這里用到了AOP機制,生成了其代理對象(通過反射機制生成接口對象,或者是通過CGLIB生成子對象)
bean的具體裝載過程是由beanWrapper實現的,它繼承了PropertyAccessor (可以對屬性進行訪問)、PropertyEditorRegistry 和TypeConverter接口 (實現類型轉換,就上前面說的)。
完成設置對象屬性之后,則會檢查是否實現了Aware類型的接口,如果實現了,則主動加載
BeanPostprocessor 可以幫助完成在初始化bean之前或之后 幫我們完成一些必要工作,比如我們在連接數據庫之前將密碼存放在一個加密文件,當我們連接數據庫之前,需要將密碼進行加載解密。只要實現 相應的接口即可
public interface BeanPostProcessor {/*** Apply this BeanPostProcessor to the given new bean instance <i>before</i> any bean* initialization callbacks (like InitializingBean's {@code afterPropertiesSet}* or a custom init-method). The bean will already be populated with property values.* The returned bean instance may be a wrapper around the original.* @param bean the new bean instance* @param beanName the name of the bean* @return the bean instance to use, either the original or a wrapped one; if* {@code null}, no subsequent BeanPostProcessors will be invoked* @throws org.springframework.beans.BeansException in case of errors* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet*/Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;/*** Apply this BeanPostProcessor to the given new bean instance <i>after</i> any bean* initialization callbacks (like InitializingBean's {@code afterPropertiesSet}* or a custom init-method). The bean will already be populated with property values.* The returned bean instance may be a wrapper around the original.* <p>In case of a FactoryBean, this callback will be invoked for both the FactoryBean* instance and the objects created by the FactoryBean (as of Spring 2.0). The* post-processor can decide whether to apply to either the FactoryBean or created* objects or both through corresponding {@code bean instanceof FactoryBean} checks.* <p>This callback will also be invoked after a short-circuiting triggered by a* {@link InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation} method,* in contrast to all other BeanPostProcessor callbacks.* @param bean the new bean instance* @param beanName the name of the bean* @return the bean instance to use, either the original or a wrapped one; if* {@code null}, no subsequent BeanPostProcessors will be invoked* @throws org.springframework.beans.BeansException in case of errors* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet* @see org.springframework.beans.factory.FactoryBean*/Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;}復制代碼在完成postProcessor之后,則會看對象是否定義了InitializingBean 接口,如果是,則會調用其afterProper- tiesSet()方法進一步調整對象實例的狀態 ,這種方式并不常見。spring還提供了另外一種指定初始化的方式,即在bean定義中指定init-method 。
當這一切完成之后,還可以指定對象銷毀 的一些回調,比如數據庫的連接池的配置,則銷毀前需要關閉連接等。相應的可以實現DisposableBean 接口或指定destroy-method
3.2 ApplicationContext
ApplicationContext 容器建立BeanFactory之上,擁有BeanFactory的所有功能,但在實現上會有所差別。我認為差別主要體現在兩個方面:1.bean的生成方式;2.擴展了BeanFactory的功能,提供了更多企業級功能的支持。
1.bean的加載方式
BeanFactory提供BeanReader來從配置文件中讀取bean配置。相應的ApplicationContext也提供幾個讀取配置文件的方式:
- FileSystemXmlApplicationContext:該容器從 XML 文件中加載已被定義的 bean。在這里,你需要提供給構造器 XML 文件的完整路徑
- ClassPathXmlApplicationContext:該容器從 XML 文件中加載已被定義的 bean。在這里,你不需要提供 XML 文件的完整路徑,只需正確配置 CLASSPATH 環境變量即可,因為,容器會從 CLASSPATH 中搜索 bean 配置文件。
- WebXmlApplicationContext:該容器會在一個 web 應用程序的范圍內加載在 XML 文件中已被定義的 bean。
- AnnotationConfigApplicationContext
- ConfigurableWebApplicationContext
另外一個比較重要的是,ApplicationContext采用的非懶加載方式。它會在啟動階段完成所有的初始化,并不會等到getBean()才執行。所以,相對于BeanFactory來 說,ApplicationContext要求更多的系統資源,同時,因為在啟動時就完成所有初始化,容 器啟動時間較之BeanFactory也會長一些。在那些系統資源充足,并且要求更多功能的場景中, ApplicationContext類型的容器是比較合適的選擇。
ApplicationContext 還額外增加了三個歷能:ApplicationEventPublisher,ResourceLoader,MessageResource
ResourceLoader
ResourceLoader并不能將其看成是Spring獨有的功能,spring Ioc只是借助于ResourceLoader來實現資源加載。也提供了各種各樣的資源加載方式:
- DefaultResourceLoader 首先檢查資源路徑是否以classpath:前綴打頭,如果是,則嘗試構造ClassPathResource類 型資源并返回。否則, 嘗試通過URL,根據資源路徑來定位資源
- FileSystemResourceLoader 它繼承自Default-ResourceLoader,但覆寫了getResourceByPath(String)方法,使之從文件系統加載資源并以 FileSystemResource類型返回
- ResourcePatternResolver 批量查找的ResourceLoader
spring與ResourceLoader之間的關系
所有ApplicationContext的具體實現類都會直接或者間接地實現AbstractApplicationContext,AbstactApplicationContext 依賴了了DeffaultResourceLoader, ApplicationContext 繼承了ResourcePatternResolver,所到頭來ApplicationContext的具體實現類都會具有DefaultResourceLoader 和 PathMatchingResourcePatterResolver的功能。這也就是會什么ApplicationContext可以實現統一資源定位。
- ResourcePatternResolver 批量查找的ResourceLoader
ApplicationEventPublisher(在介紹spring事件的時候再詳細講)
MessageSource
提供國際化支持,不講了,有需要請轉至:blog.sina.com.cn/s/blog_85d7…
#四、最佳實踐
注解掃描
component/service/controller注解
@Component public class Person {@Resourceprivate Food food;public void setFood(Food food) {this.food = food;} }復制代碼bean的前置后置
@Component public class Person {@Resourceprivate Food food;public setFood(Food food) {this.food = food;}@PostConstructpublic void wash() {System.out.println("飯前洗手");}@PreDestroypublic void brush() {System.out.println("飯后刷牙");} }復制代碼總結
以上是生活随笔為你收集整理的IoC-spring 的灵魂(带你轻松理解IOC思想及bean对象的生成过程)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 云栖Android精华文章合集
- 下一篇: Android的init过程:init.