javascript
Spring原理学习(一):BeanFactory和ApplicationContext的原理和实现
目錄
一、BeanFactory和ApplicationContext的關系
二、BeanFactory的功能
三、ApplicationContext的功能
3.1?MessageSource
3.2?ResourcePatternResolver
3.3??EnvironmentCapable
3.4?ApplicationEventPublisher
3.4.1?ApplicationEventPublisher功能體驗
?3.4.2 事件有什么用
四、BeanFactory的實現
4.1?DefaultListableBeanFactory
? ? ? ? ?4.2 BeanFactory的后處理器
? ? ? ? 4.3 Bean的后處理器
? ? ? ? 4.4 總結
五、ApplicationContext的實現
5.1?ClassPathXmlApplicationContext
5.1.1 使用
? ? ? ? 5.1.2 原理
?5.2?FileSystemXmlApplicationContext
5.2.1 使用
5.2.2 原理
5.3?AnnotationConfigApplicationContext
5.3.1 使用
5.4?AnnotationConfigServletWebServerApplication
一、BeanFactory和ApplicationContext的關系
? ? ? ? 先看下springboot的引導類:
@SpringBootApplication public class A01 {public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException {SpringApplication.run(A01.class, args);} }? ? ? ? run方法的返回值是springboot容器,我們來看一下:
ConfigurableApplicationContext context = SpringApplication.run(A01.class, args);? ? ? ? 那么ConfigurableApplicationContext是什么呢?咱們來看看類圖:
? ? ? ? 可以看到,ConfigurableApplicationContext是ApplicationContext的子類,而ApplicationContext又間接繼承了BeanFactory。
????????BeanFactory才是Spring的核心容器,主要的ApplicationContext實現都“組合”了BeanFactory的功能。
二、BeanFactory的功能
? ? ? ? 先看下BeanFactory有哪些接口:
? ? ? ? ?從表面上看,似乎只有getBean方法,但實際上我們還需要看他的實現類:控制反轉、基本的依賴注入、直至 Bean 的生命周期的各種功能, 都由它的實現類提供。
? ? ? ? 那么,想要看實現類的功能,我們該從何找起呢?
? ? ? ? 我們先查看一下springboot中默認的ConfigurableApplicationContext類中的BeanFactory的實際類型,代碼如下:
ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args); //org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();// 查看實際類型 // class org.springframework.beans.factory.support.DefaultListableBeanFactory System.out.println(beanFactory.getClass());????????從打印結果可以了解到實際類型為DefaultListableBeanFactory(詳見4.1),所以這里以BeanFactory的一個實現類DefaultListableBeanFactory作為出發點,進行分析。他的類圖:
????????我們先看看DefaultListableBeanFactory的父類DefaultSingletonBeanRegistry,看看他的源碼:
? ? ? ? ?可以看到有個成員變量singletonObjects,其實這個變量里面保存了springboot所有的單例,我們可以通過反射拿到singletonObjects后將其打印出來,就能看到所有的單例了。
三、ApplicationContext的功能
? ? ? ? 我們已經了解到,ApplicationContext是BeanFactory的子類,并且我們已經了解到了BeanFactory的功能,那么,我們將著重看看ApplicationContext比BeanFactory多了哪些功能。
? ? ? ? ?可以看到,ApplicationContext除了繼承自BeanFactory之外,還繼承了以下四個類:
- MessageSource:國際化功能,支持多種語言
- ResourcePatternResolver:通配符匹配資源路徑
- EnvironmentCapable:環境信息,系統環境變量,*.properties、*.application.yml等配置文件中的值
- ApplicationEventPublisher:發布事件對象
? ? ? ? 下面我們來分別研究一下這四個類
3.1?MessageSource
? ? ? ?MessageSource擁有國際化功能,支持多種語言。
???????與MessageSource有關的國際化功能的文件在springboot中默認放在message打頭的文件中,我們先建好這些文件:
? ? ? ? 然后在這些文件里面定義同名的key。比如在message_en.properties中定義hi=hello,在messages_ja.propertes中定義hi=こんにちは,在messages_zh中定義hi=你好,這樣在代碼中就可以根據這個key hi和不同的語言類型**獲取不同的值了。?
? ? ? ? 編寫好代碼后,運行起來之后就能看到結果:
? ? ? ? Locale.CHINA、Locale.ENGLISH等值在實際項目中會由前端解析到界面所用的語言后傳過來。
3.2?ResourcePatternResolver
????????ResourcePatternResolver可以通過通配符來匹配資源路徑。
????????例1:獲取類路徑下的messages開頭的配置文件:
Resource[] resources = context.getResources("classpath:messages*.properties"); for (Resource resource : resources) {System.out.println(resource); }?????????例2:獲取spring相關jar包中的spring.factories配置文件:
resources = context.getResources("classpath*:META-INF/spring.factories"); for (Resource resource : resources) {System.out.println(resource); }3.3??EnvironmentCapable
????????EnvironmentCapable可以獲取系統環境信息或系統環境變量里的值,比如環境變量、*.properties、*.application.yml等配置文件中的值。
//獲取系統環境變量中的java_home System.out.println(context.getEnvironment().getProperty("java_home")); //獲取項目的application.yml中的server.port屬性 System.out.println(context.getEnvironment().getProperty("server.port"));3.4?ApplicationEventPublisher
????????ApplicationEventPublisher可以用來發布事件。
3.4.1?ApplicationEventPublisher功能體驗
? ? ? ? 想要試試發布事件的功能,我們需要準備三個部分:事件發送類、事件接收(監聽)類、事件類。
? ? ? ? 先看事件類,他繼承自ApplicationEvent:
public class UserRegisteredEvent extends ApplicationEvent {public UserRegisteredEvent(Object source) {super(source);} }????????再定義一個事件接受(監聽)類,用于監聽用戶注冊事件。類上需要加@Component注解,將該類交給spring管理。spring中任意個容器都可以作為監聽器。然后定義一個處理事件的方法,參數類型為事件類的對象,方法頭上需要加上@EventListener注解。
@Component @Slf4j public class UserRegisteredListener {@EventListenerpublic void userRegist(UserRegisteredEvent event) {System.out.println("UserRegisteredEvent...");log.debug("{}", event);} }? ? ? ? 再定義一個發送事件的類,就是使用ApplicationEventPublisher的實例對象調用pubulishEvent方法發送,傳入的參數是我們剛剛定義好的事件類:
@Component @Slf4j public class UserService {@Autowiredprivate ApplicationEventPublisher context;public void register(String username, String password) {log.debug("新用戶注冊,賬號:" + username + ",密碼:" + password);context.publishEvent(new UserRegisteredEvent(this));} }? ? ? ? 然后在主啟動類中調用一下就可以了:
UserService userService = context.getBean(UserService.class); userService.register("張三", "123456");?3.4.2 事件有什么用
? ? ? ? 事件最主要的功能就是解耦。
????????譬如我們使用事件來做一個用戶注冊的功能,功能里有用戶注冊類UserService,用來發送事件,也有用戶注冊監聽類UserRegisteredListener,用于接收事件。由于用戶注冊后我們有多種后續操作,比如給用戶發短信、給用戶發郵件或給用戶發微信公眾號提醒。這樣就要求我們的系統有良好的可擴展性,UserService類和UserRegisteredListener類不能耦合在一起,而我們使用事件(如上),就能夠實現UserService類和UserRegisteredListener類的解耦:用戶注冊類UserService發送事件后,我們可以用不同的監聽類來接收,不同的監聽類做不同的事情;比如我們可以用UserRegisteredListener1來給用戶發短信,用UserRegisteredListener2來給用戶發郵件。
? ? ? ? 使用事件進行解耦是一種新的解耦方式,他與AOP方式有什么不同呢?這個值得我們思考。
四、BeanFactory的實現
4.1?DefaultListableBeanFactory
? ? ? ? BeanFactory的實現類十分多,我們需要抓住一個重點的實現類去看,這個類就是DefaultListableBeanFactory。?
? ? ? ? ?接二(BeanFactory的功能)中所言,Spring底層創建實體類就是依賴于DefaultListableBeanFactory,所以,他是BeanFactory的實現類中最重要的一個。我們有必要使用一下這個類,來模擬Spring使用DefaultListableBeanFactory創建其他實體類對象的過程。
public class TestBeanFactory {public static void main(String[] args) {//先創建bean工廠,剛創建的時候是沒有任何bean的,我們需要往里面添加bean的定義DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();// bean 的定義(即bean的一些描述信息,包含class:bean是哪個類,scope:單例還是多例,初始化、銷毀方法等)AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Config.class).setScope("singleton").getBeanDefinition();//把beanDefinition這個bean定義注冊進bean工廠,第一個參數是給它起的名字beanFactory.registerBeanDefinition("config", beanDefinition);// 打印BeanFactory中Beanfor (String name : beanFactory.getBeanDefinitionNames()) {System.out.println(name);}}@Configurationstatic class Config {@Beanpublic Bean1 bean1() {return new Bean1();}@Beanpublic Bean2 bean2() {return new Bean2();}}// bean1依賴于bean2@Slf4jstatic class Bean1 {@Autowiredprivate Bean2 bean2;public Bean2 getBean2() {return bean2;}public Bean1() {log.debug("構造 Bean1()");}}@Slf4jstatic class Bean2 {public Bean2() {log.debug("構造 Bean2()");}}interface Inter {} }? ? ? ? 這個時候打印出bean工廠中有多少bean,結果只有一個,就是我們剛剛注冊進去的config。
? ? ? ? 那么問題來了,我們在spring實戰的知識中得知:當加上@Configuration和@Bean時,容器中會注冊這些bean;換句話說,我們此時打印bean工廠的所有bean,理應看到bean1和bean2,而不是只有config。此時只有一個解釋能成立:@Configuration和@Bean都沒有被解析。那么解析這些注解的功能由誰提供呢?
? ? ? ? ?4.2 BeanFactory的后處理器
? ? ? ??BeanFactory本身實現的功能并不多,他的許多功能都是由BeanFactory的后處理器進行擴展的。
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);? ? ? ? 使用上面的工具類可以為bean工廠添加一些常用的后處理器,此時我們打印bean工廠里的所有bean:
????????可以看到現在多了一些后處理器從名字也可以大致猜出,他們是處理@Configuration的、@Autowired的……,現在只是把他們加進了bean工廠,還需要讓他們工作起來。
public class TestBeanFactory {public static void main(String[] args) {DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();// bean 的定義(class, scope, 初始化, 銷毀)AbstractBeanDefinition beanDefinition =BeanDefinitionBuilder.genericBeanDefinition(Config.class).setScope("singleton").getBeanDefinition();beanFactory.registerBeanDefinition("config", beanDefinition);// 給 BeanFactory 添加一些常用的后處理器AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values().forEach(beanFactoryPostProcessor -> {beanFactoryPostProcessor.postProcessBeanFactory(beanFactory);});// 打印BeanFactory中Beanfor (String name : beanFactory.getBeanDefinitionNames()) {System.out.println(name);}}@Configurationstatic class Config {@Beanpublic Bean1 bean1() {return new Bean1();}@Beanpublic Bean2 bean2() {return new Bean2();}}static class Bean1 {private static final Logger log = LoggerFactory.getLogger(Bean1.class);public Bean1() {log.debug("構造 Bean1()");}@Autowiredprivate Bean2 bean2;public Bean2 getBean2() {return bean2;}}static class Bean2 {private static final Logger log = LoggerFactory.getLogger(Bean2.class);public Bean2() {log.debug("構造 Bean2()");}} }? ? ? ? 此時再打印所有的bean,就可以看到bean1和bean2已經出現了。
? ? ? ? 4.3 Bean的后處理器
? ? ? ? 在4.2中我們添加了一些后處理器,比如internalConfigurationAnnotationProcessor是處理@Configuration的,它屬于BeanFactory的后處理器;而internalAutowiredAnnotationProcessor和internalCommonAnnotationProcessor就屬于bean的后處理器,他們是針對 bean 的生命周期的各個階段提供擴展, 例如 internalAutowiredAnnotationProcessor用于解析@Autowired、internalCommonAnnotationProcessor用于解析@Resource。
? ? ? ? 如果我們需要讓@Autowired 和 @Resource注解也發揮作用,則需要:
// Bean 后處理器, 針對 bean 的生命周期的各個階段提供擴展, 例如 @Autowired @Resource ...beanFactory.getBeansOfType(BeanPostProcessor.class).values().stream().sorted(beanFactory.getDependencyComparator()).forEach(beanPostProcessor -> {System.out.println(">>>>" + beanPostProcessor);beanFactory.addBeanPostProcessor(beanPostProcessor);});? ? ? ? 這樣我們的@Autowired 和 @Resource注解就發揮作用了。請注意,我們這里getBeansOfType()方法傳遞的參數是BeanPostProcessor.class,是bean的后處理器。
? ? ? ? 4.4 總結
? ? ? ??BeanFactory是一個比較基礎的類,他本身并沒有特別多的功能,這些事情它不會去做:
- 不會主動調用BeanFactory后處理器
- 不會主動添加Bean后處理器
- 不會主動初始化單例
- 不會解析#{}、${}等
五、ApplicationContext的實現
? ? ? ? 先看看ApplicationContext的實現類有哪些:
? ? ? ? 今天我們來介紹四個比較重要的實現類:
- ClassPathXmlApplicationContext
- FileSystemXmlApplicationContext
- AnnotationConfigApplicationContext
- AnnotationConfigServletWebServerApplication
5.1?ClassPathXmlApplicationContext
????????較為經典的容器, 基于 classpath(類路徑)下 xml 格式的配置文件來創建ApplicationContext.
5.1.1 使用
? ? ? ? 創建一個測試類?
private static void testClassPathXmlApplicationContext() {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("a02.xml");//看一下ApplicationContext中有多少beanfor (String name : context.getBeanDefinitionNames()) {System.out.println(name);}//看看bean2中有沒有成功注入bean1System.out.println(context.getBean(Bean2.class).getBean1()); }static class Bean1 { }static class Bean2 {private Bean1 bean1;public void setBean1(Bean1 bean1) {this.bean1 = bean1;}public Bean1 getBean1() {return bean1;} }? ? ? ? ?創建xml配置文件,并在文件中定義bean
<?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"><!-- 控制反轉, 讓 bean1 被 Spring 容器管理 --><bean id="bean1" class="com.itheima.a02.A02.Bean1"/><!-- 控制反轉, 讓 bean2 被 Spring 容器管理 --><bean id="bean2" class="com.itheima.a02.A02.Bean2"><!-- 依賴注入, 建立與 bean1 的依賴關系 --><property name="bean1" ref="bean1"/></bean> </beans>? ? ? ? 運行結果:
??
5.1.2 原理
? ? ? ? 我們模擬一下加載xml文件的過程即可明白原理了:先初始化出DefaultListableBeanFactory,然后通過 XmlBeanDefinitionReader 到xml文件中讀取bean的配置信息,將這些bean加載到bean工廠中。?
public static void main(String[] args) {//先實現DefaultListableBeanFactoryDefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();System.out.println("讀取之前...");for (String name : beanFactory.getBeanDefinitionNames()) {System.out.println(name);}System.out.println("讀取之后...");//然后到xml文件中讀取bean的定義信息XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);reader.loadBeanDefinitions(new ClassPathResource("a02.xml"));for (String name : beanFactory.getBeanDefinitionNames()) {System.out.println(name);}}?5.2?FileSystemXmlApplicationContext
????????基于磁盤路徑下 xml 格式的配置文件來創建ApplicationContext。
5.2.1 使用
? ? ? ? 編寫一個測試類:
private static void testFileSystemXmlApplicationContext() {FileSystemXmlApplicationContext context =new FileSystemXmlApplicationContext("src\\main\\resources\\a02.xml");for (String name : context.getBeanDefinitionNames()) {System.out.println(name);}//看看bean2中有沒有成功注入bean1System.out.println(context.getBean(Bean2.class).getBean1());}static class Bean1 { }static class Bean2 {private Bean1 bean1;public void setBean1(Bean1 bean1) {this.bean1 = bean1;}public Bean1 getBean1() {return bean1;} }? ? ? ? xml文件與5.1.1中為同一個。運行結果也同5.1.1?
5.2.2 原理
????????我們模擬一下加載xml文件的過程即可明白原理了:先初始化出DefaultListableBeanFactory,然后通過 XmlBeanDefinitionReader 到xml文件中讀取bean的配置信息,將這些bean加載到bean工廠中。??
public static void main(String[] args) {DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();System.out.println("讀取之前...");for (String name : beanFactory.getBeanDefinitionNames()) {System.out.println(name);}System.out.println("讀取之后...");//然后到xml文件中讀取bean的定義信息XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);reader.loadBeanDefinitions(new FileSystemResource("src\\main\\resources\\a02.xml"));for (String name : beanFactory.getBeanDefinitionNames()) {System.out.println(name);}}5.3?AnnotationConfigApplicationContext
5.3.1 使用
????????較為經典的容器, 基于 java 配置類來創建ApplicationContext。?
private static void testAnnotationConfigApplicationContext() {AnnotationConfigApplicationContext context =new AnnotationConfigApplicationContext(Config.class);for (String name : context.getBeanDefinitionNames()) {System.out.println(name);}System.out.println(context.getBean(Bean2.class).getBean1()); }@Configuration static class Config {@Beanpublic Bean1 bean1() {return new Bean1();}@Beanpublic Bean2 bean2(Bean1 bean1) {Bean2 bean2 = new Bean2();bean2.setBean1(bean1);return bean2;} }static class Bean1 { }static class Bean2 {private Bean1 bean1;public void setBean1(Bean1 bean1) {this.bean1 = bean1;}public Bean1 getBean1() {return bean1;} }? ? ? ? 運行結果:
? ? ? ? ?可以看到,結果與5.1.1和5.2.1有不同,因為我們的配置類Config也默認為一個bean注入進了bean工廠;除此之外,AnnotationConfigApplicationContext 還自動幫我們加了五個后處理器。
5.4?AnnotationConfigServletWebServerApplication
????????較為經典的容器, 基于 java 配置類來創建ApplicationContext, 用于 web 環境。
? ? ? ? 我們創建一個測試類來使用它:
private static void testAnnotationConfigServletWebServerApplicationContext() {AnnotationConfigServletWebServerApplicationContext context =new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);for (String name : context.getBeanDefinitionNames()) {System.out.println(name);}}@Configurationstatic class WebConfig {//tomcat容器@Beanpublic ServletWebServerFactory servletWebServerFactory(){return new TomcatServletWebServerFactory();}//前控制器@Beanpublic DispatcherServlet dispatcherServlet() {return new DispatcherServlet();}//讓前控制器運行在Tomcat容器中@Beanpublic DispatcherServletRegistrationBean registrationBean(DispatcherServlet dispatcherServlet) {return new DispatcherServletRegistrationBean(dispatcherServlet, "/");}//控制器@Bean("/hello")public Controller controller1() {return (request, response) -> {response.getWriter().print("hello");return null;};}}? ? ? ? 可以看到后臺打印出很多的bean。
? ? ? ? 從這里可以了解到:
- springboot內嵌了Tomcat,我們無需手動添加Tomcat容器,也可以運行bean
- springboot的所有請求都要經過dispatchServlet(前控制器),然后再走到我們自己的控制器中
- 通過DispatcherServletRegistrationBean 可以將DispatcherServlet 注冊到Tomcat中
總結
以上是生活随笔為你收集整理的Spring原理学习(一):BeanFactory和ApplicationContext的原理和实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 中国十大军校排行榜中国十大军校排行榜:
- 下一篇: gradle idea java ssm