IoC-spring 的靈魂(帶你輕鬆理解IOC思想及bean物件的生成過程)

fulton發表於2017-06-04

在理解任何技術之前,我都會問自己一個問題:它的產生是為了解決什麼樣的問題,以及如何解決這些問題?希望你能在本篇文章中找到答案……
(由於大家對Ioc應該是經常使用了,所以這裡不會告訴你應該怎麼樣使用,重要的是理解思想原理,理解過程)

一、IOC的概念

IoC可以說是spring最核心的部分,是spring家族任意元件的基本。Ioc 本身並不能算為一種技術,而是一種思想,它使你從繁瑣的物件互動中解脫出來,而專注於物件本身,更進一步突出物件導向。
我們先來回答文章開頭問題的上半部分:
我們假設一個場景:Person(人)每天都要吃早餐(食物)。我們可以用如下程式表示

public class Person {
    public void eat() {
        Food food = new food();
        System.out.println("I eat food:{}", food.toString());
    }
}複製程式碼

在我們吃飯之前必須先new food()(做飯),要不然就吃不上。
Ioc 會怎麼樣做呢

public class Person {
    private Food food;
 public void eat() {
        System.out.println("I eat food:{}", food.toString());
 }
}複製程式碼

它會在你吃的時候將食物準備好,不需要你自己做飯。因為它認為:吃飯的人不應該身兼廚師的角色。
借用《spring 揭祕》中的漫畫再說明一下吧(因為我不會畫吃飯的漫畫)。它的意思是:穿衣服出門。如果不使用Ioc,你就得自己去取衣服穿上。用了IOC,已經有美女給你拿過來並幫你穿上(有沒有一種大款的感覺)。IOC就是讓你當大款,你只需要發揮自己的特長掙錢就可以了,其它的讓小祕來。

IoC-spring 的靈魂(帶你輕鬆理解IOC思想及bean物件的生成過程)

其實上面就是IOC的核心思想,也就是它要解決的問題:讓你脫離對依賴物件的維護,只需要隨用隨取,不需要關心依賴物件的任何過程。(是不是感覺特別簡單)

二、IOC的技術實現方式

接下來的問題是如何將依賴的物件準備好呢(依賴注入),常用的有兩種方式:構造方法注入和setter注入(雖然大家都很熟悉了,但還請原諒我再說一下)
構造器注入,它就代表了當Person這個物件生成時,就準備好了:即無論你吃不吃飯,飯就在那裡,不離不棄

public Person(Food food) {
    this.food = food;
}複製程式碼

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類的關係圖(如下所示)

IoC-spring 的靈魂(帶你輕鬆理解IOC思想及bean物件的生成過程)

有三個很重要的部分:

  • BeanDefinition 實現Bean的定義(即物件的定義),且完成了對依賴的定義
  • BeanDefinitionRegistry ,將定義好的bean,註冊到容器中(此時會生成一個註冊碼)
  • BeanFactory 是一個bean工廠類,從中可以取到任意定義過的bean
    最重要的部分就是BeanDefinition,它完成了Bean的生成過程。一般情況下我們都是通過配置檔案(xml,properties)的方式對bean進行配置,每種檔案都需要實現BeanDefinitionReader,因此是reader本身現了配置文字 到bean物件的轉換過程。當然我們自己也可以實現任意格式的配置檔案,只需要自己來實現reader即可。
    Bean的生成大致可以分為兩個階段:容器啟動階段和bean例項化階段
    IoC-spring 的靈魂(帶你輕鬆理解IOC思想及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的初始化過程:

IoC-spring 的靈魂(帶你輕鬆理解IOC思想及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型別的容器是比較合適的選擇。
    IoC-spring 的靈魂(帶你輕鬆理解IOC思想及bean物件的生成過程)

    ApplicationContext 還額外增加了三個歷能:ApplicationEventPublisher,ResourceLoader,MessageResource

ResourceLoader

ResourceLoader並不能將其看成是Spring獨有的功能,spring Ioc只是藉助於ResourceLoader來實現資源載入。也提供了各種各樣的資源載入方式:

  • DefaultResourceLoader 首先檢查資源路徑是否以classpath:字首打頭,如果是,則嘗試構造ClassPathResource類 型資源並返回。否則, 嘗試通過URL,根據資源路徑來定位資源
  • FileSystemResourceLoader 它繼承自Default-ResourceLoader,但覆寫了getResourceByPath(String)方法,使之從檔案系統載入資源並以 FileSystemResource型別返回
    • ResourcePatternResolver 批量查詢的ResourceLoader
      IoC-spring 的靈魂(帶你輕鬆理解IOC思想及bean物件的生成過程)

      spring與ResourceLoader之間的關係
      IoC-spring 的靈魂(帶你輕鬆理解IOC思想及bean物件的生成過程)

      所有ApplicationContext的具體實現類都會直接或者間接地實現AbstractApplicationContext,AbstactApplicationContext 依賴了了DeffaultResourceLoader, ApplicationContext 繼承了ResourcePatternResolver,所到頭來ApplicationContext的具體實現類都會具有DefaultResourceLoader 和 PathMatchingResourcePatterResolver的功能。這也就是會什麼ApplicationContext可以實現統一資源定位。

ApplicationEventPublisher(在介紹spring事件的時候再詳細講)

  1. ApplicationEvent:繼承自EventObject,同時是spring的application中事件的父類,需要被自定義的事件繼承。
  2. ApplicationListener:繼承自EventListener,spring的application中的監聽器必須實現的介面,需要被自定義的監聽器實現其onApplicationEvent方法
  3. ApplicationEventPublisherAware:在spring的context中希望能釋出事件的類必須實現的介面,該介面中定義了設定ApplicationEventPublisher的方法,由ApplicationContext呼叫並設定。在自己實現的ApplicationEventPublisherAware子類中,需要有ApplicationEventPublisher屬性的定義。
  4. ApplicationEventPublisher:spring的事件釋出者介面,定義了釋出事件的介面方法publishEvent。因為ApplicationContext實現了該介面,因此spring的ApplicationContext例項具有釋出事件的功能(publishEvent方法在AbstractApplicationContext中有實現)。在使用的時候,只需要把ApplicationEventPublisher的引用定義到ApplicationEventPublisherAware的實現中,spring容器會完成對ApplicationEventPublisher的注入。

MessageSource

提供國際化支援,不講了,有需要請轉至:blog.sina.com.cn/s/blog_85d7…

#四、最佳實踐
註解掃描

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:component-scan base-package="org.spring21"/>


</beans>複製程式碼

component/service/controller註解


@Component
public class Person {
    @Resource
    private Food food;

    public void setFood(Food food) {
        this.food = food;
    }
}複製程式碼

bean的前置後置

@Component
public class Person {
    @Resource
    private Food food;

    public setFood(Food food) {
        this.food = food;
    }

    @PostConstruct
    public void wash() {
        System.out.println("飯前洗手");
    }

    @PreDestroy
    public void brush() {
        System.out.println("飯後刷牙");
    }
}複製程式碼

相關文章