
《SpringBoot 3入門與應用實戰》第 4 章 IOC 容器中的 Bean 閱讀筆記 6Spring Framework 中的 IOC 容器內部設計極其精巧提供了強大且實用的功能和機制通過加載解析 XML 配置文件、注解配置類實現按照配置內容工作。4.1 BeanFactory第2章講到BeanFactory 是基礎 IOC 容器ApplicationContext 包含 BeanFactory 的所有功能且擴展了更多實用特性。BeanFactory 作為Spring Framework 中的基礎 IOC 容器它的設計相對簡單且純粹但也是最核心的。4.1.1 BeanFactory 接口系列作為 Spring Framework 中的頂級容器接口BeanFactory 的設計是簡單純粹的。下面將對 BeanFactory 的核心特性進行分析。1.核心特性BeanFactory 接口直接擴展的 3 個子接口可以總結出 BeanFactory 的特性層次性、可列舉、支持自動注入。層次性BeanFactory本身支持父子結構如果在一個BeanFactory中沒有找到想要的Bean則會向上查找父BeanFactory。可列舉ListableBeanFactory 支持將容器中所有的 Bean一一列舉?。支持自動注入BeanFactory 中定義了 getBean 方法用于依賴查找其子接口 AutowireCapableBeanFactory 中對依賴注入予以支持。2.可讀與可寫在 HierarchicalBeanFactory 的下方有一個接口ConfigurableBeanFactory。Spring Framework 中對于核心 API 的命名有非常強的規律性當我們看到類名的前面帶有 “Configurable” 時意味著這個接口的行為有“寫”的動作不帶 Configurable 前綴的接口只有“讀”的動作。可寫與可讀的概念在學習 Java SE 的面向對象編程中一個類的屬性設置為 private 后提供 getter 方法則意味著該屬性可讀提供 setter 方法則意味著該屬性可寫。同樣Spring Framework 中的這些 BeanFactory包括后面的 ApplicationContext 中都有這樣的設計普通的 BeanFactory 只有 get 相關的操作而 “Configurable” 開頭的 BeanFactory 或者 ApplicationContext 就具有了 set 相關的操作。4.1.2 BeanFactory 的實現類有關 BeanFactory 實現類的簡單講解我們重點關注兩個抽象子類和一個落地實現類。抽象實現 AbstractBeanFactory、一個底層實現DefaultListableBeanFactory 以及介于兩者之間的 AbstractAutowireCapableBeanFactory。1.核心特性簡單描述 BeanFactory 的實現類中設計的功能和機制。加載 Bean 的信息BeanFactory 利用不同的加載機制XML或注解配置類等加載 Bean 的信息。作用域BeanFactory 的內部能分辨 Bean 的作用域是單實例還是原型等。創建 Bean BeanFactory 具備創建 Bean 的能力以及屬性賦值、依賴注入、生命周期回調。生命周期管理BeanFactory 負責控制 Bean 在創建階段的生命周期。2.BeanFactory 的約定在 BeanFactory 的設計之初它制定了 Bean 創建的兩大環節和幾個核心步驟。(1)加載 Bean 的注冊信息從 XML 配置文件、注解配置類、模式注解組件掃描等方式得知哪些 Bean 需要被注入到 IOC 容器。(2)創建并初始化 Bean該環節包含 2 個步驟分別是 Bean 的創建、Bean的初始化。4.2 ApplicationContextBeanFactory 僅實現了 IOC 容器中最核心的容器部分而 ApplicationContext 提供了更多可以用于實際應用開發的強大功能特性。打一個比方如果說 BeanFactory 是 IOC 容器的“芯片”?那么 ApplicationContext 則是一臺完整的主機。4.2.1 ApplicationContext 接口系列借助 IDEA可以展示 ApplicationContext 接口的父接口、擴展接口之間的關系如圖所示。從圖中可以看到ApplicationContext 除了繼承 BeanFactory 接口還額外繼承了幾個功能性接口這些接口共同組成了 ApplicationContext 擴展的幾個核心特性。簡單概括 ApplicationContext 的功能如下。訪問 Bean 的能力ApplicationContext 繼承自 BeanFactory擁有 BeanFactory 的所有能力。層級關系ApplicationContext與BeanFactory一樣也存在層級關系。加載文件資源ApplicationContext 可以從類路徑 (classpath)、文件系統等來源中加載文件。事件發布與驅動機制ApplicationContext 設計了基于觀察者模式的事件驅動機制可以實現事件的發布與廣播。國際化支持ApplicationContext可以針對不同國家/地區的訪問提供符合用戶閱讀習慣語言的頁面和數據。配置屬性ApplicationContext內部有一塊專有區域用于管理和保存程序運行時所使用的配置屬性。此外ApplicationContext 還有一個子接口 ConfigurableApplicationContext它的設計與 ConfigurableBeanFactory 之于 BeanFactory 完全一致。ConfigurableApplicationContext 接口中有兩個新增的重要方法分別是 refresh 和 close代表初始化刷新和關閉 IOC 容器。4.2.2 ApplicationContext 的實現類我們先將 Spring Framework 中 ApplicationContext 的重要實現類都羅列出來如圖所示。根據加載配置源的不同可以將 ApplicationContext 分為兩個大類基于 XML 配置文件驅動的實現代表類 ClassPathXmlApplicationContext和基于注解驅動的實現代表類AnnotationConfigApplicationContext?。無論是哪種實現它們都來自同一個父類 AbstractApplicationContext。AbstractApplicationContext 中定義和實現了絕大部分應用上下文的特性和功能包含生命周期實現、容器初始化和銷毀的邏輯、特殊類型 Bean 的處理等。當下 Spring Framework 的版本已發展至 6.0Spring Boot 早已成為企業項目開發的必備基礎框架而 Spring Boot 推薦使用注解驅動配置的方式相應地放棄了基于 XML 配置文件的實現方式所以本書主要側重注解驅動的 IOC 容器講解。此外ApplicationContext 在 Web 應用下還有對應的擴展這部分內容會在 WebMvc 開發中講解。4.3 事件驅動與監聽器下面我們學習一些 IOC 容器支持的功能特性這些特性在平時的開發中不一定全部得到使用但想熟練掌握 Spring Framework了解并掌握這些知識點非常重要。4.3.1 觀察者模式本節講解事件驅動機制。對于事件與監聽器可能讀者會聯想到觀察者模式。觀察者模式也被稱為發布訂閱模式也有人叫它 “監聽器模式”?它是 GoF 23 設計模式中行為型模式之一。觀察者模式關注的點是某一個對象被修改 / 做出某些反應 / 發布一個信息等會自動通知依賴它的對象訂閱者?。觀察者模式的三大核心是觀察者、被觀察的主題、訂閱者。觀察者 (Observer) 需要綁定要通知的訂閱者 (Subscriber)并且要觀察指定的主題 (Subject)。4.3.2 Spring 中的觀察者模式Spring Framework 中能夠體現觀察者模式的特性就是事件驅動和監聽器。在觀察者模式中監聽器充當訂閱者監聽特定的事件事件源充當被觀察的主題用來發布事件IOC 容器本身也是事件廣播器可以理解為觀察者。Spring Framework 中事件驅動的核心可以劃分為 4 部分事件源、事件、廣播器和監聽器。(1) 事件源發布事件的對象。(2) 事件事件源發布的信息/做出的動作。(3) 廣播器廣播事件給監聽器的對象 (ApplicationContext)。ApplicationContext 接口中實現了 ApplicationEventPublisher 接口具備事件廣播器發布事件的能力。ApplicationEventMulticaster 組合了所有的監聽器具備事件廣播器廣播事件的能力。(4) 監聽器監聽事件的對象。4.3.3 事件與監聽器實踐下面通過一個最簡單的示例體會 Spring Framework 中事件與監聽器的使用。1.編寫監聽器Spring Framework 中內置的監聽器接口是 ApplicationListener它附帶了一個泛型可以指定監聽器“感興趣”的具體事件只有泛型指定類型的事件才會被監聽響應?。如果我們要自定義監聽器則只需實現 ApplicationListener 接口。為了快速體會事件和監聽器的功能本節先介紹兩個事件ContextRefreshedEvent 和 ContextClosedEvent它們分別代表容器刷新初始化完畢和即將關閉。下面編寫一個監聽器 ContextRefreshedApplicationListener并指定其監聽 ContextRefreshedEvent 事件。注意編寫完畢的監聽器需要注冊到 IOC 容器才可以生效在本示例中使用模式注解 組件掃描的方式加載監聽器。packagecom.yangjunbo.bean.event.examplea;importorg.springframework.context.ApplicationListener;importorg.springframework.context.event.ContextRefreshedEvent;importorg.springframework.stereotype.Component;ComponentpublicclassContextRefreshedApplicationListenerimplementsApplicationListenerContextRefreshedEvent{OverridepublicvoidonApplicationEvent(ContextRefreshedEventevent){System.out.println(ContextRefreshedApplicationListener監聽到ContextRefreshedEvent事件);}}2.編寫啟動類隨后編寫一個測試啟動類 QuickstartListenerApplication使用組件掃描的方式驅動 IOC 容器并在 IOC 容器初始化完畢后關閉 IOC 容器。調用創建 IOC 容器和 close 方法這兩個動作可以在 IOC 容器的內部分別產生 ContextRefreshedEvent 和 ContextClosedEvent 事件從而觸發相應的監聽器邏輯。運行 main 方法控制臺會根據流程依次打印如下信息證明監聽器已經成功執行。以上就是 Spring Framework 中事件與監聽器的最簡單使用。packagecom.yangjunbo.bean.event.examplea;importorg.springframework.context.annotation.AnnotationConfigApplicationContext;publicclassQuickstartListenerApplication{publicstaticvoidmain(String[]args)throwsException{System.out.println(準備初始化IOC容器。。。);AnnotationConfigApplicationContextctxnewAnnotationConfigApplicationContext(com.yangjunbo.bean.event.examplea);System.out.println(IOC容器初始化完成。。。);ctx.close();System.out.println(IOC容器關閉。。。);}}3.注解式監聽器編寫監聽器的方式除了通過實現 ApplicationListener 接口還可以使用 EventListener 注解注冊監聽器。使用注解式監聽器時定義監聽器的類不再需要實現任何接口而是直接在需要做出事件響應的方法上標注 EventListener 注解即可。packagecom.yangjunbo.bean.event.examplea;importorg.springframework.context.event.ContextClosedEvent;importorg.springframework.context.event.EventListener;importorg.springframework.stereotype.Component;ComponentpublicclassContextClosedApplicationListener{EventListenerpublicvoidonContextClosedEvent(ContextClosedEventevent){System.out.println(ContextClosedApplicationListener監聽到ContextClosedEvent事件);}}之后重新運行 QuickstartListenerApplication 的 main 方法控制臺可以打印出 ContextClosedApplicationListener 監聽事件的響應證明注解式監聽器同樣正確生效。由以上兩種監聽器的代碼示例可以總結出以下幾個結論ApplicationListener 會在容器初始化階段就準備就緒在容器銷毀時一同銷毀。ApplicationListener 也是 IOC 容器中的普通Bean。IOC容器中有一些內置事件供我們監聽。4.3.4 Spring 的內置事件在 Spring Framework 中已經內置了一些事件。1.ApplicationEventApplicationEvent 是 Spring Framework 中事件模型的抽象它本是一個抽象類里面沒有定義重要的內容只有事件發生時的時間戳。ApplicationEvent 繼承自 JDK 中原生的觀察者模式的事件模型。ApplicationEvent 表示基礎事件它被設計為抽象的因為直接發布基礎事件沒有意義。2.ApplicationContextEventApplicationContextEvent 繼承自 ApplicationEvent由 ApplicationContextEvent 的源碼可知它在構造時會將 IOC 容器 ApplicationContext 一并傳入這就意味著事件發生時可以通過監聽器直接取到 ApplicationContext。3.ContextRefreshedEvent 和 ContextClosedEventContextRefreshedEvent 與 ContextClosedEvent 是成對的事件定義分別對應 IOC 容器刷新完畢但尚未啟動完成以及 IOC 容器已經關閉但尚未銷毀所有 Bean。對于上述兩個事件發生時機隨著后續學習的推進會對 IOC 容器的生命周期有更全面的認識。4.ContextStartedEvent 和 ContextStoppedEventContextStartedEvent 與 ContextStoppedEvent 事件的觸發事件跟Bean 的生命周期擴展點Lifecycle 接口相關。在第 3 章的最后講解了一個 Lifecycle 接口當時講了一個要點Lifecycle 的觸發時機容器調用 start 和 stop 方法時觸發。事件名稱觸發時機關鍵說明ContextRefreshedEvent容器refresh()完成時所有單例 Bean 已創建、屬性注入、初始化回調如PostConstruct均已完成。ContextStartedEvent容器start()完成時在ContextRefreshedEvent之后觸發。此時所有實現了Lifecycle接口的 Bean 的start()方法已被回調。ContextClosedEvent容器close()執行時IOC 容器已關閉但此時單例 Bean 尚未被銷毀。ContextStoppedEvent容器stop()完成時在ContextClosedEvent之后觸發。此時所有實現了Lifecycle接口的 Bean 的stop()方法已被回調但單例 Bean 仍未被銷毀。4.3.5 自定義事件開發下面考慮一個 Spring Framework 內置事件實際的業務場景論壇應用中當新用戶注冊成功后會同時發送短信、郵件、站內信通知用戶注冊成功。在這個場景中如果后續再添加一個 “發放論壇積分” 邏輯如何實現才能保證代碼編寫得更優雅、更易維護毫無疑問選擇使用合適的設計模式有助于提高代碼的可維護性。在這個場景中當用戶注冊成功后廣播一個 “用戶注冊成功” 事件將用戶信息隨事件廣播出去發送短信、郵件、站內信的監聽器監聽到注冊成功的事件后會分別執行不同形式的通知動作即便是后期再添加 “發放論壇積分” 邏輯也只需要再注冊一個對應的監聽器到 IOC 容器不會產生很強的代碼耦合。下面就來演示上述的場景的代碼實現。1.自定義用戶注冊成功事件Spring Framework 中的自定義事件的方式是通過繼承抽象類 ApplicationEvent 實現的定義一個簡單的用戶注冊成功事件RegisterSuccessEvent所有自定義的事件類都需要聲明一個帶有一個參數的構造方法。packagecom.yangjunbo.bean.event.exampleb;importorg.springframework.context.ApplicationEvent;/** * 注冊成功的事件 */publicclassRegisterSuccessEventextendsApplicationEvent{publicRegisterSuccessEvent(Objectsource){super(source);}}2.編寫監聽器使用兩種不同的方式編寫三個監聽器分別代表短信推送、郵件推送、站內信推送。在一個類中可以同時出現多個被 EventListener 注解標注的方法即一個類中可以注冊多個注解式事件監聽器。packagecom.yangjunbo.bean.event.exampleb;importorg.springframework.context.event.EventListener;importorg.springframework.stereotype.Component;ComponentpublicclassSenderListener{EventListenerpublicvoidsendMessage(RegisterSuccessEventevent){System.out.println(監聽到用戶注冊成功發送站內信。。。);}EventListenerpublicvoidsendEmail(RegisterSuccessEventevent){System.out.println(監聽到用戶注冊成功發送郵件中。。。);}}packagecom.yangjunbo.bean.event.exampleb;importorg.springframework.context.ApplicationListener;importorg.springframework.stereotype.Component;ComponentpublicclassSmsSenderListenerimplementsApplicationListenerRegisterSuccessEvent{OverridepublicvoidonApplicationEvent(RegisterSuccessEventevent){System.out.println(監聽到用戶注冊成功發送短信。。。);}}3.編寫注冊邏輯業務層只有事件和監聽器還不夠還需要有一個事件源作為事件發布器在應用上下文中發布事件。我們可以編寫一個 RegisterService 類并注入 Spring Framework 內置的事件發布器 ApplicationEventPublisher注入的方式可以選用注解Autowired 自動注入也可以借助 ApplicationEventPublisherAware 實現回調注入。packagecom.yangjunbo.bean.event.exampleb;importorg.springframework.context.ApplicationEventPublisher;importorg.springframework.context.ApplicationEventPublisherAware;importorg.springframework.stereotype.Service;ServicepublicclassRegisterServiceimplementsApplicationEventPublisherAware{ApplicationEventPublisherpublisher;OverridepublicvoidsetApplicationEventPublisher(ApplicationEventPublisherpublisher){this.publisherpublisher;}publicvoidregister(Stringusername){// 用戶注冊的動作。。。System.out.println(username注冊成功。。。);// 發布事件publisher.publishEvent(newRegisterSuccessEvent(username));}}4.編寫測試啟動類接下來編寫測試啟動類 RegisterEventApplication。IOC 容器初始化完畢后獲取 RegisterService隨后調用 register 方法模擬一次用戶注冊的發生。運行 main 方法控制臺打印出注冊動作以及監聽器的觸發反應。packagecom.yangjunbo.bean.event.exampleb;importorg.springframework.context.annotation.AnnotationConfigApplicationContext;publicclassRegisterEventApplication{publicstaticvoidmain(String[]args)throwsException{AnnotationConfigApplicationContextctxnewAnnotationConfigApplicationContext(com.yangjunbo.bean.event.exampleb);RegisterServiceregisterServicectx.getBean(RegisterService.class);registerService.register(張三);}}5.調整監聽器的觸發順序如果業務需要調整為先發送站內信后發送郵件這就需要我們定制監聽器的執行順序。Spring Framework 提供了兩種排序的定制方式(1)實現 Ordered 接口并重寫 getOrder 方法傳入排序值(2)在需要排序的類/方法上標注 Order 注解并傳入排序值默認的排序值為 Ordered.LOWEST_PRECEDENCE即 Integer.MAX_VALUE代表最靠后?。按照這個規則我們可以在 SenderListener 的 sendMessage 方法上標注 Order(0)重新運行啟動類 RegisterEventApplication 的 main 方法觀察控制臺的打印內容可以發現觸發順序被成功修改。將 sendMessage 方法上 Order 注解的 value 指定為 Integer.MAX_VALUE - 1隨后再次重新運行 RegisterEventApplication 的 main 方法發現運行結果發送站內信在發送郵件中之前證明默認的排序值是 Integer.MAX_VALUE。6.最佳實踐通過使用事件驅動機制可以很好地解決單體應用中業務邏輯之間的解耦。實際項目開發中自定義事件的使用場景恰恰就如本節演示的這樣針對一個基礎業務邏輯牽動多個關聯的子業務。可以在基礎業務邏輯發生時向 IOC 容器廣播一個特定的事件并將關聯的核心業務數據放入事件中隨后負責監聽該事件的監聽器就可得以回調從而從事件體中獲取業務數據進而執行相應的子業務邏輯。