在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.beans
和org.springframework.context
包是Spring IoC容器的基礎。Spring提供了很多Application
接口的實現。在單獨的應用中,創建ClassPathXmlApplicationContext
和FileSystemXmlApplicationContext
的實例是非常常用的做法。示例如下:
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
Hello hello = (Hello) ac.getBean("hello");
hello.sayHello();
然而在大部分的應用場景中,不需要實例化一個或者多個Spring IoC容器的實例。例如在web應用的場景下,只需要在web.xml中創建七行樣板配置的代碼如下:
<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...)
(溫馨提示:點擊圖片可以查看高清大圖)
從上圖就能很清楚的看出ApplicationContext
繼承的接口分為五類:
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;
調用這個方法返回的是一個對象的實例。此接口通常用於封裝一個泛型工廠,在每次調用的時候返回一些目標對象新的實例。
ObjectFactory
和FactoryBean
是類似的,只不過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)
方法
- 通過名字判斷是否包含指定bean的定義的
或許你已經注意到了,有兩個方法含有類型是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
方法用於獲取父級BeanFactory
。containsLocalBean
用於判斷本地的工廠是否包含指定的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)
- ....