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

    }
    @Configuration
    static class Config {
        @Bean
        public TestBeanFactory.Bean1 bean1() {
            return new TestBeanFactory.Bean1();
        }

        @Bean
        public 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()");
        }

        @Autowired
        private 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()");
        }
    }





    @Configuration
    static class Config {
        @Bean
        public Bean1 bean1() {
            return new Bean1();
        }

        @Bean
        public Bean2 bean2() {
            return new Bean2();
        }

        @Bean
        public Bean3 bean3() {
            return new Bean3();
        }

        @Bean
        public 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()");
        }

        @Autowired
        private 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);
        }
    }

    @Configuration
    static class WebConfig {
        @Bean
        public ServletWebServerFactory servletWebServerFactory(){
            return new TomcatServletWebServerFactory();
        }
        @Bean
        public DispatcherServlet dispatcherServlet() {
            return new DispatcherServlet();
        }
        @Bean
        public DispatcherServletRegistrationBean registrationBean(DispatcherServlet dispatcherServlet) {
            return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
        }
        @Bean("/hello")
        public Controller controller1() {
            return (request, response) -> {
                response.getWriter().print("hello");
                return null;
            };
        }
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/894599.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Olap数据处理

一、OLAP 是什么 1. OLAP的定义 OLAP&#xff08;Online Analytical Processing&#xff0c;联机分析处理&#xff09;是一种软件技术&#xff0c;它主要专注于复杂的分析操作&#xff0c;帮助分析人员、管理人员或执行人员从多角度对信息进行快速、一致、交互地存取&#xf…

电脑桌面自己变成了英文Desktop,怎么改回中文

目录 前言找到Desktop查看位置查找目标修改文件名为桌面重启电脑 或 重启 Windows 资源管理器CtrlShiftEsc 打开任务管理器找到 Windows 资源管理器重启 Windows 资源管理器 查看修改结果 前言 许多人在使用电脑的时候发现&#xff0c;我们经常使用的桌面&#xff0c;不知道因为…

Vue向上滚动加载数据时防止内容闪动

目前的需求&#xff1a;当前组件向上滚动加载数据&#xff0c;dom加载完后&#xff0c;页面的元素位置不能发生变化 遇到的问题&#xff1a;加载完数据后&#xff0c;又把滚轮滚到之前记录的位置时&#xff0c;内容发生闪动 现在的方案&#xff1a; 加载数据之前记录整体滚动条…

004-按照指定功能模块名称分组

按照指定功能模块名称分组 一、说明1.现在有一个需求&#xff1a;2.具体做法 二、代码案例三、效果展示 一、说明 1.现在有一个需求&#xff1a; 需要把一个功能模块的几个功能点放在同一个文档目录下&#xff0c;这几个功能点分布在不同的 Controller 2.具体做法 需要把他…

登录前端笔记(二):vuex管理用户数据;跨域;axios封装;环境;请求响应拦截;权限;用户资料Vuex共享

一、Vuex登录流程之用户模块&#xff1a; 简言之&#xff1a;点击登录调用actions且得到token&#xff0c;把得到的token提交给mutations从而修改state里的数据。 原视频 &#xff08;1&#xff09;Vuex用户模块流程 组件页面里点击登录后&#xff0c;调用stores里的actions&…

智慧油田智能安全管控方案-AI助力油气田安全管控升级

在科技日新月异的今天&#xff0c;万物纵横科技凭借其前沿的智慧油田智能安全管控方案&#xff0c;正引领着油气田行业向智能化、高效化转型。该方案深度融合了AI视频智能分析与AIoT&#xff08;物联网人工智能&#xff09;技术&#xff0c;为采油场、油气场的设备运维、环境监…

查缺补漏----Cache命中率与缺失率的判断

第一类问题对比&#xff1a; 块大小16B&#xff0c;int型数据占4B&#xff0c;所以一个Cache块可以放4个数组&#xff0c;所以访问1个数组&#xff0c;后面3个数组的地址可以在同一个Cache块找到。 一个数组要访问2次&#xff0c;即读这个数组&#xff0c;然后给这个数组赋值&a…

傅里叶变换(FT)与快速傅里叶变换(FFT)的区别

傅里叶变换&#xff08;Fourier Transform, FT&#xff09;和快速傅里叶变换&#xff08;Fast Fourier Transform, FFT&#xff09;都是用于信号频域分析的工具&#xff0c;但它们在计算方式和效率上存在显著的区别。下面小编将详细说明傅里叶变换和快速傅里叶变换的定义、原理…

MATLAB图像重心计算

图像重心&#xff08;或质心&#xff09;计算是计算机视觉和图像处理领域 应用领域广泛&#xff1a;包括医疗,生物&#xff0c;动画&#xff0c;机器人等。 该文章通过灰度转换->二值化->质心计算 以下是代码中涉及的一些数学概念和公式&#xff1a; 灰度转换&#xff1a…

离岗睡岗预警系统 值班室离岗识别系统Python 结合 OpenCV 库

在众多工作场景中&#xff0c;存在着一些特殊岗位&#xff0c;这些岗位对于人员的专注度和警觉性有着极高的要求。然而&#xff0c;离岗睡岗现象却时有发生&#xff0c;给工作的正常开展和安全保障带来了严重的威胁。本文将深入探讨特殊岗位离岗睡岗的危害&#xff0c;以及如何…

音乐播放器-0.专栏介绍​

1.简介 本专栏使用Qt QWidget作为显示界面&#xff0c;你将会学习到以下内容&#xff1a; 1.大量ui美化的实例。 2.各种复杂ui布局。 3.常见显示效果实现。 4.大量QSS实例。 5.Qt音频播放&#xff0c;音乐歌词文件加载&#xff0c;展示。 6.播放器界面换肤。 相信学习了本专栏…

【汇编语言】寄存器(内存访问)(六)—— 栈

文章目录 前言正文结语 前言 &#x1f4cc; 汇编语言是很多相关课程&#xff08;如数据结构、操作系统、微机原理&#xff09;的重要基础。但仅仅从课程的角度出发就太片面了&#xff0c;其实学习汇编语言可以深入理解计算机底层工作原理&#xff0c;提升代码效率&#xff0c;尤…

java集合进阶篇-《List集合》

个人主页→VON 收录专栏→java从入门到起飞 目录 ​编辑 一、前言 二、List集合简要概述 三、List集合主要函数的应用 四、List集合的遍历 五、思考 一、前言 List集合与Collection集合的相同之处还是挺多的&#xff0c;不过有些小细节又不太一样&#xff0c;其中有一个…

嵌入式学习-IO进程-Day04

嵌入式学习-IO进程-Day04 进程的函数接口 fork和Vfork 回收进程资源 wait waitpid 退出进程 获取进程号&#xff08;getpid&#xff0c;getppid&#xff09; 守护进程 守护进程的特点 创建步骤 exec函数族 线程 概念 线程和进程的区别 线程资源 线程函数接口 创建线程&#xff…

ASO优化手机游戏的秘密功能

在本文中&#xff0c;我们将尝试弄清楚手机游戏的 ASO 优化是否有任何特定功能。移动游戏在移动应用世界中占有特殊的地位&#xff0c;因为它们是最赚钱的细分市场。然而&#xff0c;今天&#xff0c;我们将关注的不是货币化&#xff0c;而是基础知识—文本和视觉优化、它们在游…

AI自动生成PPT哪个软件好?智能生成PPT不再熬夜做课件

大概这世上&#xff0c;都是职场牛马对“PPT”这三个字母的头痛反应最大吧&#xff01; 是的&#xff0c;就连各个年级段的老师也是很头痛——愁着怎样能在排版整齐的情况下&#xff0c;将必考知识点都呈现在PPT每一张幻灯片页面里...... 近期打听到用人工智能生成ppt课件&am…

2024全国大数据与计算智能挑战赛火热报名中!

一年一度的 全国大数据与计算智能挑战赛震撼来袭&#xff01; 报名速通&#xff1a; https://www.datafountain.cn/special/BDSSF2024 大数据与决策&#xff08;国家级&#xff09;实验室连续三年组织发起全国大数据与计算智能挑战赛&#xff0c;旨在深入挖掘大数据应用实践中亟…

基于模型预测控制(MPC)储能控制策略-多目标哈里斯鹰(MOHHO)算法的储能容量配置方法

目录 一、主要内容&#xff1a; 二、运行效果&#xff1a; 三、模型预测控制介绍&#xff1a; 四、多目标哈里斯鹰算法&#xff1a; 五、代码数据下载&#xff1a; 一、主要内容&#xff1a; 本研究旨在提出一种双层控制模型&#xff0c;结合模型预测控制&#xff08;MPC…

2024年四非边缘鼠鼠计算机保研回忆(记录版 碎碎念)

Hi&#xff0c;大家好&#xff0c;我是半亩花海。写下这篇博客时已然是金秋十月&#xff0c;心中的石头终于落地&#xff0c;恍惚间百感交集。对于保研这条路&#xff0c;我处于摸着石头过河、冲击、随缘的这些状态。计算机保研向来比其他专业难&#xff0c;今年形势更是艰难。…

开发一个微信小程序要多少钱?

在当今数字化时代&#xff0c;微信小程序成为众多企业和个人拓展业务、提供服务的热门选择。那么&#xff0c;开发一个微信小程序究竟需要多少钱呢&#xff1f; 开发成本主要取决于多个因素。首先是功能需求的复杂程度。如果只是一个简单的信息展示小程序&#xff0c;功能仅限…