Spring源碼閱讀-IoC容器解析

在Spring框架中最重要的是Spring IoC容器,它是Spring框架的核心。本文將從更高的角度來解析Sping IoC容器,瞭解其是如何設計的。瞭解同樣東西最好的辦法是從其核心本質出發,只要把握住了這樣一個核心,其餘的一些東西也迎刃而解了。這是一個很好的開端,咱們一塊兒開始吧...程序員

Spring IoC容器

org.springframework.context.ApplicationContext接口表明Spring IoC容器,主要負責bean的實例化、配置、裝配,簡而言之,Spring IoC容器是管理這些bean的(這裏所說的bean指的是組成你的應用程序中的對象,而且這些對象被Spring所管理)。容器如何知道哪些對象要進行實例化、配置和裝配的呢?是經過讀取配置文件元數據來達到這個效果的,配置文件元數據是用xml配置、Java註解和Java代碼配置來表示的。這使得做爲程序員的咱們,只須要向Spring容器提供配置元數據,Spring容器就能在咱們的應用中實例化、配置和裝配這些對象。org.springframework.beansorg.springframework.context包是Spring IoC容器的基礎。Spring提供了不少Application接口的實現。在單獨的應用中,建立ClassPathXmlApplicationContextFileSystemXmlApplicationContext的實例是很是經常使用的作法。示例以下:web

ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
Hello hello = (Hello) ac.getBean("hello");
hello.sayHello();

然而在大部分的應用場景中,不須要實例化一個或者多個Spring IoC容器的實例。例如在web應用的場景下,只須要在web.xml中建立七行樣板配置的代碼以下:spring

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/applicationContext.xml</paramvalue>
</context-param>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

下面這張圖從更高的視角展現了Spring是怎樣工做的。你的應用程序中的類是和配置元數據組合在一塊兒,以便在ApplicationContext建立和初始化以後,你擁有了一個徹底配置的、可執行的系統。編程

ApplicationContext設計解析

爲了方便對ApplicationContext接口的層次結構有一個大概的認識,下面使用IDEA來生成ApplicationContext的繼承關係圖。(選中ApplicationContext接口->右鍵->Diagrams->Show Diagrams...)app

(舒適提示:點擊圖片能夠查看高清大圖)框架

從上圖就能很清楚的看出ApplicationContext繼承的接口分爲五類:ide

  • BeanFactory:提供了可以管理任何對象的高級配置機制,這個接口是Spring框架中比較重要的一個接口。
    • ListableBeanFactory:從該接口的名字就能知道,該接口除了擁有BeanFactory的功能外,該接口還有能列出factory中全部bean的實例的能力。
    • HierarchicalBeanFactory:該接口除了擁有BeanFactory的功能外,還提供了BeanFactory分層的機制,查找bean的時候,除了在自身BeanFactory查找外,若是沒有查找到,還會在父級BeanFactory進行查找。
  • MessageSource:消息資源的處理,用於國際化。
  • ApplicationEventPublisher:用於處理事件發佈機制。
  • EnvironmentCapable:提供了Environment的訪問能力。
  • ResourceLoader:用於加載資源的策略接口(例如類路徑下的資源、系統文件下的資源等等)。
    • ResourcePatternResolver:用於將位置模式(例如Ant風格的路徑模式)解析成資源對象的策略接口。classpath*:前綴能匹配因此類路徑下的資源。

先看一下在ApplicationContext中定義的方法:函數式編程

String getId(); // 獲取ApplicationContext的惟一id
String getApplicationName(); // 該上下文所屬的已經部署了的應用的名字,默認爲""
String getDisplayName(); // 友好的展現名字
long getStartupDate(); // 該上下文第一次加載的時間

ApplicationContext getParent(); // 父級ApplicationContext
AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException;

前四個方法用於獲取該ApplicationContext的一些基本信息,getAutowireCapableBeanFactory()用於暴露AutowireCapableBeanFactory的功能,這一般不是提供給用於代碼使用的,除非你想要在應用上下文的外面初始化bean的實例,關於AutowireCapableBeanFactory後面會有更加詳細的解析。函數

BeanFactory

BeanFactory是Spring框架中比較重要的一個接口,下面列出了這個接口中的方法的定義:

// 獲取bean
Object getBean(String name) throws BeansException;
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

// 獲取bean的提供者(對象工廠)
<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
<T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);

boolean containsBean(String name); // 是否包含指定名字的bean
boolean isSingleton(String name) throws NoSuchBeanDefinitionException; // 是否爲單例
boolean isPrototype(String name) throws NoSuchBeanDefinitionException; // 是否爲原型
// 指定名字的bean是否和指定的類型匹配
boolean isTypeMatch(String name, ResolvableType typeToMatch);
boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
Class<?> getType(String name) 
    throws NoSuchBeanDefinitionException; // 獲取指定名字的bean的類型
String[] getAliases(String name); // 獲取指定名字的bean的全部別名

這些方法大體能夠分爲三類:

  • getBean()方法用於獲取匹配的bean的實例對象(有多是Singleton或者Prototype的),若是沒有找到匹配的bean則拋出BeansException子類的異常。若是在當前的工廠實例中沒有找到匹配的bean,會在父級的工廠中進行查找。帶有args參數的getBean()方法,容許顯式的去指定構造器或者工廠方法的參數,會覆蓋了在bean的定義中定義的參數,這僅僅在建立一個新的實例的時候才起做用,而在獲取一個已經存在的實例是不起做用的。

  • getBeanProvider()方法用於獲取指定bean的提供者,能夠看到它返回的是一個ObjectProvider,其父級接口是ObjectFactory。首先來看一下ObjectFactory,它是一個對象的實例工廠,只有一個方法:

    T getObject() throws BeansException;

    調用這個方法返回的是一個對象的實例。此接口一般用於封裝一個泛型工廠,在每次調用的時候返回一些目標對象新的實例。ObjectFactoryFactoryBean是相似的,只不過FactoryBean一般被定義爲BeanFactory中的服務提供者(SPI)實例,而ObjectFactory一般是以API的形式提供給其餘的bean。簡單的來講,ObjectFactory通常是提供給開發者使用的,FactoryBean通常是提供給BeanFactory使用的。

    ObjectProvider繼承ObjectFactory,特爲注入點而設計,容許可選擇性的編程和寬泛的非惟一性的處理。在Spring 5.1的時候,該接口從Iterable擴展,提供了對Stream的支持。該接口的方法以下:

    // 獲取對象的實例,容許根據顯式的指定構造器的參數去構造對象
    T getObject(Object... args) throws BeansException;
    // 獲取對象的實例,若是不可用,則返回null
    T getIfAvailable() throws BeansException;
    T getIfAvailable(Supplier<T> defaultSupplier) throws BeansException;
    void ifAvailable(Consumer<T> dependencyConsumer) throws BeansException;
    // 獲取對象的實例,若是不是惟一的或者沒有首先的bean,則返回null
    T getIfUnique() throws BeansException;
    T getIfUnique(Supplier<T> defaultSupplier) throws BeansException;
    void ifUnique(Consumer<T> dependencyConsumer) throws BeansException;
    
    // 獲取多個對象的實例
    Iterator<T> iterator();
    Stream<T> stream();
    Stream<T> orderedStream()

    這些接口是分爲兩類,

    • 一類是獲取單個對象,getIfAvailable()方法用於獲取可用的bean(沒有則返回null),getIfUnique()方法用於獲取惟一的bean(若是bean不是惟一的或者沒有首選的bean返回null)。getIfAvailable(Supplier<T> defaultSupplier)getIfUnique(Supplier<T> defaultSupplier),若是沒有獲取到bean,則返回defaultSupplier提供的默認值,ifAvailable(Consumer<T> dependencyConsumer)ifUnique(Consumer<T> dependencyConsumer)提供了以函數式編程的方式去消費獲取到的bean。
    • 另外一類是獲取多個對象,stream()方法返回連續的Stream,不保證bean的順序(一般是bean的註冊順序)。orderedStream()方法返回連續的Stream,預先會根據工廠的公共排序比較器進行排序,通常是根據org.springframework.core.Ordered的約定進行排序。
  • 其餘的是一些工具性的方法:

    • 經過名字判斷是否包含指定bean的定義的containsBean(String name)方法
    • 判斷是單例和原型的isSingleton(String name)isPrototype(String name)方法
    • 判斷給定bean的名字是否和類型匹配的isTypeMatch方法
    • 根據bean的名字來獲取其類型的getType(String name)方法
    • 根據bean的名字來獲取其別名的getAliases(String name)方法

或許你已經注意到了,有兩個方法含有類型是ResolvableType的參數,那麼ResolvableType是什麼呢?假如說你要獲取泛型類型的bean:MyBean<TheType>,根據Class 來獲取,確定是知足不了要求的,泛型在編譯時會被擦除。使用 ResolvableType就能知足此需求,代碼以下:

ResolvableType type = ResolvableType.forClassWithGenerics(MyType.class, TheType.class);
ObjectProvider<MyType<TheType>> op = applicationContext.getBeanProvider(type);
MyType<TheType> bean = op.getIfAvailable()

簡單的來講,ResolvableType是對Java java.lang.reflect.Type的封裝,而且提供了一些訪問該類型的其餘信息的方法(例如父類, 泛型參數,該類)。從成員變量、方法參數、方法返回類型、類來構建ResolvableType的實例。

ListableBeanFactory

ListableBeanFactory接口有能列出工廠中全部的bean的能力,下面給出該接口中的全部方法:

boolean containsBeanDefinition(String beanName); // 是否包含給定名字的bean的定義
int getBeanDefinitionCount(); // 工廠中bean的定義的數量
String[] getBeanDefinitionNames(); // 工廠中全部定義了的bean的名字
// 獲取指定類型的bean的名字
String[] getBeanNamesForType(ResolvableType type);
String[] getBeanNamesForType(Class<?> type);
String[] getBeanNamesForType(Class<?> type, boolean includeNonSingletons, boolean allowEagerInit);
// 獲取全部使用提供的註解進行標註的bean的名字
String[] getBeanNamesForAnnotation(Class<? extends Annotation> annotationType);
// 查找指定bean中的全部指定的註解(會考慮接口和父類中的註解)
<A extends Annotation> A findAnnotationOnBean(String beanName, Class<A> annotationType)
    throws NoSuchBeanDefinitionException;

// 根據指定的類型來獲取全部的bean
<T> Map<String, T> getBeansOfType(Class<T> type) throws BeansException;
<T> Map<String, T> getBeansOfType(Class<T> type, boolean includeNonSingletons, boolean allowEagerInit)
    throws BeansException;
// 獲取全部使用提供的註解進行標註了的bean
Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType) throws BeansException;

上面的這些方法都不考慮祖先工廠中的bean,只會考慮在當前工廠中定義的bean。

  • 前八個方法用於獲取bean的一些信息
  • 最後的三個方法用於獲取全部知足條件的bean,返回結果Map中的鍵爲bean的名字,值爲bean的實例。這些方法都會考慮經過FactoryBean建立的bean,這也意味着FactoryBean會被初始化。爲何這裏的三個方法不返回List?Map不光包含這些bean的實例,並且還包含bean的名字,而List只包含bean的實例。也就是說Map比List更加的通用。

HierarchicalBeanFactory

HierarchicalBeanFactory接口定義了BeanFactory之間的分層結構,ConfigurableBeanFactory中的setParentBeanFactory方法能設置父級的BeanFactory,下面列出了HierarchicalBeanFactory中定義的方法:

BeanFactory getParentBeanFactory(); // 獲取父級的BeanFactory
// 本地的工廠是否包含指定名字的bean
boolean containsLocalBean(String name);

這兩個方法都比較直接明瞭,getParentBeanFactory方法用於獲取父級BeanFactorycontainsLocalBean

用於判斷本地的工廠是否包含指定的bean,忽略在祖先工廠中定義的bean。

MessageSource

MessageSource主要用於消息的國際化,下面是該接口中的方法定義:

// 獲取消息
String getMessage(String code, Object[] args, String defaultMessage, Locale locale);
String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException;
String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;

以上的三個方法都是用於獲取消息的,第一個方法提供了默認消息,第二個接口若是沒有獲取到指定的消息會拋出異常。第三個接口中的MessageSourceResolvable參數是對代碼、參數值、默認值的一個封裝。

ApplicationEventPublisher

ApplicationEventPublisher接口封裝了事件發佈功能,提供Spring中事件的機制。接口中的方法定義以下:

// 發佈事件
void publishEvent(ApplicationEvent event);
void publishEvent(Object event);

第一個方法用於發佈特定於應用程序事件。第二個方法能發佈任意的事件,若是事件不是ApplicationEvent,那麼會被包裹成PayloadApplicationEvent事件。

EnvironmentCapable

EnvironmentCapable提供了訪問Environment的能力,該接口只有一個方法:

Environment getEnvironment();

Environment表示當前正在運行的應用的環境變量,它分爲兩個部分:profiles和properties。它的父級接口PropertyResolver提供了property的訪問能力。

ResourceLoader和ResourcePatternResolver

先來看一下ResourceLoader,該接口是用來加載資源(例如類路徑或者文件系統中的資源)的策略接口。該接口中的方法以下:

Resource getResource(String location); // 根據指定的位置獲取資源
ClassLoader getClassLoader(); // 獲取該資源加載器所使用的類加載器

該接口只有簡單明瞭的兩個方法,一個是用來獲取指定位置的資源,一個用於獲取資源加載器所使用的類加載器。Resource是從實際類型的底層資源(例如文件、類路徑資源)進行抽象的資源描述符。先看下Resource中的方法:

boolean exists(); // 資源其實是否存在
boolean isReadable(); // 資源是否可讀
boolean isOpen(); // 檢查資源是否爲打開的流
boolean isFile(); // 資源是否爲文件系統上的一個文件

URL getURL() throws IOException; // 獲取url
URI getURI() throws IOException; // 獲取URI
File getFile() throws IOException; // 獲取文件

ReadableByteChannel readableChannel() throws IOException; // 獲取ReadableByteChannel

long contentLength() throws IOException; // 資源的內容的長度
long lastModified() throws IOException; // 資源的最後修改時間
// 相對於當前的資源建立一個新的資源
Resource createRelative(String relativePath) throws IOException; 
String getFilename(); // 獲取資源的文件名
String getDescription(); // 獲取資源的描述信息

Resource的父級接口InputStreamSource,能夠簡單的理解爲InputStream的來源,只有一個方法,以下:

InputStream getInputStream() throws IOException; // 獲取輸入流

接下來在來看一下ResourcePatternResolver,該接口用於解析一個位置模式(例如Ant風格的路徑模式),該接口只有一個方法,以下:

// 將給定的位置模式解析成資源對象
Resource[] getResources(String locationPattern) throws IOException;

Spring IoC容器設計覆盤

假如讓你設計IoC容器,你該如何去作呢?首先你應該要明確你設計的容器的功能和特性,而後根據這些功能和特性設計出合理的接口。下面只是粗略的分析一下:

  • IoC容器對bean的配置和管理,那麼是否是須要設計一個接口來完成這些功能呢?(BeanFactory)
  • 既然須要這些元數據的配置,那麼是否是須要設計一個接口來完成對一些配置文件的讀取。(ResourceLoader和Resource)
  • 在IoC容器初始化、摧毀的時候,是否是可能要執行一些操做呢?那麼是否是須要使用事件機制來完成呢?(ApplicationEventPublisher)
  • ....

本文思惟導圖

相關文章
相關標籤/搜索