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…

【算法日记】力扣239 滑动窗口最大值

题目描述 给你一个整数数组 nums&#xff0c;有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回 滑动窗口中的最大值 。 示例 1&#xff1a; 输入&#xff1a;nums [1,3,-1,-3,5,3,6,…

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

融合创新科技&#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库的…

Linux文件权限基础与管理指南

在Linux操作系统中&#xff0c;文件权限是一个至关重要的概念&#xff0c;它决定了用户对文件和目录的访问控制。理解文件权限不仅有助于保护系统安全&#xff0c;还能提高文件管理的效率。本文将介绍Linux文件权限的基本概念、类型以及如何管理这些权限&#xff0c;并提供更多…

Cisco Nexus N93108转换模式for Nxos to ACI mode失败案例

到货6台 Nexus 93108TC-FX switch 到货时系统为NXOS 由于我们是ACI环境 &#xff0c;所以需要将其转换为ACI 镜像&#xff0c;否则是无法注册到APIC上的。 前5台的操作都很顺利&#xff0c;按以下步骤操作完重启就完成了。 1 确认bootflash中有ACI的镜像 switch# dir4096 …

一款零依赖、跨平台的流媒体协议处理工具,支持 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;支持…

java HttpURLConnection传递键值对参数,表单参数

当表头设置参数类型为这个的时候&#xff0c; conn.setRequestProperty(“Content-Type”,“application/x-www-form-urlencoded”); 如果参数用的是jsonstring的话&#xff0c;在请求中&#xff0c;参数会都跑到key中&#xff0c;而且不会自动识别key和value&#xff1b; Str…

SpringBoot框架的车辆管理自动化解决方案

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…

ARINC 429总线协议

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

Linux C语言 进程详解——fork()/wait()/waitpid()

一、fork()函数 1. 头文件 #include<unistd.h> #include<sys/types.h>2. 函数原型及功能介绍函数原型pid_t fork( void);(pid_t 是一个宏定义,其实质是int 被定义在#includesys/types.h>中)返回值:若成功调用一次则返回两个值,子进程返回0,父进程返回子进…

Redis应用高频面试题

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

2024 复健记

2024 复健记 OI生涯结束两年了&#xff08;六年OI一场空&#xff0c;梦里花落知多少&#xff09; 这两天闲着无事翻了翻自己的CSDN&#xff0c;发现上一篇文章已经是两年之前了。那时候退役&#xff0c;虽然不知道梦应归于何处&#xff0c;但依旧梦里栩然蝴蝶、一身轻。如今忆…

独孤思维:图书电商做亏本了怎么弥补

01 昨天有个人问我&#xff0c;我做cps拉新。 拉了多少人&#xff0c;奖励了我免费星球的名额。 这个是博主个人行为&#xff0c;而且也是阶段性奖励&#xff0c;现在没有了。 其实这些都不是关键。 很多人喜欢问一些无聊的问题&#xff0c;只想要结果。 却从来不去探究自…

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

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