目錄html
在上篇文章中,和你們一塊兒討論了 Spring 的總體架構,其大體分爲五個模塊:核心容器、AOP、Web、Data 數據訪問、Test模塊。其中核心容器是 Spring 的核心部分,其它模塊也都依賴於該容器。這裏和就你們一塊兒深刻討論 Spring 的容器,它的做用是什麼、怎麼實現的。java
容器顧名思義就是用來裝東西的,裝的是什麼?裝的是 Bean。web
Bean 是 Spring 的基本單位,在基於 Spring 的 Java EE 應用中,全部的組件都被當成 Bean 處理,包括數據源、Hibernate 的 SessionFactory、事務管理器等。在 Spring 中,Bean 是一個很是廣義的概念,任何 Java 對象、Java 組件都被當成 Bean 處理。spring
那容器僅僅是用來保存 Bean 這麼簡單麼?不是。設計模式
當咱們須要使用某個 Bean 時,容器會自動幫咱們建立,並在適當時銷燬。還有一種狀況,當某個 Bean 中需建立另外一個 Bean 時,也就是 Bean 之間有依賴關係,這種依賴的 Bean 也是由容器自動建立。在外界有一個標準的名詞,前者稱呼爲 IOC,也就是控制反轉,後者稱呼爲 DI,也就是依賴注入。安全
IOC/DIsession
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 注入給它。app
IOC 和 DI 其實歸根結底實現的功能是相同的,只是一樣的功能站在不一樣的角度來闡述罷了,不過咱們一般喜歡將這兩個概念統稱爲 IOC。固然,在真實場景中,交由 Spring 容器建立的 Bean 泛指在應用程序中的表現層、業務層、持久層等各層對應的 Bean,如 Controller、Service 等;進行數據交互的模型,如 DTO、VO 等就不需交由 Spring 來建立。框架
因此,容器本質上能夠也能夠看做是 Bean 工廠,該工廠管理 Bean 的生命週期,以及 Bean 之間的依賴關係。外界也將 Spring 容器稱爲 IOC 容器。固然,這裏容器僅僅是 Spring 的抽象概念,代碼中將其具象化爲 BeanFactory 或 ApplicationContext,容器功能也由具象化的類進行處理。
容器的實現類並非惟一的,Spring 框架提供了多個容器的實現,這些容器分爲兩套體系:一套是早期的 BeanFactory 體系;還有一套是如今經常使用的 ApplicationContext,也可稱爲應用上下文,它繼承了 BeanFactory,它除了有 BeanFactory 的功能外
,還提供了其餘服務,例如事務和 AOP 服務、國際化(il8n)的消息源以及應用程序事件處理等企業級的服務。
說到這,就不得不說 Spring 的兩種配置方式,在早期都是 XML 配置文件的方式,而如今使用的是註解配置的方式。BeanFactory 體系的容器通常用來處理 XML 配置文件的方式,而 ApplicationContext 體系則均可以處理。
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 體系中經常使用的實現類有:
以上三個是 BeanFactory 的直系親屬,這個三個直系親屬下面又派生了兩個複雜的容器:
最後是核心容器:
BeanFactory 大體的繼承關係以下:
其實之前經常使用的容器是 XmlBeanFactory ,它是 DefaultListableBeanFactory 的實現類,現已被廢除,緣由還未找到,有知道的小夥伴,可在底下留言告知。
但咱們基本不單獨使用 BeanFactory ,而是直接使用 ApplicationContext ,由於 ApplicationContext 包含了 BeanFactory。
上面說過 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 自身提供的方法很是簡單,但它繼承了六個接口,來擴展自身功能:
ApplicationContext 一樣提供了很是多的實現類,其又可細分爲兩大類, ConfigurableApplicationContext 和 WebApplicationContext。
該接口是比較重要的一個接口,幾乎全部的應用上下文都實現了該接口。該接口在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; }
該接口下又有幾個重要的實現類:
該接口是專門爲 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(); }
該接口的核心實現類有:
從上面能夠看出 BeanFactory 是 Sping 框架的基礎接口,通常是面向 Spring 自己;而 ApplicationContext 是以 BeanFactory 爲基礎進行綜合能力擴展,用於知足大型業務應用的建立, ApplicationContext 通常面向使用 Sping 框架的開發者。幾乎全部的應用場合咱們都是直接使用 ApplicationContet 而非底層的 BeanFactory。
下表列出了BeanFactory 和 ApplicationContext 接口和實現所提供的功能:
功能 / 特色 | BeanFactory | ApplicationContext |
---|---|---|
Bean 實例化/裝配 | 有 | 有 |
BeanPostProcessor 自動註冊 | 沒有 | 有 |
BeanFactoryPostProcessor 自動註冊 | 沒有 | 有 |
MessageSource 便捷訪問(針對i18n) | 沒有 | 有 |
ApplicationEvent 發佈 | 沒有 | 有 |
二者還有一個區別是:
在真實環境中,通常經過集成 SSM 或者 SpringBoot 來自動建立 ApplicationContext。
先從 SSM 開始
一、在 web.xml 配置監聽器
<!-- spring監聽器 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
二、容器啓動時會調用 ContextLoaderListener 中的 contextInitialized 方法。
public class ContextLoaderListener extends ContextLoader implements ServletContextListener { ... @Override public void contextInitialized(ServletContextEvent event) { initWebApplicationContext(event.getServletContext()); } ... }
三、調用父類的 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
一、從啓動類開始
@SpringBootApplication public class DiveInSpringBootApplication { public static void main(String[] args) { SpringApplication.run(DiveInSpringBootApplication.class, args); } }
二、找到 SpringApplication 中,最後重載的 run 方法
public ConfigurableApplicationContext run(String... args) { ... ConfigurableApplicationContext context = null; ... try { ... // 經過 createApplicationContext 方法建立上下文,根據 Web 環境不一樣建立的上下文也不一樣 context = createApplicationContext(); ... // 該方法用於啓動上下文 refreshContext(context); ... } catch (Throwable ex) { ... } context = createApplicationContext(); ... }
三、進入 refreshContext 方法,裏面調用了 refresh 方法
private void refreshContext(ConfigurableApplicationContext context) { refresh(context); if (this.registerShutdownHook) { try { context.registerShutdownHook(); } catch (AccessControlException ex) { // Not allowed in some environments. } } }
四、這裏,最終也是調用 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 方法中的各部分進行詳細討論。
最後來作個總體的總結。文章從 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