前言
在上篇文章中,和大家一起討論了 Spring 的整體架構,其大致分為五個模塊:核心容器、AOP、Web、Data 數據訪問、Test模塊。其中核心容器是 Spring 的核心部分,其它模塊也都依賴於該容器。這里和就大家一起深入討論 Spring 的容器,它的作用是什么、怎么實現的。
1、容器簡介
容器顧名思義就是用來裝東西的,裝的是什么?裝的是 Bean。
Bean 是 Spring 的基本單位,在基於 Spring 的 Java EE 應用中,所有的組件都被當成 Bean 處理,包括數據源、Hibernate 的 SessionFactory、事務管理器等。在 Spring 中,Bean 是一個非常廣義的概念,任何 Java 對象、Java 組件都被當成 Bean 處理。
那容器僅僅是用來保存 Bean 這么簡單么?不是。
當我們需要使用某個 Bean 時,容器會自動幫我們創建,並在適當時銷毀。還有一種情況,當某個 Bean 中需創建另一個 Bean 時,也就是 Bean 之間有依賴關系,這種依賴的 Bean 也是由容器自動創建。在外界有一個標准的名詞,前者稱呼為 IOC,也就是控制反轉,后者稱呼為 DI,也就是依賴注入。
IOC/DI
IOC (Inversion of Control) 控制反轉:所謂控制反轉,就是當我們需要某個 Bean 時,將 Bean 的名稱告知容器,由容器去創建該 Bean,而不是我們手動 new 一個,這里 Bean 創建管理的控制權都交給了容器,所以這是一種控制權的反轉。其通俗點講就是需要什么東西讓別人送過來,而不是自己去拿。
DI (Dependency Injection) 依賴注入:就是指當 A Bean 里面需創建 B Bean 時,會在創建 A Bean 的時候,自動將依賴的 B Bean 注入進去,其 B Bean 是被動接受注入而不是自己主動去找。換句話說就是指 A Bean 不是從容器中查找它依賴的 B Bean,而是在容器創建 A Bean 候主動將它依賴的 B Bean 注入給它。
IOC 和 DI 其實歸根結底實現的功能是相同的,只是同樣的功能站在不同的角度來闡述罷了,不過我們通常喜歡將這兩個概念統稱為 IOC。當然,在真實場景中,交由 Spring 容器創建的 Bean 泛指在應用程序中的表現層、業務層、持久層等各層對應的 Bean,如 Controller、Service 等;進行數據交互的模型,如 DTO、VO 等就不需交由 Spring 來創建。
所以,容器本質上可以也可以看作是 Bean 工廠,該工廠管理 Bean 的生命周期,以及 Bean 之間的依賴關系。外界也將 Spring 容器稱為 IOC 容器。當然,這里容器僅僅是 Spring 的抽象概念,代碼中將其具象化為 BeanFactory 或 ApplicationContext,容器功能也由具象化的類進行處理。
2、容器的結構
容器的實現類並不是唯一的,Spring 框架提供了多個容器的實現,這些容器分為兩套體系:一套是早期的 BeanFactory 體系;還有一套是現在常用的 ApplicationContext,也可稱為應用上下文,它繼承了 BeanFactory,它除了有 BeanFactory 的功能外
,還提供了其他服務,例如事務和 AOP 服務、國際化(il8n)的消息源以及應用程序事件處理等企業級的服務。
說到這,就不得不說 Spring 的兩種配置方式,在早期都是 XML 配置文件的方式,而現在使用的是注解配置的方式。BeanFactory 體系的容器一般用來處理 XML 配置文件的方式,而 ApplicationContext 體系則都可以處理。
2.1 BeanFactory
BeanFactory 是容器最基礎的類,它定義了容器的基本功能規范:
public interface BeanFactory {
// 對 FactoryBean 的轉義定義,因為如果使用 bean 的名字檢索 FactoryBean 得到的對象是工廠生成的對象,
// 如果需要得到工廠本身,需要轉義(FactoryBean 在后續會詳細介紹)
String FACTORY_BEAN_PREFIX = "&";
// 根據 bean 的名字,獲取在容器中 bean 實例
Object getBean(String name) throws BeansException;
//根據 bean 的名字和 Class 類型來得到 bean 實例,增加了類型安全驗證機制。
<T> T getBean(String name, @Nullable 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 的檢索,看看是否在容器有這個名字的 bean
boolean containsBean(String name);
// 根據 bean 名字,判斷這個 bean 是不是單例
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
// 根據 bean 名字,判斷這個 bean 是不是原型
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
// 根據 bean 名字,判斷是否與指定的類型匹配
boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, @Nullable Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
// 得到 bean 實例的 Class 類型
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
// 得到bean 的別名,如果根據別名檢索,那么其原名也會被檢索出來
String[] getAliases(String name);
}
在 BeanFactory 里只對容器的基本行為作了定義,其根本不關心你的 Bean 是如何定義怎樣加載的。
正如我們只關心工廠里得到什么的產品對象,至於工廠是怎么生產這些對象的,這個基本的接口不關心。而要知道工廠是如何產生對象的,我們就需要看具體的容器了,也就是 BeanFactory 的子類。
BeanFactory 體系中常用的實現類有:
- ListableBeanFactory:提供容器中 bean 迭代的功能。如返回所有 Bean 的名字、容器中 Bean 的數量等。
- HierarchicalBeanFactory:提供父容器的訪問功能,可通過 ConfigurableBeanFactory 的 setParentBeanFactory 方法設置父容器。
- AutowireCapableBeanFactory:為 Spring 容器之外的 Bean ,也就是未交由 Spring 管理的 Bean ,提供依賴注入的功能。
以上三個是 BeanFactory 的直系親屬,這個三個直系親屬下面又派生了兩個復雜的容器:
- ConfigurableBeanFactory:其繼承了 HierarchicalBeanFactory 和 SingletonBeanRegistry 這兩個接口,其提供了很多方法,如:定義類加載器、類型轉化、屬性編輯器、注冊依賴 Bean 、銷毀 bean 等,且該接口被大多數的容器繼承、實現。
- ConfigurableListableBeanFactory:這個接口繼承了 ListableBeanFactory、 AutowireCapableBeanFactory、ConfigurableBeanFactory,自身主要提供用於分析和修改 bean 定義以及預先實例化單例 Bean 的方法。
最后是核心容器:
- DefaultListableBeanFactory:它實現了以上所有的接口,在 BeanFactory 體系中可以作為一個獨立的容器使用。
BeanFactory 大致的繼承關系如下:
其實以前常用的容器是 XmlBeanFactory ,它是 DefaultListableBeanFactory 的實現類,現已被廢除,原因還未找到,有知道的小伙伴,可在底下留言告知。
但我們基本不單獨使用 BeanFactory ,而是直接使用 ApplicationContext ,因為 ApplicationContext 包含了 BeanFactory。
2.2 ApplicationContext
上面說過 ApplicationContext 是 BeanFactory 子類,它不僅包含 BeanFactory 所有功能,還對其進行了擴展,而我們喜歡將 ApplicationContext 稱為應用上下文,因為容器只是它的基本功能。
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
// 返回此應用程序上下文的唯一ID
@Nullable
String getId();
// 返回此上下文所屬的應用程序名稱
String getApplicationName();
// 返回應用上下文具像化的類名
String getDisplayName();
// 返回第一次加載此上下文時的時間戳
long getStartupDate();
// 獲取父級應用上下文
@Nullable
ApplicationContext getParent();
// 將 AutowireCapableBeanFactory 接口暴露給外部使用
AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException;
}
ApplicationContext 自身提供的方法非常簡單,但它繼承了六個接口,來擴展自身功能:
- EnvironmentCapable:獲取 Environment。
- ListableBeanFactory、HierarchicalBeanFactory:這是 BeanFactory 體系接口,分別提供 Bean 迭代和訪問父容器的功能。
- MessageSource:支持國際化功能。
- ApplicationEventPublisher:應用事件發布器,封裝事件發布功能的接口。
- ResourcePatternResolver:該接口繼承至 ResourceLoader ,作用是加載多個 Resource。
ApplicationContext 同樣提供了非常多的實現類,其又可細分為兩大類, ConfigurableApplicationContext 和 WebApplicationContext。
2.2.1 ConfigurableApplicationContext
該接口是比較重要的一個接口,幾乎所有的應用上下文都實現了該接口。該接口在ApplicationContext的基礎上提供了配置應用上下文的能力,此外提供了生命周期的控制能力。
public interface ConfigurableApplicationContext extends ApplicationContext, Lifecycle, Closeable {
// 應用上下文配置時,這些符號用於分割多個配置路徑
String CONFIG_LOCATION_DELIMITERS = ",; \t\n";
// BeanFactory中,ConversionService類所對應的bean的名字。如果沒有此類的實例的話嗎,則使用默認的轉換規則
String CONVERSION_SERVICE_BEAN_NAME = "conversionService";
//LoadTimeWaver類所對應的Bean在容器中的名字。如果提供了該實例,上下文會使用臨時的 ClassLoader ,這樣,LoadTimeWaver就可以使用bean確切的類型了
String LOAD_TIME_WEAVER_BEAN_NAME = "loadTimeWeaver";
// Environment 類在容器中實例的名字
String ENVIRONMENT_BEAN_NAME = "environment";
// System 系統變量在容器中對應的Bean的名字
String SYSTEM_PROPERTIES_BEAN_NAME = "systemProperties";
// System 環境變量在容器中對應的Bean的名字
String SYSTEM_ENVIRONMENT_BEAN_NAME = "systemEnvironment";
// 設置容器的唯一ID
void setId(String id);
// 設置此容器的父容器
void setParent(@Nullable ApplicationContext parent);
// 設置容器的 Environment 變量
void setEnvironment(ConfigurableEnvironment environment);
// 以 ConfigurableEnvironment 的形式返回此容器的環境變量。以使用戶更好的進行配置
@Override
ConfigurableEnvironment getEnvironment();
// 此方法一般在讀取應用上下文配置的時候調用,用以向此容器中增加BeanFactoryPostProcessor。增加的Processor會在容器refresh的時候使用。
void addBeanFactoryPostProcessor(BeanFactoryPostProcessor postProcessor);
// 向容器增加一個 ApplicationListener,增加的 Listener 用於發布上下文事件,如 refresh 和 shutdown 等
void addApplicationListener(ApplicationListener<?> listener);
// 向容器中注入給定的 Protocol resolver
void addProtocolResolver(ProtocolResolver resolver);
// 這是初始化方法,因此如果調用此方法失敗的情況下,要將其已經創建的 Bean 銷毀。
// 換句話說,調用此方法以后,要么所有的Bean都實例化好了,要么就一個都沒有實例化
void refresh() throws BeansException, IllegalStateException;
// 向JVM注冊一個回調函數,用以在JVM關閉時,銷毀此應用上下文
void registerShutdownHook();
// 關閉此應用上下文,釋放其所占有的所有資源和鎖。並銷毀其所有創建好的 singleton Beans
@Override
void close();
// 檢測此 FactoryBean 是否被啟動過
boolean isActive();
// 返回此應用上下文的容器。
// 千萬不要使用此方法來對 BeanFactory 生成的 Bean 做后置處理,因為單例 Bean 在此之前已經生成。
// 這種情況下應該使用 BeanFactoryPostProcessor 來在 Bean 生成之前對其進行處理
ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
}
該接口下又有幾個重要的實現類:
- AbstractApplicationContext:這是個抽象類,僅實現了公共的上下文特性。這個抽象類使用了模板方法設計模式,需要具體的實現類去實現這些抽象的方法。
- GenericApplicationContext:該類繼承自 AbstractApplicationContext,是為通用目的設計的,它能加載各種配置文件,例如 xml,properties 等等。它的內部持有一個 DefaultListableBeanFactory 的實例,實現了 BeanDefinitionRegistry 接口,以便允許向其應用任何 bean 的定義的讀取器。
- AnnotationConfigApplicationContext:該類繼承自 GenericApplicationContext ,提供了注解配置(例如:@Configuration、@Component等)和類路徑掃描(scan方法)的支持。
2.2.2 WebApplicationContext
該接口是專門為 Web 應用准備的,其允許從相對於 Web 根目錄的路徑中裝載配置文件完成初始化。
public interface WebApplicationContext extends ApplicationContext {
// 整個 Web 應用上下文是作為屬性放置在 ServletContext 中的,該常量就是應用上下文在 ServletContext 屬性列表中的 key
String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
// 定義了三個作用域的名稱
String SCOPE_REQUEST = "request";
String SCOPE_SESSION = "session";
String SCOPE_APPLICATION = "application";
// 在工廠中的 bean 名稱
String SERVLET_CONTEXT_BEAN_NAME = "servletContext";
// ServletContext 初始化參數名稱
String CONTEXT_PARAMETERS_BEAN_NAME = "contextParameters";
// 在工廠中 ServletContext 屬性值環境bean的名稱
String CONTEXT_ATTRIBUTES_BEAN_NAME = "contextAttributes";
// 用來獲取 ServletContext 對象
@Nullable
ServletContext getServletContext();
}
該接口的核心實現類有:
- ConfigurableWebApplicationContext:該接口同時繼承了 WebApplicationContext 和 ConfigurableApplicationContext,提供了 Web 應用上下文的可配置的能力。
- GenericWebApplicationContext:該類繼承自 GenericApplicationContext,實現了 ConfigurableWebApplicationContext。
- XmlWebApplicationContext:該上下文是使用 Xml 配置文件的方式,不過是在 Web 環境中使用的。
- AnnotationConfigServletWebServerApplicationContext:該類是被 SpringBoot 擴展而來的,SpringBoot 使用的就是該上下文。
2.3 差異對比
從上面可以看出 BeanFactory 是 Sping 框架的基礎接口,一般是面向 Spring 本身;而 ApplicationContext 是以 BeanFactory 為基礎進行綜合能力擴展,用於滿足大型業務應用的創建, ApplicationContext 一般面向使用 Sping 框架的開發者。幾乎所有的應用場合我們都是直接使用 ApplicationContet 而非底層的 BeanFactory。
下表列出了BeanFactory 和 ApplicationContext 接口和實現所提供的功能:
功能 / 特點 | BeanFactory | ApplicationContext |
---|---|---|
Bean 實例化/裝配 | 有 | 有 |
BeanPostProcessor 自動注冊 | 沒有 | 有 |
BeanFactoryPostProcessor 自動注冊 | 沒有 | 有 |
MessageSource 便捷訪問(針對i18n) | 沒有 | 有 |
ApplicationEvent 發布 | 沒有 | 有 |
兩者還有一個區別是:
- ApplicationContext 在容器啟動時,一次性創建了所有的 Bean。
- BeanFactory 在容器啟動時,並未創建 Bean,直到第一次訪問某個 Bean 時才創建目標 Bean。
3、ApplicationContext 准備啟動
在真實環境中,一般通過集成 SSM 或者 SpringBoot 來自動創建 ApplicationContext。
先從 SSM 開始
1、在 web.xml 配置監聽器
<!-- spring監聽器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
2、容器啟動時會調用 ContextLoaderListener 中的 contextInitialized 方法。
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
...
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
...
}
3、調用父類的 initWebApplicationContext 方法,在該方法中創建、啟動上下文。
public class ContextLoader {
...
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
...
try {
if (this.context == null) {
// 通過 createWebApplicationContext 方法創建上下文,默認創建 XmlWebApplicationContext
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
...
// 在該方法中調用上下文的 refresh 方法,refresh 就是啟動上下文的入口
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
...
}
...
}
...
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
...
wac.refresh();
}
...
}
SpringBoot 啟動 ApplicationContext
1、從啟動類開始
@SpringBootApplication
public class DiveInSpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(DiveInSpringBootApplication.class, args);
}
}
2、找到 SpringApplication 中,最后重載的 run 方法
public ConfigurableApplicationContext run(String... args) {
...
ConfigurableApplicationContext context = null;
...
try {
...
// 通過 createApplicationContext 方法創建上下文,根據 Web 環境不同創建的上下文也不同
context = createApplicationContext();
...
// 該方法用於啟動上下文
refreshContext(context);
...
}
catch (Throwable ex) {
...
}
context = createApplicationContext();
...
}
3、進入 refreshContext 方法,里面調用了 refresh 方法
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
4、這里,最終也是調用 ApplicationContext 的 refresh 方法來啟動上下文
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
((AbstractApplicationContext) applicationContext).refresh();
}
注:這里主要討論容器的創建和啟動部分,所以省略了其他部分的代碼。其中 SpringBoot 啟動上下文在前幾篇 《SpringBoot系列》文章有詳細介紹,感興趣的伙伴可自行查閱
可以看到雖然 SSM 和 SpringBoot 的上下文對象不同,但最終都是調用上下文中的 refresh 方法來啟動。該方法是 ApplicationContext 的核心,如 Bean 注冊、注入、解析 XML 、解析注解等是從該方法開始,其內部實現大致如下:
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 1. 初始化 refresh 的上下文環境,就是記錄下容器的啟動時間、標記已啟動狀態、處理配置文件中的占位符
prepareRefresh();
// 2. 初始化 BeanFactory,加載並解析配置
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
/* ---至此,已經完成了簡單容器的所有功能,下面開始對簡單容器進行增強--- */
// 3. 對 BeanFactory 進行功能增強,如設置BeanFactory的類加載器,添加幾個 BeanPostProcessor,手動注冊幾個特殊的 bean
prepareBeanFactory(beanFactory);
try {
// 4. 后置處理 beanFactory,交由子類實現
postProcessBeanFactory(beanFactory);
// 5. 調用已注冊的 BeanFactoryPostProcessor
invokeBeanFactoryPostProcessors(beanFactory);
// 6. 注冊 BeanPostProcessor,僅僅是注冊,調用在getBean的時候
registerBeanPostProcessors(beanFactory);
// 7. 初始化國際化資源
initMessageSource();
// 8. 初始化事件廣播器
initApplicationEventMulticaster();
// 9. 留給子類實現的模板方法
onRefresh();
// 10. 注冊事件監聽器
registerListeners();
// 11. 實例化所有非延遲加載的單例
finishBeanFactoryInitialization(beanFactory);
// 12. 完成刷新過程,發布應用事件
finishRefresh();
} catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + ex);
}
// 13.銷毀已經初始化的 singleton 的 Beans,以免有些 bean 會一直占用資源
this.destroyBeans();
// Reset 'active' flag.
this.cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
} finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
this.resetCommonCaches();
}
}
}
接下來的文章,將對 refresh 方法中的各部分進行詳細討論。
4、總結
最后來做個整體的總結。文章從 Spring 的整體架構開始討論,整體分為五個模塊:核心容器、AOP、Web、Data 數據訪問、Test模塊,而核心容器又是 Spring 的基礎。容器的作用是什么?提供 IOC/DI 功能;怎么實現的?其核心是 BeanFactory 和 ApplicationContext ,一般使用 ApplicationContext ,其包含了 BeanFactory 的所有功能,並對其進行擴展。在 SSM 和 SpringBoot 中自動創建 ApplicationContext 並調用它的 refresh 方法進行啟動,它的 refresh 就是實現容器一系列功能的入口。
以上就是本章內容,如果文章中有錯誤或者需要補充的請及時提出,本人感激不盡。
參考:
https://www.cnblogs.com/09120912zhang/p/7746252.html
https://docs.spring.io/spring/docs/5.2.3.RELEASE/spring-framework-reference/core.html#context-introduction
https://www.jianshu.com/p/2854d8984dfc
https://blog.csdn.net/baidu_36327010/article/details/87983262
https://www.cnblogs.com/zhangfengxian/p/11192054.html
https://www.cnblogs.com/sharpest/p/10885820.html