日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

Spring原理学习(一):BeanFactory和ApplicationContext的原理和实现

發布時間:2024/1/18 javascript 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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的原理和实现的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。