Spring5底層原理之BeanFactory與ApplicationContext

目錄

BeanFactory與ApplicationContext

BeanFactory

ApplicationContext

容器實現

BeanFactory實現

ApplicationContext實現

ClassPathXmlApplicationContext的實現

AnnotationConfigApplicationContext的實現

AnnotationConfigServletWebServerApplicationContext的實現


BeanFactory與ApplicationContext

BeanFactory

BeanFactory是ApplicationContext的父接口。是Spring的核心容器,ApplicationContext的主要的方法均是調用BeanFactory的方法。比如說下面獲取bean對象案例中

@SpringBootApplication
public class CodeApplication {public static void main(String[] args) {ConfigurableApplicationContext ctx = SpringApplication.run(CodeApplication.class, args);ctx.getBean("abc");}
}

通過Ctrl+alt+b鍵可以看到具體的實現方法如下,先獲取BeanFactory對象后調用BeanFactory對象的getBean()方法。

	@Overridepublic Object getBean(String name) throws BeansException {assertBeanFactoryActive();return getBeanFactory().getBean(name);}

BeanFactory表面只能getBean(),實際上控制反轉,基本的依賴注入、直至Bean的生命周期的各種功能,都由它的實現類體提供。

ApplicationContext

相比較BeanFactory,ApplicationContext多了哪些功能?

由類圖可以看出,ApplicationContext除了繼承BeanFactory的兩個接口,也繼承了四個其他接口。

接下來分別用代碼查看具體做了什么。

首先是翻譯。不過該翻譯需要自己導入文本

首先在resource包下創建messages開頭的properties文件。在里面編寫key=value樣式的翻譯

在主程序中編寫代碼。可以正常輸出翻譯后的內容。

第二個就是路徑查找資源

第三個是從環境中獲取配置值

第四個是發布事件

這個可以用于解耦,比如用戶注冊成功后,可能需要發送短信通知,又或是郵件通知。不知道如何去通知時,不能隨意更改注冊部分的功能,這時可以通過發布事件來處理。

首先自定義事件,需要繼承ApplicationEvent類。

//自定義事件,需要繼承ApplicationEvent
public class UserRegisterEvent extends ApplicationEvent {public UserRegisterEvent(Object source) {super(source);}
}
@Component
public class Component1 {@Autowiredprivate ApplicationEventPublisher publisher;public void register(){System.out.println("用戶注冊");//發布者發布一個UserRegisterEvent事件,從this這個對象發出publisher.publishEvent(new UserRegisterEvent(this));}
}
@Component
public class Component2 {@EventListenerpublic void send(UserRegisterEvent event){System.out.println("發送信息"+event);}
}
@SpringBootApplication
public class CodeApplication {public static void main(String[] args) {ConfigurableApplicationContext ctx = SpringApplication.run(CodeApplication.class, args);Component1 publisher = ctx.getBean(Component1.class);publisher.register();}
}

運行結果如下?

用戶注冊

發送信息com.zmt.test.UserRegisterEvent[source=com.zmt.test.Component1@1d3ac898]

容器實現

BeanFactory實現

從空項目中從頭實現BeanFactory

public class TestBeanFactory {public static void main(String[] args) {/*** 從頭實現BeanFactory*///創建bean工廠DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();//beanDefinition定義,存儲class,scope,初始化方法,銷毀方法等信息AbstractBeanDefinition bean = BeanDefinitionBuilder.genericBeanDefinition(Config.class).setScope("singleton").getBeanDefinition();beanFactory.registerBeanDefinition("config",bean);//接下來獲取查看哪些bean被加載String[] names = beanFactory.getBeanDefinitionNames();for (String name : names) {System.out.println(name);}}@Configurationstatic class Config {@Beanpublic Bean1 bean1(){return new Bean1();}@Beanpublic Bean2 bean2(){return new Bean2();}}static class Bean1 {@Autowiredprivate Bean2 bean2;public Bean1() {System.out.println("構造bean1");}public Bean2 getBean2() {return bean2;}}static class Bean2 {public Bean2() {System.out.println("構造bean2");}}
}

運行結果如下?

config

由此可以看出,這些注解都沒有被解析。只有初始時定義的bean被加載。想要解析注解,需要給beanFactory添加一個后處理器。

運行結果如下?

config

org.springframework.context.annotation.internalConfigurationAnnotationProcessor

org.springframework.context.annotation.internalAutowiredAnnotationProcessor

org.springframework.context.annotation.internalCommonAnnotationProcessor

org.springframework.context.event.internalEventListenerProcessor

org.springframework.context.event.internalEventListenerFactory

可以看到添加了五個處理器。接下來調用這些處理器去解析注解

運行結果如下?

config

org.springframework.context.annotation.internalConfigurationAnnotationProcessor

org.springframework.context.annotation.internalAutowiredAnnotationProcessor

org.springframework.context.annotation.internalCommonAnnotationProcessor

org.springframework.context.event.internalEventListenerProcessor

org.springframework.context.event.internalEventListenerFactory

bean1

bean2

這里看到,可以成功解析注解了,測試Bean1中的getBean2方法發現

bean2并沒有被注入,很顯然時@Autowried沒有起作用。我們需要再寫一個Bean后處理器(針對bean的生命周期各個階段進行擴展)。

也從另一方面體現了,只有當只用bean對象時才會被創建,不使用時只會在beanFactory中保存bean信息。

如果需要提前加載bean,則需要調用方法beanFactory.preInstantiateSingletons()。

具體代碼如下

public class TestBeanFactory {public static void main(String[] args) {/*** 從頭實現BeanFactory*///創建bean工廠DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();//beanDefinition定義,存儲class,scope,初始化方法,銷毀方法等信息AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Config.class).setScope("singleton").getBeanDefinition();//將beanDefinition注冊到beanFactory中beanFactory.registerBeanDefinition("config", beanDefinition);//為BeanFactory添加一些常用的Bean工廠后處理器AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);//調用bean工廠后處理器的方法beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values().forEach(processor -> processor.postProcessBeanFactory(beanFactory));for (String name : beanFactory.getBeanDefinitionNames()) {System.out.println(name);}//將BeanDefinition中的Bean后處理器添加bean后處理器beanFactory.getBeansOfType(BeanPostProcessor.class).values().forEach(beanFactory::addBeanPostProcessor);//單例對象事先創建,而不是創建時才創建bean對象beanFactory.preInstantiateSingletons();System.out.println(beanFactory.getBean(Bean1.class).getBean2());}@Configurationstatic class Config {@Beanpublic Bean1 bean1() {return new Bean1();}@Beanpublic Bean2 bean2() {return new Bean2();}}static class Bean1 {@Autowiredprivate Bean2 bean2;public Bean1() {System.out.println("構造bean1");}public Bean2 getBean2() {return bean2;}}static class Bean2 {public Bean2() {System.out.println("構造bean2");}}
}

從上述代碼中我們可以得到以下結論

  • beanFactory不會主動調用Bean工廠后處理器
  • beanFactory不會主動添加Bean后處理器
  • beanFactory不會主動初始化單例bean
  • beanFactory不會解析${}與#{}

ApplicationContext實現

ClassPathXmlApplicationContext的實現

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="bean1" class="com.zmt.test3.Bean1"/><bean id="bean2" class="com.zmt.test3.Bean2"><property name="bean1" ref="bean1"/></bean>
</beans>
public static void testClassPathXmlApplication(){ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");for (String beanDefinitionName : context.getBeanDefinitionNames()) {System.out.println(beanDefinitionName);}System.out.println(context.getBean(Bean2.class).getBean1());
}

運行結果如下?

bean1

bean2

com.zmt.test3.Bean1@5f3a4b84

applicationContext讀取xml中的配置具體操作如下

public static void main(String[] args){//創建BeanFactoryDefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();//創建xml讀取器XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);System.out.println("讀取配置文件前");for (String beanDefinitionName : beanFactory.getBeanDefinitionNames()) {System.out.println(beanDefinitionName);}//讀取xml文件reader.loadBeanDefinitions("application.xml");System.out.println("讀取配置文件后");for (String beanDefinitionName : beanFactory.getBeanDefinitionNames()) {System.out.println(beanDefinitionName);}
}

運行結果如下??

讀取配置文件前

20:52:54.539 [main] DEBUG org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loaded 2 bean definitions from class path resource [application.xml]

讀取配置文件后

bean1

bean2

讀取xml文件的實現方式還有一種FileSystemXmlApplicationContext,這種實現方式與ClassPathXmlApplicationContext的區別在于前者可以指定配置文件在磁盤中的路徑或是src下的路徑,其他與后者無異。

AnnotationConfigApplicationContext的實現

@Configuration
public class Config{@Beanpublic Bean1 bean1(){return new Bean1();}//也可以通過添加@Autowired注解在Bean2中注入bean1對象@Beanpublic Bean2 bean2(Bean1 bean1){Bean2 bean2 = new Bean2();bean2.setBean1(bean1);return bean2;}
}
public static void testAnnotationConfigApplicationContext(){AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);for (String beanDefinitionName : context.getBeanDefinitionNames()) {System.out.println(beanDefinitionName);}System.out.println(context.getBean(Bean2.class).getBean1());
}

運行結果如下??

org.springframework.context.annotation.internalConfigurationAnnotationProcessor

org.springframework.context.annotation.internalAutowiredAnnotationProcessor

org.springframework.context.annotation.internalCommonAnnotationProcessor

org.springframework.context.event.internalEventListenerProcessor

org.springframework.context.event.internalEventListenerFactory

codeApplication.Config

bean1

bean2

com.zmt.test3.Bean1@96def03

由此可以看出,相比于ClassPahtXmlApplicationContext中,AnnotationConfigApplicationContext會自動添加常用的后處理器并主動調用這些后處理器

AnnotationConfigServletWebServerApplicationContext的實現

這個是用于web環境下的容器實現,既支持Java配置類又支持servlet與servlet的web容器

public static void testAnnotationConfigServletWebServerApplicationContext(){AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);for (String beanDefinitionName : context.getBeanDefinitionNames()) {System.out.println(beanDefinitionName);}
}
@Configuration
public class WebConfig{//創建一個web容器@Beanpublic ServletWebServerFactory servletWebServerFactory(){return new TomcatServletWebServerFactory();}//前端控制器,用來分發Servlet@Beanpublic DispatcherServlet dispatcherServlet(){return new DispatcherServlet();}//將前端控制器注冊到Web容器當中@Beanpublic DispatcherServletRegistrationBean registrationBean(DispatcherServlet dispatcherServlet){return new DispatcherServletRegistrationBean(dispatcherServlet,"/");}//創建一個控制器用來處理前端請求@Bean("/hello")//當Bean注解中以/開頭時,后面的內容除了作為bean名稱外,還要充當網絡訪問路徑public Controller controller1(){return new Controller() {@Overridepublic ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {response.getWriter().print("hello");return null;}};}
}

運行結果如下??

org.springframework.context.annotation.internalConfigurationAnnotationProcessor

org.springframework.context.annotation.internalAutowiredAnnotationProcessor

org.springframework.context.annotation.internalCommonAnnotationProcessor

org.springframework.context.event.internalEventListenerProcessor

org.springframework.context.event.internalEventListenerFactory

codeApplication.WebConfig

servletWebServerFactory

dispatcherServlet

registrationBean

/hello

訪問/hello路徑結果如下?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/384572.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/384572.shtml
英文地址,請注明出處:http://en.pswp.cn/news/384572.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【Linux系統編程學習】 靜態庫的制作與使用

此為牛客網Linux C課程 1.4&1.5 的課程筆記。 0. 關于靜態庫與動態庫 庫就是封裝好的、可服用的代碼&#xff0c;而靜態和動態是指鏈接。 這節課講的是靜態庫&#xff0c;是指在鏈接階段&#xff0c;會將匯編生成的目標文件.o與引用到的庫一起鏈接打包到可執行文件中&…

【Linux系統編程學習】 動態庫的制作與使用

此為牛客網Linux C課程1.6&1.7 的課程筆記。 1. 動態庫命名規則 2. 動態庫的制作 第一步&#xff0c;用gcc編譯生成.o目標文件&#xff0c;注意要用-fpic參數生成與位置無關的代碼&#xff1b; 第二步&#xff0c;用gcc的-shared參數生成動態庫。 涉及到的兩個參數之前學過…

【Linux系統編程學習】 靜態庫與動態庫的對比與總結

此為牛客網Linux C課程 1.9 的課程筆記。 1. 前幾節課知識總結 程序編譯成為可執行文件的過程&#xff1a; 靜態庫制作過程&#xff1a; 動態庫制作過程&#xff1a; 2. 靜態庫的優缺點&#xff1a; 3. 動態庫的優缺點&#xff1a; 更多可參考&#xff1a;吳秦&#xff1…

【Linux系統編程學習】 Makefile簡單入門

此為牛客網Linux C課程1.10&1.11&1.12 的課程筆記。 0. Makefile介紹 1. Makefile文件命名與規則 示例&#xff1a; 使用vim編寫如下名為Makefile的文件&#xff1a; app:sub.o add.o mult.o div.o main.ogcc sub.o add.o mult.o div.o main.o -o appsub.o:sub.cgcc …

【Linux系統編程學習】 GDB調試器的簡單使用

此為牛客網Linux C課程 1.13&1.14&1.15&1.16 的課程筆記。 0. GDB簡介 1. 準備工作 想要使用gdb調試&#xff0c;首先需要用gcc的-g參數生成可執行文件&#xff0c;這樣才能在可執行文件中加入源代碼信息以便調試&#xff0c;但是注意這并不是將源文件嵌入到可執行…

【Linux系統編程學習】C庫IO函數與系統IO函數的關系

此為黑馬Linux課程筆記。 1. C標準IO函數工作流程 如圖&#xff0c;以C庫函數的fopen為例&#xff0c;其返回類型是FILE類型的指針&#xff0c;FILE類型包含很多內容&#xff0c;主要包含三個內容&#xff1a;文件描述符、文件讀寫指針的位置和I/O緩沖區的地址。 文件描述符&…

【Linux系統編程學習】 文件描述符

此為牛客網Linux C課程1.19課程筆記。 1. 文件描述符表 如圖&#xff0c;我們知道每個進程都有其虛擬地址空間&#xff08;0~4G&#xff09;&#xff0c;其中3 ~ 4G部分為內核區。進程的進程控制塊保存就在內核區&#xff0c;而PCB中維護一個打開文件描述符表&#xff0c;每個…

【Linux系統編程學習】Linux系統IO函數(open、read、write、lseek)

此為牛客網Linux C課程1.20課程筆記。 1.open函數 open函數有兩種&#xff0c;分別是打開一個已經存在的文件和創建并打開一個不存在的文件。 #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>// 打開一個已經存在的文件 int open(const…

【Linux系統編程學習】Linux進程控制原語(fork、exec函數族、wait)

此為牛客Linux C和黑馬Linux系統編程課程筆記。 1. fork函數 1.1 fork創建單個子進程 #include<unistd.h> pid_t fork(void);作用&#xff1a;創建一個子進程。 pid_t類型表示進程ID&#xff0c;但為了表示-1&#xff0c;它是有符號整型。(0不是有效進程ID&#xff0…

【Linux系統編程學習】匿名管道pipe與有名管道fifo

此為牛客Linux C和黑馬Linux系統編程課程筆記。 0. 關于進程通信 Linux環境下&#xff0c;進程地址空間相互獨立&#xff0c;每個進程各自有不同的用戶地址空間。任何一個進程的全局變量在另一個進程中都看不到&#xff0c;所以進程和進程之間不能相互訪問&#xff0c;要交換…

【Linux系統編程學習】信號、信號集以其相關函數

此為牛客Linux C和黑馬Linux系統編程課程筆記。 文章目錄0. 信號的概念1. Linux信號一覽表2. 信號相關函數3. kill函數4. raise函數5. abort函數6. alarm函數7. setitimer函數8. signal函數9. 信號集10. 自定義信號集相關函數11. sigprocmask函數12. sigpending函數13. sigacti…

【Linux系統編程學習】父進程捕獲SIGCHLD信號以處理僵尸進程

配合之前說過的sigaction函數和waitpid函數&#xff0c;我們可以解決子進程變成僵尸進程的問題。 先看如下示例程序&#xff1a; #include <sys/time.h> #include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <signal.h> …

【Linux系統編程學習】Linux線程控制原語

此為牛客Linux C課程筆記。 0. 關于線程 注意&#xff1a;LWP號和線程id不同&#xff0c; LWP號是CPU分配時間片的依據&#xff0c;線程id是用于在進程內部區分線程的。 1. 線程與進程的區別 對于進程來說&#xff0c;相同的地址(同一個虛擬地址)在不同的進程中&#xff0c;反…

【Linux網絡編程學習】預備知識(網絡字節序、IP地址轉換函數、sockaddr數據結構)

此為牛客Linux C課程和黑馬Linux系統編程筆記。 1. 網絡字節序 我們已經知道&#xff0c;內存中的多字節數據相對于內存地址有大端和小端之分。 磁盤文件中的多字節數據相對于文件中的偏移地址也有大端小端之分。網絡數據流同樣有大端小端之分&#xff0c;那么如何定義網絡數…

【Linux網絡編程學習】socket API(socket、bind、listen、accept、connect)及簡單應用

此為牛客Linux C課程和黑馬Linux系統編程筆記。 1. 什么是socket 所謂 socket&#xff08;套接字&#xff09;&#xff0c;就是對網絡中不同主機上的應用進程之間進行雙向通信的端點的抽象。 一個套接字就是網絡上進程通信的一端&#xff0c;提供了應用層進程利用網絡協議交換…

【Linux網絡編程學習】使用socket實現簡單服務器——多進程多線程版本

此為牛客Linux C課程和黑馬Linux系統編程筆記。 1. 多進程版 1.1 思路 大體思路與上一篇的單進程版服務器–客戶端類似&#xff0c;都是遵循下圖&#xff1a; 多進程版本有以下幾點需要注意&#xff1a; 由于TCP是點對點連接&#xff0c;服務器主進程連接了一個客戶端以后…

【Linux網絡編程學習】I/O多路復用——select和poll

此為牛客Linux C課程和黑馬Linux系統編程筆記。 0. I/O多路復用 所謂I/O就是對socket提供的內存緩沖區的寫入和讀出。 多路復用就是指程序能同時監聽多個文件描述符。 之前的學習中寫了多進程和多線程版的簡單服務器模型&#xff0c;但是有個問題&#xff1a;每次新來一個客…

【Linux網絡編程學習】I/O多路復用——epoll

此為牛客Linux C課程和黑馬Linux系統編程筆記。 1. 關于epoll epoll是Linux下多路復用IO接口select/poll的增強版本&#xff0c;它能顯著提高程序在大量并發連接中只有少量活躍的情況下的系統CPU利用率&#xff0c;因為它會復用文件描述符集合來傳遞結果而不用迫使開發者每次…

【Linux網絡編程學習】阻塞、非阻塞、同步、異步以及五種I/O模型

文章目錄1. 基本概念1.1 阻塞與非阻塞1.2 同步與異步1.3 為什么沒有“異步阻塞”2. 五種IO模型2.1 阻塞 blocking2.2 非阻塞 non-blocking2.3. IO復用&#xff08;IO multiplexing&#xff09;2.4 信號驅動&#xff08;signal-driven&#xff09;2.5 異步&#xff08;asynchron…

LRU緩存 數據結構設計(C++)

做LeetCode第146題LRU緩存&#xff0c;覺得收獲不小&#xff0c;特此記錄。 請你設計并實現一個滿足 LRU (最近最少使用) 緩存 約束的數據結構。 實現 LRUCache 類&#xff1a; LRUCache(int capacity) 以 正整數 作為容量 capacity 初始化 LRU 緩存。int get(int key) 如果關鍵…