Spring詳解2.理解IoC容器


點擊進入個人博客

1 如何理解IoC

1.1 依然是KFC的案例

interface Burger {
    int getPrice();
}
interface Drink {
    int getPrice();
}

class ZingerBurger implements Burger {
    public int getPrice() {
        return 10;
    }
}
class PepsiCola implements Drink {
    public int getPrice() {
        return 5;
    }
}

/**
 * 香辣雞腿堡套餐
 */
class ZingerBurgerCombo {
    private Burger burger = new ZingerBurger();
    private Drink drink = new PepsiCola();

    public int getPrice() {
        return burger.getPrice() + drink.getPrice();
    }
}

上述案例中咱們實現了一個香辣雞腿堡套餐,在ZingerBurgerCombo中,咱們發現套餐與漢堡、套餐與飲品產生了直接的耦合。要知道肯德基中的套餐是很是多的,這樣須要創建大量不一樣套餐的類;並且若是該套餐中的百事可樂若是須要換成其餘飲品的話,是不容易改變的。java

class KFCCombo {
    private Burger burger;
    private Drink drink;

    public KFCCombo(Burger burger, Drink drink) {
        this.burger = burger;
        this.drink = drink;
    }
}

class KFCWaiter {
    public KFCCombo getZingerBurgerCombo() {
        return new KFCCombo(new ZingerBurger(), new PepsiCola());
    }
    // else combo…
}

爲了防止套餐和漢堡、飲品的耦合,咱們統一用KFCCombo來表示全部的套餐組合,引入了一個新的類KFCWaiter,讓她負責全部套餐的裝配。git

1.2 控制反轉與依賴注入

控制反轉IoC

IoC的字面意思是控制反轉,它包括兩部分的內容:github

  • 控制:在上述案例中,控制就是選擇套餐中漢堡和飲品的控制權。
  • 反轉:反轉就是把該控制權從套餐自己中移除,放到專門的服務員手中。

對於Spring來講,咱們經過Spring容器管理來管理和控制Bean的裝配。web

依賴注入

因爲IoC這個重要的概念比較晦澀隱諱,Martin Fowler提出了DI(Dependency Injection,依賴注入)的概念用以代替IoC,即讓調用類對某一接口實現類的依賴關係由第三方(容器或協做類)注入,以移除調用類對某一接口實現類的依賴。spring

Spring容器

Spring就是一個容器,它經過配置文件或註解描述類和類之間的依賴關係,自動完成類的初始化和依賴注入的工做。讓開發着能夠從這些底層實現類的實例化、依賴關係裝配等工做中解脫出來,專一於更有意義的業務邏輯開發。編程

2 IoC的類型

從注入方法上看,IoC能夠分爲:構造函數注入、屬性注入、接口注入數組

構造函數注入
class KFCCombo {
    private Burger burger;
    private Drink drink;

    // 在此注入對應的內容
    public KFCCombo(Burger burger, Drink drink) {
        this.burger = burger;
        this.drink = drink;
    }
}
屬性注入
class KFCCombo {
    private Burger burger;
    private Drink drink;

    // 在此注入對應的內容
    public void setBurger(Burger burger) {
        this.burger = burger;
    }

    // 在此注入對應的內容
    public void setDrink(Drink drink) {
        this.drink = drink;
    }
}
接口注入
interface InjectFood {
    void injectBurger(Burger burger);
    void injectDrink(Drink drink);
}

class KFCCombo implements InjectFood {
    private Burger burger;
    private Drink drink;
    
    // 在此注入對應的內容
    public void injectBurger(Burger burger) {
        this.burger = burger; 
    }
    // 在此注入對應的內容
    public void injectDrink(Drink drink) {
        this.drink = drink;
    }
}

接口注入和屬性注入並沒有本質的區別,並且還增長了一個額外的接口致使代碼增長,所以不推薦該方式。緩存

3 資源訪問

JDK提供的訪問資源的類(如java.net.URL、File等)並不能很好地知足各類底層資源的訪問需求,好比缺乏從類路徑或者Web容器上下文中獲取資源的操做類。所以,Spring提供了Resource接口,並由此裝載各類資源,包括配置文件、國際化屬性文件等資源。服務器

3.1 Resource類圖

Resource類圖.jpg

  • WritableResource:可寫資源接口,Spring3.1新增的接口,有2個實現類:FileSystemResourcePathResource
  • ByteArrayResource:二進制數組表示的資源,二進制數組資源能夠在內存中經過程序構造。
  • ClassPathResource:類路徑下的資源,資源以相對於類路徑的方式表示
  • FileSystemResouce:文件系統資源,資源以文件系統路徑的方式表示。
  • InputStreamResource:以輸入流返回標識的資源
  • ServletContextResource:爲訪問Web容器上下文中的資源而設計的類,負責以相對於Web應用根目錄的路徑來加載資源。支持以流和URL的形式訪問,在war包解包的狀況下,也能夠經過File方式訪問。 該類還能夠直接從JAR包中訪問資源。
  • UrlResource:封裝了java.net.URL,它使用戶可以訪問任何能夠經過URL表示的資源,如文件系統的資源、HTTP資源、FTP資源
  • PathResource:Spring 4.0提供的讀取資源文件的新類。PathResource封裝了java.net.URLjava.nio.file.Path、文件系統資源,它使用戶可以訪問任何能夠經過URL、Path、系統文件路徑標識的資源,如文件系統的資源、HTTP資源、FTP資源

3.2 資源加載

爲了訪問不一樣類型的資源,Resource接口下提供了不一樣的子類,這形成了使用上的不便。Spring提供了一個強大的加載資源的方式,在不顯示使用Resource實現類的狀況下,僅經過不一樣資源地址的特殊標示就能夠訪問對應的資源。架構

地址前綴 實例 釋義
classpath: classpath:com/ankeetc/spring/Main.class 從類不經中加載資源,classpath: 和 classpath:/ 是等價的,都是相對於類的根路徑,資源文件能夠在標準的文件系統中,也能夠在jar或者zip的類包中
file: file:/Users/zhengzhaoxi/.gitconfig 使用UrlResource從文件系統目錄中裝載資源,能夠採用絕對路徑或者相對路徑
http:// http://spiderlucas.github.io/... 使用UrlResource從web服務器中加載資源
ftp:// ftp://spiderlucas.github.io/atom.xml 使用UrlResource從FTP服務器中裝載資源
沒有前綴 com/ankeetc/spring/Main.class 根據ApplicationContext的具體實現類採用對應類型的Resource
classpath:與classpath*:

假設有多個Jar包或者文件系統類路徑下擁有一個相同包名(如com.ankeetc):

  • classpath:只會在第一個加載的com.ankeetc包的類路徑下查找
  • classpath*:會掃描到全部的這些JAR包及類路徑下出現的com.ankeetc類路徑。

這對於分模塊打包的應用很是有用,假設一個應用分爲2個模塊,每個模塊對應一個配置文件,分別爲module1.yaml 、module2.yaml,都放在了com.ankeetc的目錄下,每一個模塊單獨打成JAR包。可使用 classpath*:com/ankeetc/module*.xml加載全部模塊的配置文件。

Ant風格的資源地址通配符
  • ? 匹配文件名中的一個字符
  • * 匹配文件名中的任意字符
  • ** 匹配多層路徑
資源加載器

ResourceLoader類圖.png

  • ResourceLoader接口僅有一個getResource(String loacation)方法,能夠根據一個資源地址加載文件資源。不過,資源地址僅支持帶資源類型前綴的表達式,不支持Ant風格的資源路徑表達式。
  • ResourcePatternResolver擴展ResourceLoader接口,定義了一個新的接口方法getResource(String locationPattern),改方法支持帶資源類型前綴及Ant風格的資源路徑表達式。
  • PathMatchingResourcePatternResolver是Spring提供的標準實現類。

4 BeanFactory

  • BeanFactory是Spring框架最核心的接口,它提供了高級IoC的配置機制。咱們通常稱BeanFactory爲IoC容器。
  • BeanFactory是Spring框架等基礎設施,面向Spring自己。
  • BeanFactory是一個類工廠,能夠建立並管理各類類的對象。
  • 全部能夠被Spring容器實例化並管理的Java類均可以成爲Bean。
  • BeanFactory主要方法就是getBean(String beanName);
  • BeanFactory啓動IoC容器時,並不會初始化配置文件中定義的Bean,初始化動做在第一個調用時產生。
  • 對於單實例的Bean來講,BeanFactory會緩存Bean實例,在第二次使用geBean()獲取Bean時,將直接從IoC容器的緩存中獲取Bean實例。

4.1 BeanFactory體系結構

BeanFactory體系結構.png

  • BeanFactory:BeanFactory接口位於類結構樹的頂端,它最主要的方法就是getBean(String beanName) ,該方法從容器中返回特定名稱的Bean,BeanFactory的功能經過其餘接口而獲得不斷擴展。
  • ListableBeanFactory:該接口定義了訪問容器中Bean基本信息的若干方法,如:查看 Bean 的個數、獲取某一類型Bean的配置名、或查看容器中是否包含某一個Bean。
  • HierarhicalBeanFactory:父子級聯 IoC 容器的接口,子容器能夠經過接口方法訪問父容器。
  • ConfigurableBeanFactory:該接口加強了IoC容器的可定製性,它定義了設置類裝載器、屬性 編輯器、容器初始化後置處理器等方法。
  • AutowireCapableBeanFactory:定義了將容器中的Bean按某種規則(如:按名稱匹配、按類型匹配)進行自動裝配的方法。
  • SingletonBeanFactory:定義了容許在運行期間向容器註冊單實例Bean的方法。
  • BeanDefinitionRegistry:Spring配置文件中每個Bean節點元素在Spring容器裏都經過一個 BeanDefinition對象表示,它描述了Bean的配置信息。而BeanDefinitionRegistry接口提供了向容器手工註冊BeanDefinition對象的方法。

4.2 初始化BeanFactory

BeanFactory有多種實現,最經常使用的是 XmlBeanFactory,但在Spring 3.2時已被廢棄。目前建議使用XmlBeanDefinitionReader與DefaultListableBeanFactory。

<?xml version="1.0" encoding="utf-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
           http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
    <bean id="car" class="com.ankeetc.spring.Car"></bean>
</beans>
public static void main(String[] args) throws Exception {
        ResourceLoader resourceLoader = new DefaultResourceLoader();
        Resource resource = resourceLoader.getResource("beans.xml");

        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
        reader.loadBeanDefinitions(resource);

        beanFactory.getBean("");
    }

4.3 BeanFactory中Bean的生命週期

Bean的生命週期

  1. 當調用者經過getBean()向容器請求一個Bean時,若是容器註冊了InstantiationAwareBeanPostProcessor接口,則在實例化Bean以前,調用postProcessBeforeInstantiation()方法。
  2. 根據配置調用構造方法或者工廠方法實例化Bean。
  3. 調用InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation()
  4. 調用InstantiationAwareBeanPostProcessor#postProcessPropertyValues()方法。
  5. 設置屬性值。
  6. 若是Bean實現了BeanNameAware接口,則將調用BeanNameAware#setBeanName()接口方法,將配置文件中該Bean對應的名稱設置到Bean中。
  7. 若是Bean實現了BeanFactoryAware接口,將調用BeanFactoryAware#setBeanFactory()接口方法。
  8. 若是容器註冊了BeanPostProcessor接口,將調用BeanPostProcessor#postProcessBeforeInitialization()方法。入參Bean是當前正在處理的Bean,BeanName是當前Bean的配置名,返回的對象爲處理後的Bean。BeanPostProcessor在Spring框架中佔有重要的地位,爲容器提供對Bean進行後續加工處理的切入點,AOP、動態代理都經過BeanPostProcessor來實現。
  9. 若是Bean實現了InitializingBean接口,則將調用InitializingBean#afterPropertiesSet()方法。
  10. 若是<bean>中定義了init-method初始化方法,則執行這個方法。
  11. 調用BeanPostProcessor#postProcessAfterInitialization()方法再次加工Bean。
  12. 若是<bean>中指定了Bean的做用範圍爲scope='prototype',則將Bean返回給調用者,Spring再也不管理這個Bean的生命週期。若是scope='singleton',則將Bean放入Spring IoC容器的緩存池中,並返回Bean。
  13. 對於scope='singleton'的Bean,當容器關閉時,將觸發Spring對Bean的後續生命週期的管理工做。若是Bean實現了DisposableBean接口,將調用DisposableBean#destroy()方法。
  14. 對於`scope='singleton'的Bean,若是經過<bean>的destroy-method屬性指定了Bean的銷燬方法,那麼Spring將執行這個方法。
Bean方法的分類
  • Bean自身的方法:構造方法、Setter設置屬性值、init-method、destroy-method。
  • Bean級生命週期接口方法:如上圖中藍色的部分。BeanNameAware、BeanFactoryAware、InitializingBean、DisposableBean,這些由Bean自己直接實現
  • 容器級生命週期接口方法:如上圖中的紅色的部分。InstantiationAwareBeanPostProcessor和BeanPostProcessor這兩個接口,通常稱它們的實現類爲後處理器。後處理器接口不禁Bean自己實現,實現類以容器附加裝置的形式註冊到Spring容器中。當Spring容器建立任何Bean的時候,這些後處理器都會起做用,因此這些後處理器的影響是全局的。若是須要多個後處理器,能夠同時實現Ordered接口,容器將按照特定的順序依此調用這些後處理器。
  • 工廠後處理器接口方法:AspectJWeavingEnabler、CustomAutowireConfigurer、ConfigurationClassPostProcessor等方法,工廠後處理器也是容器級的,在應用上下文裝配配置文件後當即使用。

4.4 BeanFactory生命週期案例

public class Main {
    public static void main(String[] args) {
        Resource resource = new PathMatchingResourcePatternResolver().getResource("classpath:beans.xml");

        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
        reader.loadBeanDefinitions(resource);

        beanFactory.addBeanPostProcessor(new InstantiationAwareBeanPostProcessor() {
            public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
                System.out.println("InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation()");
                return null;
            }

            public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
                System.out.println("InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation()");
                return true;
            }

            public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
                System.out.println("InstantiationAwareBeanPostProcessor.postProcessPropertyValues()");
                return pvs;
            }

            public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
                System.out.println("BeanPostProcessor.postProcessBeforeInitialization()");
                return bean;
            }

            public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
                System.out.println("BeanPostProcessor.postProcessAfterInitialization()");
                return bean;
            }
        });

        MyBean myBean = beanFactory.getBean("myBean", MyBean.class);
        beanFactory.destroySingletons();
    }
}

class MyBean implements BeanNameAware, BeanFactoryAware, InitializingBean, DisposableBean {
    private String prop;

    public MyBean() {
        System.out.println("MyBean:構造方法");
    }

    public String getProp() {
        System.out.println("MyBean:get方法");
        return prop;
    }

    public void setProp(String prop) {
        System.out.println("MyBean:set方法");
        this.prop = prop;
    }

    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("MyBean:BeanFactoryAware.setBeanFactory()");
    }

    public void setBeanName(String name) {
        System.out.println("MyBean:BeanNameAware.setBeanName()");
    }

    public void destroy() throws Exception {
        System.out.println("MyBean:DisposableBean.destroy()");
    }

    public void afterPropertiesSet() throws Exception {
        System.out.println("MyBean:InitializingBean.afterPropertiesSet()");
    }

    // 配置文件中init-method
    public void myInit() {
        System.out.println("MyBean:myInit()");
    }

    // 配置文件中destroy-method
    public void myDestroy() {
        System.out.println("MyBean:myDestroy()");
    }
}
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">

    <bean class="com.ankeetc.spring.MyBean" name="myBean" init-method="myInit" destroy-method="myDestroy">
        <property name="prop" value="prop"/>
    </bean>
</beans>

4.5 關於Bean生命週期接口的探討

  • Bean生命週期帶來的耦合:經過實現Spring的Bean生命週期接口對Bean進行額外控制,雖然讓Bean具備了更細緻的生命週期階段,但也帶來了一個問題,Bean和Spring框架緊密綁定在一塊兒了,這和Spring一直推崇的「不對應用程序類做任何限制」的理念是相悖的。業務類本應該徹底POJO化,只實現本身的業務接口,不須要和某個特定框架(包括Spring框架)的接口關聯。
  • init-method和destroy-method:能夠經過<bean>的init-methoddestroy-method屬性配置方式爲Bean指定初始化和銷燬的方法,採用這種方式對Bean生命週期的控制效果和經過實現InitializingBeanDisposableBean接口所達到的效果是徹底相同的,並且達到了框架解耦的目的。
  • BeanPostProcessor:BeanPostProcessor接口不須要Bean去繼承它,能夠像插件同樣註冊到Spring容器中,爲容器提供額外功能。

5 ApplicationContext

5.1 Application體系結構

ApplicationContext體系結構

  • ApplicationEventPublisher:讓容器擁有了發佈應用上下文事件的功能,包括容器啓動事件 、關閉事件等。實現了ApplicationListener事件監聽接口的Bean能夠接收到容器事件,並對容器事件進行響應處理。在ApplicationContext抽象實現類AbstractApplicationContext中存在一個 ApplicationEventMulticaster,它負責保存全部的監聽器,以便在容器產生上下文事件時通知這些事件監聽者。
  • MessageSource:爲容器提供了i18n國際化信息訪問的功能。
  • ResourcePatternResolver:全部ApplicationContext實現類都實現了相似於PathMatchingResourcePatternResolver的功能,能夠經過帶前綴 Ant 風格的資源類文件路徑來裝載Spring的配置文件。
  • LifeCycle:它提供了start()和stop()兩個方法,主要用於控制異步處理過程。在具體使用時,該接口同時被ApplicationContext實現及具體Bean實現,ApplicationContext會將start/stop的信息傳遞給容器中全部實現了該接口的Bean,以達到管理和控制JMX、任務調度等目的。
  • ConfigurableApplicationContext:它擴展了ApplicationContext,讓ApplicationContext具備啓動、刷新和關閉應用上下文的能力。上下文關閉時,調用 refresh()便可啓動上下文;若是已經啓動,則調用refresh()可清除緩存並從新加載配置信息;調用close()可關閉應用上下文。

5.2 初始化ApplicationContext

  • ClassPathXmlApplicationContext:從類路徑中加載XML配置文件,也能夠顯示的使用帶資源類型前綴的路徑。
  • FileSystemXmlApplicationContext:從文件系統路徑下加載XML配置文件,也能夠顯示的使用帶資源類型前綴的路徑。
  • AnnotationConfigApplicationContext:一個標註@Configuration註解的POJO便可提供Spring所需的Bean配置信息。
  • GenericGroovyApplicationContext:從Groovy配置中提取Bean。
public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext classPathXmlApplicationContext =
                new ClassPathXmlApplicationContext("beans.xml");
        FileSystemXmlApplicationContext fileSystemXmlApplicationContext =
                new FileSystemXmlApplicationContext("file:/Users/zhengzhaoxi/Git/spring/src/main/resources/beans.xml");
        AnnotationConfigApplicationContext annotationConfigApplicationContext =
                new AnnotationConfigApplicationContext(Config.class);
    }

5.3 WebApplicationContext體系結構

見後續章節

5.4 ApplicationContext中Bean的生命週期

Bean的生命週期

不一樣點

Bean在ApplicationContext和BeanFactory中生命週期相似,但有如下不一樣點

  1. 若是Bean實現了ApplicationContextAware接口,則會增長一個調用該接口方法的ApplicationContextAware.setApplicationContext的方法。
  2. 若是在配置文件中生命了工廠後處理器接口BeanFactoryPostProcessor的實現類,則應用上下文在裝在配置文件以後、初始化Bean實例以前會調用這些BeanFactoryPostProcessor對配置信息進行加工處理。Spring提供了多個工廠容器,如CustomEditorConfigurePropertyPlaceholderConfigurer等。工廠後處理器是容器級的,只在應用上下文初始化時調用一次,其目的是完成一些配置文件加工處理工做。
  3. ApplicationContext能夠利用Java反射機制自動識別出配置文件中定義的BeanPostProcessorInstantiationAwareBeanPostProcessorBeanFactoryPostProcessor,並自動將它們註冊到應用上下文中(以下所示);而BeanFactory須要經過手工調用addBeanPostProcessor()方法註冊。
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">

    <bean class="com.ankeetc.spring.MyBean" name="myBean" init-method="myInit" destroy-method="myDestroy">
        <property name="prop" value="prop"/>
    </bean>

    <!-- 工廠後處理器 -->
    <bean id="myBeanPostProcessor" class="com.ankeetc.spring.MyBeanPostProcessor"/>

    <!-- 註冊Bean後處理器 -->
    <bean id="myBeanFactoryPostProcessor" class="com.ankeetc.spring.MyBeanFactoryPostProcessor"/>
</beans>

6 BeanFactory與ApplicationContext區別

  • 通常稱BeanFactory爲IoC容器:Bean工廠(com.springframework.beans.factory.BeanFactory)是Spring框架中最核心的接口,它提供了高級 IoC 的配置機制 。BeanFactory使管理不一樣類型的Java對象成爲可能。BeanFactory是Spring框架的基礎設施,面向Spring自己。
  • 通常稱ApplicationContext爲應用上下文或Spring容器:應用上下文(com.springframework.context.ApplicationContext)創建在BeanFactory基礎之上,提供了更多面向應用的功能,它提供了國際化支持和框架事件體系,更易於建立實際應用 。 ApplicationContext 面向使用 Spring 框架的開發者,幾乎全部的應用場合均可以直接使用 ApplicationContext。
  • BeanFactory在初始化容器的時候,並未實例化Bean,直到第一次訪問某個Bean時才實例化Bean;ApplicationContext在初始化應用上下文的時候就實例化所有單實例Bean。
  • ApplicationContext能夠利用Java反射機制自動識別出配置文件中定義的BeanPostProcessorInstantiationAwareBeanPostProcessorBeanFactoryPostProcessor,並自動將它們註冊到應用上下文中;而BeanFactory須要經過手工調用addBeanPostProcessor()方法註冊。

7 父子容器

  • 經過HierarchicalBeanFactory接口,Spring的IoC容器能夠創建父子層級關聯的容器體系,子容器能夠訪問父容器中的 Bean,但父容器不能訪問子容器的Bean。
  • 在容器內,Bean的id必須是惟一的,但子容器能夠擁有一個和父容器id相同的 Bean。
  • 父子容器層級體系加強了Spring容器架構的擴展性和靈活性,所以第三方能夠經過編程的方式,爲一個已經存在的容器添加一個或多個特殊用途的子容器,以提供一些額外的功能。
  • Spring使用父子容器的特性實現了不少能力,好比在Spring MVC中,展示層Bean位於一個子容器中,而業務層和持久層的Bean位於父容器中。 這樣展示層Bean就能夠引用業務層和持久層的Bean,而業務層和持久層的Bean則看不到展示層的Bean。
相關文章
相關標籤/搜索