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

在理解任何技術以前,我都會問本身一個問題:它的產生是爲了解決什麼樣的問題,以及如何解決這些問題?但願你能在本篇文章中找到答案……
(因爲你們對Ioc應該是常用了,因此這裏不會告訴你應該怎麼樣使用,重要的是理解思想原理,理解過程)javascript

1、IOC的概念

IoC能夠說是spring最核心的部分,是spring家族任意組件的基本。Ioc 自己並不能算爲一種技術,而是一種思想,它使你從繁瑣的對象交互中解脫出來,而專一於對象自己,更進一步突出面向對象。
咱們先來回答文章開頭問題的上半部分:
咱們假設一個場景:Person(人)天天都要吃早餐(食物)。咱們能夠用以下程序表示html

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

在咱們吃飯以前必須先new food()(作飯),要否則就吃不上。
Ioc 會怎麼樣作呢java

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

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


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

2、IOC的技術實現方式

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

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

setter注入,有所不一樣:俺不是那麼隨便的食物,你得喊我(set)俺纔過來,有種悶騷的感受。反正我就喜歡這種……數據庫

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

但不管前提哪種注入方法,你總得有小祕來執行吧!!!so,你只須要默默地躺在那來享受,小祕帶來百般絕技!!!app

3、IOC容器

小祕絕技雖然精彩,但要實現卻並不那麼容易。它須要一系列技術要實現。首先它須要知道服務的對象是誰,以及須要爲服務對象提供什麼樣的服務。提供的服務指:要完成對象的構建(即把飯作好),將其送到服務對象即完成對象的綁定(即把飯端到我面前)。
上面的話別看糊塗了,再聲明一下,Ioc須要實現兩個技術:框架

  • 對象的構建
  • 對象的綁定

對於這兩個方面技術的實現具備不少的方式:硬編碼(Ioc 框架都支持),配置文件(咱們的重點),註解(最潔的方式)。但不管哪一種方式都是在Ioc容器裏面實現的(咱們能夠理解爲一個大池子,裏面躺着各類各樣的對象,並能經過必定的方式將它們聯繫起來)
spring提供了兩種類型的容器,一個是BeanFactory,一個是ApplicationContext(能夠認爲是BeanFactory的擴展),下面咱們將介紹這兩種容器如何實現對對象的管理。ide

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能夠實現統一資源定位。

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…

#4、最佳實踐
註解掃描

<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("飯後刷牙");
    }
}複製代碼
相關文章
相關標籤/搜索