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.mzph.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模型

文章目录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…

STM32时钟树解析

本人之前其实也用STM32做过一些小东西&#xff0c;但因为时钟的初始化一般是直接在SystemInit时钟系统初始化函数里直接配置为72MHz&#xff0c;所以对于STM32的时钟框图并没有怎么理会&#xff0c;今天刚好有空就重新看了一下并写一篇博客记录一下吧&#xff0c;以免以后又忘了…

S3C2440时钟体系

S3C2440在默认情况下&#xff0c;整个系统全靠一个12MHz的外部晶振提供频率来工作运行的&#xff0c;也就是说CPU、内存、UART、ADC等所有需要用到时钟频率的硬件都工作在12MHz下&#xff0c;但是通过查阅芯片手册我们知道CPU时钟最高可为400MHZ&#xff0c;那么怎么设置时钟让…