spring底层原理

devtools/2024/10/19 18:47:28/

本文参考黑马程序员的spring底层讲解,想要更详细的可以去看视频。

另外文章会每日更新,大概持续1个月!!!每天更新一讲

这部分比较抽象,要经常复习!!!

一、BeanFactory与ApplicationContext

1、关系

我们在启动类中的代码,获取返回值就得到了ConfigurableApplicationContext类。

ConfigurableApplicationContext context = SpringApplication.run(A01.class, args);

观看下面的类图,可以发现这个类继承了ApplicationContext接口,而ApplicationContext又间接继承了BeanFactory接口。
 

注意:接口可以多继承,类不能多继承

2、到底什么是 BeanFactory?

  1. 它是 ApplicationContext 的父接口 它才是 Spring 的核心容器, 主要的 ApplicationContext 实现都【组合】了它的功能,【组合】是指 ApplicationContext 的一个重要成员变量就是 BeanFactory
  2. BeanFactory 能干点啥

    • 表面上只有 getBean

    • 实际上控制反转、基本的依赖注入、直至 Bean 的生命周期的各种功能,都由它的实现类提供

    • 例子中通过反射查看了它的成员变量 singletonObjects,内部包含了所有的单例 bean

  3. ApplicationContext 比 BeanFactory 多点啥

    • ApplicationContext 组合并扩展了 BeanFactory 的功能

    • 国际化、通配符方式获取一组 Resource 资源、整合 Environment 环境、事件发布与监听

    • 新学一种代码之间解耦途径,事件解耦。  (注意:这里的 通知机制是同步的,主要是用于解耦,而不是异步通知)


/*BeanFactory 与 ApplicationContext 的区别*/
@SpringBootApplication
public class A01 {private static final Logger log = LoggerFactory.getLogger(A01.class);public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException {ConfigurableApplicationContext context = SpringApplication.run(A01.class, args);/*1. 到底什么是 BeanFactory- 它是 ApplicationContext 的父接口- 它才是 Spring 的核心容器, 主要的 ApplicationContext 实现都【组合】了它的功能*/System.out.println(context);/*2. BeanFactory 能干点啥- 表面上只有 getBean- 实际上控制反转、基本的依赖注入、直至 Bean 的生命周期的各种功能, 都由它的实现类提供*/Field singletonObjects = DefaultSingletonBeanRegistry.class.getDeclaredField("singletonObjects");singletonObjects.setAccessible(true);ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();Map<String, Object> map = (Map<String, Object>) singletonObjects.get(beanFactory);map.entrySet().stream().filter(e -> e.getKey().startsWith("component")).forEach(e -> {System.out.println(e.getKey() + "=" + e.getValue());});/*3. ApplicationContext 比 BeanFactory 多点啥*/System.out.println(context.getMessage("hi", null, Locale.CHINA));System.out.println(context.getMessage("hi", null, Locale.ENGLISH));System.out.println(context.getMessage("hi", null, Locale.JAPANESE));Resource[] resources = context.getResources("classpath*:META-INF/spring.factories");for (Resource resource : resources) {System.out.println(resource);}System.out.println(context.getEnvironment().getProperty("java_home"));System.out.println(context.getEnvironment().getProperty("server.port"));//        context.publishEvent(new UserRegisteredEvent(context));context.getBean(Component1.class).register();/*4. 学到了什么a. BeanFactory 与 ApplicationContext 并不仅仅是简单接口继承的关系, ApplicationContext 组合并扩展了 BeanFactory 的功能b. 又新学一种代码之间解耦途径练习:完成用户注册与发送短信之间的解耦, 用事件方式、和 AOP 方式分别实现*/}
}

二、beanFactory的实现

1、第一部分(beanFactory的后处理器)

首先我们看这段代码,运行之后为什么没有打印被@Bean修饰的两个bean呢。你没有看到通过 @Bean 修饰的两个 bean1()bean2() 打印出来,是因为当前的代码只是注册了一个 Config 配置类的 BeanDefinition,而没有通过 Spring 容器去解析 @Configuration 配置类中的 @Bean 方法,从而自动注册 bean1bean2

public static void main(String[] args) {//1、DefaultListableBeanFactory 是 Spring 中最常用的 BeanFactory 实现类DefaultListableBeanFactory beanFactory=new DefaultListableBeanFactory();//2、定义和注册 Bean 定义   genericBeanDefinition的参数代表要定义bean的类型AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Config.class).setScope("singleton").getBeanDefinition();beanFactory.registerBeanDefinition("config",beanDefinition);//将bean注册到bean工厂for (String name : beanFactory.getBeanDefinitionNames()) {System.out.println(name);}}@Configurationstatic class Config {@Beanpublic TestBeanFactory.Bean1 bean1() {return new TestBeanFactory.Bean1();}@Beanpublic TestBeanFactory.Bean2 bean2() {return new TestBeanFactory.Bean2();}}

我们应该向bean工厂加上一些后处理器,让后处理器去完成后续的任务

// 给 BeanFactory 添加一些常用的后处理器
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);

添加这行代码之后,再次打印bean工厂中的bean定义,我们会发现多了几个后处理器。bean1和bean2还没有看到,这是因为这些后处理器还没执行

随后我们要让这些后处理器执行起来,

// BeanFactory 后处理器主要功能,补充了一些 bean 定义
beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values().forEach(e->e.postProcessBeanFactory(beanFactory)
);

 我详细来说明

①beanFactory.getBeansOfType(BeanFactoryPostProcessor.class)获取到name为key,后处理器(BeanFactoryPostProcessor)为value的map集合。

②调用values方法获取到map集合中里面的后处理器封装成一个集合。

③然后调用forEach挨个执行处理器的代码。

这段代码的功能是对所有注册的 BeanFactoryPostProcessor 进行调用。

2、第二部分(bean的后处理器)

准备了下面的两个bean,其中bean1依赖注入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;}@Autowired@Resource(name = "bean4")private Inter bean3;public Inter getInter() {return bean3;}}static class Bean2 {private static final Logger log = LoggerFactory.getLogger(Bean2.class);public Bean2() {log.debug("构造 Bean2()");}}@Configurationstatic class Config {@Beanpublic Bean1 bean1() {return new Bean1();}@Beanpublic Bean2 bean2() {return new Bean2();}@Beanpublic Bean3 bean3() {return new Bean3();}@Beanpublic Bean4 bean4() {return new Bean4();}}interface Inter {}static class Bean3 implements Inter {}static class Bean4 implements Inter {}

我们执行前面的代码,并添加

System.out.println(beanFactory.getBean(Bean1.class).getBean2());

这样就会触发bean1的构造方法,但是bean2没有获取到

但是我们发现,并没有出现构造bean2的字样。证明了前面的beanFactory添加的后处理器并没有实现@Autowired功能,而且要注意的是 这些bean都是用到 了才会加载。如果我们没有beanFactory.getBean(Bean1.class); bean1也不会被构造。

 //执行bean的后处理器的逻辑beanFactory.getBeansOfType(BeanPostProcessor.class).values().forEach(beanPostProcessor->   beanFactory.addBeanPostProcessor(beanPostProcessor));

这样就能得到bean2的构造

beanFactory的bean加载默认是懒汉式的,我们可以设置为饿汉式,就是全部单例bean都构造出来

beanFactory.preInstantiateSingletons();

这样就用等待getBean才加载了

后处理器的排序

准备代码:(其他代码跟上面重复的就不写出来了)

 interface Inter {}static class Bean3 implements Inter {}static class Bean4 implements Inter {}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;}//@Autowired@Resource(name = "bean4")private Inter bean3;public Inter getInter() {return bean3;}}

如果我们执行

System.out.println(beanFactory.getBean(Bean1.class).getInter());

打印的是

我们会发现和@Resource的name指定的类型一致


如果我们写成(这种情况实际 开发中不会遇到,但能帮我们理解背后的原理)

@Autowired
@Resource(name = "bean4")
private Inter bean3;

那生效的是bean3还是bean4呢

打印结果是com.itheima.a02.TestBeanFactory$Bean3@663c9e7a

为什么会这样呢? 这是和添加bean后处理器的顺序有关。

添加比较器的情况

如果我们加上个比较器再进行bean处理器的添加

beanFactory.getBeansOfType(BeanPostProcessor.class).values().stream().sorted(beanFactory.getDependencyComparator()).forEach(beanPostProcessor -> {System.out.println(">>>>" + beanPostProcessor);beanFactory.addBeanPostProcessor(beanPostProcessor);
});

 当我们执行代码后发现是@Resource,翻看源码发现这两个注解的类中有一个Order字段,

越小的优先级越低(这里黑马的老师没有讲清楚,因为sort是升序排序,所以order越小排在钱买你,所以优先级才更高)

       Common  -->Ordered.LOWEST_PRECEDENCE - 3));Autowired  -->Ordered.LOWEST_PRECEDENCE - 2));

可以看出Common(也就是Resource的那个类的order更小)

三、Application的实现

下面是几种Application的实现,分别是spring加载bean的方式和springboot加载bean的方式

1、传统bean的注入 (使用xml的方式) --了解即可

ClassPathXmlApplicationContext context =new ClassPathXmlApplicationContext("b01.xml");for (String name : context.getBeanDefinitionNames()) {System.out.println(name);
}
<?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.itheima.a39.A39_1.Bean1"/></beans>

还有一种是使用文件路径进行加载bean,只是写法不同。

private static void testFileSystemXmlApplicationContext() {FileSystemXmlApplicationContext context =new FileSystemXmlApplicationContext("src\\main\\resources\\a02.xml");for (String name : context.getBeanDefinitionNames()) {System.out.println(name);}System.out.println(context.getBean(Bean2.class).getBean1());}

2、解析xml文件注入bean的原理

底层还是使用

DefaultListableBeanFactory bean工厂

然后使用XmlBeanDefinitionReader 来读取xml文件里面的bean信息,加载到bean工厂中。
(使用file导入bean的方式也是一样,只是换个方法)

 
public static void main(String[] args) {//获取bean工厂DefaultListableBeanFactory beanFactory=new DefaultListableBeanFactory();System.out.println("读取前=========================");for (String beanDefinitionName : beanFactory.getBeanDefinitionNames()) {System.out.println(beanDefinitionName);}XmlBeanDefinitionReader reader=new XmlBeanDefinitionReader(beanFactory);  //专门用于读取xml里面的bean信息的类 并将bean工厂传给他reader.loadBeanDefinitions(new ClassPathResource("a02.xml"));//指定xml文件的路径System.out.println("读取后=========================");for (String beanDefinitionName : beanFactory.getBeanDefinitionNames()) {System.out.println(beanDefinitionName);}}

3、AnnotationConfigApplicationContext 

这种是springboot加载bean的方式。

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());}

不单只bean加载进来,甚至Config类都加载到了IOC容器中还有一系列后处理器

这里提一嘴,在使用传统加载bean的方式中(使用xml方式),我们只需要加上下面的这个配置,就能实现加载一系列后处理器的效果。以前老师说的扫描bean.

4、AnnotationConfigServletWebServerApplicationContext 

这个是加载web应用所需要的一些bean.  这集老师的讲解让我大为震撼。

黑马满一航老师讲解

就是需要内嵌的Tomcat和DispatcherServlet这两个bean,还有将他们进行绑定的bean。这三个bean是web应用中最基础的。

DispatcherServlet是前端控制器,所有http请求都必须经过它。

 // ⬇️较为经典的容器, 基于 java 配置类来创建, 用于 web 环境private static void testAnnotationConfigServletWebServerApplicationContext() {AnnotationConfigServletWebServerApplicationContext context =new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);for (String name : context.getBeanDefinitionNames()) {System.out.println(name);}}@Configurationstatic class WebConfig {@Beanpublic ServletWebServerFactory servletWebServerFactory(){return new TomcatServletWebServerFactory();}@Beanpublic DispatcherServlet dispatcherServlet() {return new DispatcherServlet();}@Beanpublic DispatcherServletRegistrationBean registrationBean(DispatcherServlet dispatcherServlet) {return new DispatcherServletRegistrationBean(dispatcherServlet, "/");}@Bean("/hello")public Controller controller1() {return (request, response) -> {response.getWriter().print("hello");return null;};}}


http://www.ppmy.cn/devtools/127076.html

相关文章

用map实现el-table全选

<el-button size"small" type"primary" click"searchProxy">查询</el-button><el-checkbox v-model"selectAll" change"changeSelectAll" >全选</el-checkbox><el-table:data"taskList&…

什么是回调地狱,如何避免?

概念 回调地狱&#xff08;Callback Hell&#xff09;&#xff0c;也称为金字塔之痛&#xff08;Pyramid of Doom&#xff09;&#xff0c;指的是在 JavaScript 中处理多个嵌套异步操作时&#xff0c;由于回调函数的层层嵌套而导致的代码结构复杂且难以阅读的情况。 回调地狱…

【C语言】指针与函数:传值与传址

函数在使用的时候&#xff0c;给到的形式参数属于局部变量&#xff0c;仅在函数体内部有效。 传值&#xff0c;对于两个值的交换&#xff0c;不影响函数调用之前的数值&#xff0c;也就是不会改变main函数或其他函数中的值。这个就是传值&#xff0c;传递的是实参。传址&#…

985研一学习日记 - 2024.10.16

一个人内耗&#xff0c;说明他活在过去&#xff1b;一个人焦虑&#xff0c;说明他活在未来。只有当一个人平静时&#xff0c;他才活在现在。 日常 1、起床6:00√ 2、健身1个多小时 今天练了二头和背部&#xff0c;明天练胸和三头 3、LeetCode刷了3题 旋转图像&#xff1a…

单片机探秘:从理论到应用

单片机探秘:从理论到应用 在这个科技飞速发展的时代,单片机的应用如同一颗璀璨的星星,照亮了我们生活的方方面面。今天,让我们一同深入探讨单片机的原理与应用,揭开这个技术领域的神秘面纱。 1. 单片机概述 1.1 什么是单片机 你可曾想过,生活中很多自动化设备是如何工…

FreeRTOS:任务通知

目录 一、简介 二、相关API 1.发送任务通知的API 2.获取任务通知的API 三、使用场景 1.代替消息队列 2.代替二值信号量 3.代替计数信号量 4.代替事件组 一、简介 FreeRTOS的任务通知&#xff08;Task Notifications&#xff09;是一个轻量级、快速的机制&#xff0c;用于…

Java @RequestPart注解:同时实现文件上传与JSON对象传参

RequestPart注解&#xff1a;用于处理multipart/form-data请求的一部分&#xff0c;通常用于文件上传或者处理表单中的字段。 java后端举例&#xff1a; PostMapping("/fileTest")public AjaxResult fileTest(RequestPart("file") MultipartFile file,Req…

机器人的应用 基于5G的变电站智慧管控系统

背景概述 一、电力行业面临的挑战与变革 随着全球工业化和信息化的快速发展&#xff0c;电力行业作为国民经济的基础性行业&#xff0c;其重要性日益凸显。然而&#xff0c;随着电力网络的不断扩展和复杂化&#xff0c;变电站和开关站作为电力传输与分配的关键节点&#xff0…