spring底层原理

本文参考黑马程序员的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.mzph.cn/news/882331.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【JPCS独立出版 | 福州大学主办 | 有确定的ISSN号】第三届可再生能源与电气科技国际学术会议(ICREET 2024)

第三届可再生能源与电气科技国际学术会议&#xff08;ICREET 2024&#xff09; 2024 3rd International Conference on Renewable Energy and Electrical Technology ICREET 2024已成功申请JPCS - Journal of Physics: Conference Series (ISSN:1742-6596) 独立出版&#xf…

引领智慧文旅新纪元,开启未来旅游新境界

融合创新科技&#xff0c;重塑旅游体验&#xff0c;智慧文旅项目定义旅游新未来 在全球化的浪潮中&#xff0c;旅游已成为连接世界的重要纽带。智慧文旅项目&#xff0c;不仅仅是一次技术的革新&#xff0c;更是对旅游行业未来发展的一次深刻思考。信鸥科技通过运用云计算、大数…

Vue3动态组件原来是这样

什么是Vue3动态组件 在Vue3中&#xff0c;动态组件简单来说就是根据不同的条件进行不同组件的渲染&#xff0c;可以联想一下在前端中常用到的动态样式 基本使用 在Vue3中&#xff0c;动态组件的使用也是非常简单的&#xff0c;只需要使用<component>标签&#xff0c;并…

WPFDeveloper正式版发布

WPFDeveloper WPFDeveloper一个基于WPF自定义高级控件的WPF开发人员UI库&#xff0c;它提供了众多的自定义控件。 该项目的创建者和主要维护者是现役微软MVP 闫驚鏵: https://github.com/yanjinhuagood 该项目还有众多的维护者&#xff0c;详情可以访问github上的README&…

Redis 高可用:从主从到集群的全面解析

目录 一、主从复制 (基础)1. 同步复制a. 全量数据同步b. 增量数据同步c. 可能带来的数据不一致 2. 环形缓冲区a. 动态调整槽位 3. runid4. 主从复制解决单点故障a. 单点故障b. 可用性问题 5. 注意事项a. Replica 主动向 Master 建立连接b. Replica 主动向 Master 拉取数据 二、…

STM32传感器模块编程实践(八) HX711压力传感器称重模块简介及驱动源码

文章目录 一.概要二.HX711主要技术指标三.HX711模块参考原理图四.模块接线说明五.模块工作原理介绍六.模块通讯协议介绍七.STM32单片机与HX711模块实现重量测量实验1.硬件准备2.软件工程3.软件主要代码4.实验效果 八.小结 一.概要 电子秤是将检测与转换技术、计算机技术、信息…

Python网络爬虫从入门到实战

目录 引言 一、网络爬虫的概念 二、 网络爬虫的基本工作流程 &#xff08;一&#xff09;过程&#xff1a; &#xff08;二&#xff09;安装requests模块和beautifulsoup4模块 &#xff08;三&#xff09;requests库的使用 1、requests库的基本介绍 2、导入requests库的…

一款零依赖、跨平台的流媒体协议处理工具,支持 RTSP、WebRTC、RTMP 等视频流协议的处理

大家好&#xff0c;今天给大家分享一款功能强大的流媒体协议处理工具go2rtc&#xff0c;支持多种协议和操作系统&#xff0c;具有零依赖、零配置、低延迟等特点。 项目介绍 go2rtc可以从各种来源获取流&#xff0c;包括 RTSP、WebRTC、HomeKit、FFmpeg、RTMP 等&#xff0c;并…

学习文档10/16

MySQL 字符集&#xff1a; MySQL 支持很多种字符集的方式&#xff0c;比如 GB2312、GBK、BIG5、多种 Unicode 字符集&#xff08;UTF-8 编码、UTF-16 编码、UCS-2 编码、UTF-32 编码等等&#xff09;。 查看支持的字符集 你可以通过 SHOW CHARSET 命令来查看&#xff0c;支持…

ARINC 429总线协议

一、概述 ARINC 是美国航空无线电公司英文字头的缩写&#xff0c; 该公司1977年7月21日出版了“ARINC 429规范”一书&#xff0c;429规范就是飞机电子系统之间数字式数据传输的标准格式&#xff0c;在飞机上使用429总线的电子设备均应遵守这个规范&#xff0c;这样才能保证电子…

Redis应用高频面试题

Redis 作为一个高性能的分布式缓存系统,广泛应用于后端开发中,因此在后端研发面试中,关于 Redis 的问题十分常见。 本文整理了30个常见的 Redis 面试题目,涵盖了 Redis 的源码、数据结构、原理、集群模式等方面的知识,并附上简要的回答,帮助大家更好地准备相关的面试。 …

2024年【N2观光车和观光列车司机】及N2观光车和观光列车司机模拟考试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 N2观光车和观光列车司机考前必练&#xff01;安全生产模拟考试一点通每个月更新N2观光车和观光列车司机模拟考试题题目及答案&#xff01;多做几遍&#xff0c;其实通过N2观光车和观光列车司机操作证考试很简单。 1、…

LabVIEW提高开发效率技巧----用户权限控制

在LabVIEW开发中&#xff0c;用户权限控制是一个重要的设计模块&#xff0c;尤其在多用户系统中&#xff0c;它可以确保数据安全并控制不同用户的操作权限。为了实现用户权限控制&#xff0c;可以通过角色与权限管理模块来进行设计和实施。以下将从多个角度详细说明如何在LabVI…

Sentinel 快速入门

前置推荐阅读:Sentinel 介绍-CSDN博客 前置推荐阅读&#xff1a;Nacos快速入门-CSDN博客 快速开始 欢迎来到 Sentinel 的世界&#xff01;这篇新手指南将指引您快速入门 Sentinel。 Sentinel 的使用可以分为两个部分: 核心库&#xff08;Java 客户端&#xff09;&#xff1a…

新版vs code + Vue高亮、语法自动补全插件

vs code 版本或及以上 安装以下三个插件插件 Vetur Vue语法支持。包括语法高亮、语法代码提示、语法lint检测 ESLint语法纠错 Prettier 2.左下角设置 3.进行配置 配置内容&#xff1a; {"editor.fontSize": 20,"window.zoomLevel": 1,"workben…

Windows】【DevOps】Windows Server 2022 采用WinSW 启动一个会创建新的控制台程序窗口的程序行为分析

WinSW使用参考 【Windows】【DevOps】Windows Server 2022 采用WinSW将一个控制台应用程序作为服务启动&#xff08;方便&#xff09;-CSDN博客 源码 调整ConsoleApp1程序源代码如下&#xff1a; using System; using System.Diagnostics; using System.IO; using System.R…

软件生存期和软件过程

软件生存周期 软件生存周期&#xff08;Software Life Cycle&#xff09;&#xff1a;一个软件项目从问题提出开始&#xff0c;直到软件产品最终退役&#xff08;废弃不用&#xff09;为止。 软件生存周期分为三个时期&#xff1a;计划、开发和维护 整个软件生存周期划分为多…

王爽汇编语言第三版实验3

实验任务 将下面的程序保存为t1.asm&#xff0c;将其生成可执行文件t1.exe 用Vscode编写源程序t1.asm 用脚本一键生成可执行文件t1.exe 成功运行 查看资源管理器&#xff0c;成功生成T1.obj与t1.exe文件‘ 用debug跟踪t1.exe的执行过程&#xff0c;写出每一步执行后&#xff…

大模型生图安全疫苗注入——进阶解决方案与系统优化(DataWhale组队学习)

引言 大家好&#xff0c;我是GISer Liu&#x1f601;&#xff0c;上篇博客中&#xff0c;我们基于DataWhale 2024年10月大模型生图安全疫苗注入赛道的任务&#xff0c;介绍了攻击与防御的基本策略&#xff0c;如通过上下文稀释法、隐喻替换等绕过检测机制&#xff0c;并提出了多…

win10远程桌面打开后,其他主机访问时会报错:发生内部错误;关闭远程桌面,则报无法连接;

win10远程桌面打开后&#xff0c;其他主机访问时会报错&#xff1a;发生内部错误&#xff1b;关闭远程桌面&#xff0c;则报无法连接&#xff1b; 该问题困扰两天&#xff0c;最后这篇文章找到解决方法&#xff0c;成功连上了&#xff1a;https://www.zun.com/zx/yunwei/4798.h…