spring高级篇(九)

news2024/9/23 13:18:44

boot的执行流程分为构造SpringApplication对象、调用run方法两部分

1、Spring Boot 执行流程-构造

        通常我们会在SpringBoot的主启动类中写以下的代码:

        参数一是当前类的字节码,参数二是main的args参数。

public class StartApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringApplication.class,args);
    }
}

        在SpringApplication.run的内部,会做两件事:

        创建SpringApplication对象:

        以及调用SpringApplication对象的run方法:

        在创建SpringApplication对象时,通常又会做下面几件事:

  • 获取bean definition源
  • 获取推断应用类型
  • ApplicationContext初始化器
  • 监听器与事件
  • 主类推断
1.1、获取bean definition源

        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));

       将传入的 primarySources数组转换为 LinkedHashSet,并赋值给当前对象的 primarySources成员变量。这个集合用于存储应用程序的主要源,通常是启动类(例如,@SpringBootApplication注解标记的类)。

1.2、获取推断应用类型

        this.webApplicationType = WebApplicationType.deduceFromClasspath();

        通过deduceFromClasspath() 方法进行应用类型推论:

        deduceFromClasspath() WebApplicationType的方法,WebApplicationType是一个枚举类:

        简单说明一下这段代码:第一个if判断,如果是reactive.DispatcherHandler,并且不是servlet.DispatcherServlet和servlet.ServletContainer,就推断类型为REACTIVE并返回。

        如果上面的条件不成立,也就是应用类型没有被推断为REACTIVE,就会循环成员变量的SERVLET_INDICATOR_CLASSES数组,该数组中有两个元素:

  • javax.servlet.Servlet
  • org.springframework.web.context.ConfigurableWebApplicationContext

        如果任意一个元素不存在就推断类型为NONE并返回。

        最后推论类型为SERVLET并返回

1.3、ApplicationContext初始化器

        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

        获取所有注册的 ApplicationContextInitializer实例,并将其设置为应用程序的初始化器。

        通过SpringApplication实例的.addInitializers()方法注册初始化器

springApplication.addInitializers(new ApplicationContextInitializer<ConfigurableApplicationContext>() {
            @Override
            public void initialize(ConfigurableApplicationContext applicationContext) {
                if (applicationContext instanceof GenericApplicationContext){
                    ((GenericApplicationContext) applicationContext).registerBean("bean3",Bean3.class);
                }
            }
        });
1.4、监听器与事件

        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

        获取所有注册的 ApplicationListener实例,并将其设置为应用程序的监听器。

        通过SpringApplication实例的.addListeners()方法注册监听器与事件

  springApplication.addListeners(new ApplicationListener<ApplicationEvent>() {
            @Override
            public void onApplicationEvent(ApplicationEvent event) {
                System.out.println("事件:"+event.getClass());
            }
        });
1.5、主类推断

        this.mainApplicationClass = deduceMainApplicationClass();

        调用deduceMainApplicationClass() 方法进行主类推论:

  • 通过创建一个新的 RuntimeException对象来获取当前线程的堆栈轨迹(stack trace),即调用堆栈信息。
  • 遍历堆栈信息,找到方法名为"main"的函数,并返回类的class对象
  • 没有找到就抛出异常

2、Spring Boot 执行流程-run

2.1、事件发布器

           

        在run方法中,首先会通过getRunListeners(args);得到 SpringApplicationRunListeners。

        SpringApplicationRunListeners是一个事件发布器,会在run方法的执行不同阶段中发布对应的事件:

  1. 得到事件发布器后,会发布listeners.starting(); 事件,代表spring boot 开始启动。
  2. prepareEnvironment(listeners, applicationArguments);方法执行时,会发布listeners.environmentPrepared(environment); 事件,代表环境信息准备完成
  3. prepareContext(context, environment, listeners, applicationArguments, printedBanner); 方法执行时,会发布listeners.contextPrepared(context);事件,代表spring容器创建,但未调用初始化器。listeners.contextLoaded(context);事件,代表所有bean definition加载完毕。(然后会调用context.refresh();方法
  4. 在调用context.refresh();方法后,会发布listeners.started(context);事件
  5. 如果在这个过程中发生了异常,会发布listeners.failed(context, exception); 事件
  6. 最后会发布listeners.running(context);事件,代表spring boot 启动完成

        在第六点中,如果在发布listeners.running(context);事件的过程中出现异常,不会发布listeners.failed(context, exception); 事件:

       

        我们也可以模拟一下上述过程:

        事件发布器SpringApplicationRunListener是一个接口,其与子类的对应关系是放在spring.factories的配置文件中:

public class A34 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        SpringApplication springApplication = new SpringApplication(A34.class);
        springApplication.addListeners(new ApplicationListener<ApplicationEvent>() {
            @Override
            public void onApplicationEvent(ApplicationEvent event) {
                System.out.println(event.getClass());
            }
        });

        //演示事件发送器,获取事件发送器类名
        //事件发送器是一个接口,和子类的对应关系存放在spring.factories配置文件中
        List<String> factoryNames = SpringFactoriesLoader.loadFactoryNames(SpringApplicationRunListener.class, A34.class.getClassLoader());
        for (String name : factoryNames) {
            System.out.println(name);
            //创建事件发布器实现类
            Class<?> clazz = Class.forName(name);
            Constructor<?> constructor = clazz.getConstructor(SpringApplication.class, String[].class);
            EventPublishingRunListener publishingRunListener = (org.springframework.boot.context.event.EventPublishingRunListener) constructor.newInstance(springApplication, args);

            //模拟SpringApplication源码public ConfigurableApplicationContext run(String... args) 方法中的七个事件
            //spring boot 开始启动
            publishingRunListener.starting();
            //环境信息准备完成
            publishingRunListener.environmentPrepared(new StandardEnvironment());
            //在spring容器创建,并调用初始化器之前,发送此事件
            GenericApplicationContext context = new GenericApplicationContext();
            publishingRunListener.contextPrepared(context);
            //所有bean definition加载完毕
            publishingRunListener.contextLoaded(context);
            context.refresh();
            //spring 容器初始化完成 refresh方法调用完成,加载了所有后处理器,初始化所有单例
            publishingRunListener.started(context);
            //spring boot 启动完毕
            publishingRunListener.running(context);
            //过程中发生错误
            publishingRunListener.failed(context,new Exception("报错"));
        }
    }
}

2.2、封装args参数

        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

        用于将args参数进行封装,便于最后一步执行实现了ApplicationRunner接口的方法。(实现了CommandLineRunner或ApplicationRunner接口的方法会在boot启动时运行其中的逻辑)


2.3、创建环境对象

        ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments); 用于创建环境对象:

        getOrCreateEnvironment()方法用于根据不同的webApplication类型,去创建对应的环境对象。(webApplication类型是在构造SpringApplication时推论出的

        configureEnvironment(ConfigurableEnvironment environment, String[] args) 方法用于配置应用程序的环境:

        environment.setConversionService(new ApplicationConversionService()); 会先添加转换器:

        其中又有configureProfiles(environment, args);方法来配置环境的配置文件:

         configurePropertySources(environment, args) 方法配置环境的属性源(重点:在这一步只加入命令行参数,没有加入application.properties):

        首先从环境中获取systemProperties和systemEnvironment


2.4、配置文件读取统一命名处理

       prepareEnvironment中的 ConfigurationPropertySources.attach(standardEnvironment); 方法用于统一命名处理:

        因为配置文件在读取的过程中可能会存在这样的问题:

        我们定义了一个配置文件,每个键的格式都不一样:

        当我们不做任何处理,在读取时键名统一使用"-"分隔时,只能读取到第一个的值,因为命名和文件中不匹配。ConfigurationPropertySources.attach()  方法可以解决这样的问题。

public class Step4 {
    public static void main(String[] args) throws IOException {
        StandardEnvironment standardEnvironment = new StandardEnvironment();
        standardEnvironment.getPropertySources().addLast(new ResourcePropertySource("step4",new ClassPathResource("step4.properties")));

        // run 源码中做了统一命名ConfigurationPropertySources.attach(environment);
        ConfigurationPropertySources.attach(standardEnvironment);
        //不做任何处理 只能读取第一个 因为命名和文件中的不匹配
        System.out.println(standardEnvironment.getProperty("user.first-name"));
        System.out.println(standardEnvironment.getProperty("user.middle-name"));
        System.out.println(standardEnvironment.getProperty("user.last-name"));
    }
}

        在attach方法内部,最后会将configurationProperties加入environment的头部,以便优先被访问


2.5、EnvironmentPostProcessor功能扩展

        EnvironmentPostProcessor相当于环境对象的后处理器,可以增加一些扩展。

        prepareEnvironment 中的listeners.environmentPrepared(bootstrapContext, environment);

使用事件发布器去触发监听器,调用其中的后处理器方法:(监听器是在SpringApplication构造时加入的,但是需要等到环境对象创建后才应该触发其中的那些后处理器

       通过debug发现,在构造SpringApplication时已经加入了对于环境对象后处理器的监听器

        关于后处理器的实现关系也是定义在spring.factories文件中的:

         EnvironmentPostProcessorApplicationListener就是去读取EnvironmentPostProcessor中的后处理器


2.6、将环境中的键值和SpringApplication中的属性匹配

         prepareEnvironment 中的bindToSpringApplication(environment); 用于将环境中的键值和SpringApplication中的属性匹配:

        读取配置文件中以spring.main开头的键,并且与SpringApplication中的成员变量绑定:

        上面配置文件中的键都是SpringApplication中的成员变量:


2.7、输出Banner图标

        run()方法中的printBanner(environment); 用于在控制台或日志中输入图标:


2.8、创建容器

        在环境准备完成并发布listeners.environmentPrepared(environment); 事件后,会创建容器:

 

          createApplicationContext() 方法根据构造SpringApplication推断的不同的类型有不同的实现:


2.9、准备容器

        prepareContext(context, environment, listeners, applicationArguments, printedBanner); 方法用于准备容器:

        在准备容器时又会回调构造SpringApplication时编写的初始化器中的增强逻辑:


2.10、加载Bean定义

         prepareContext方法中的load(context, sources.toArray(new Object[0]));

        这段代码大致的意思是,加载应用程序的配置源,并根据需要设置相关的配置,然后执行加载操作。

        根据不同的配置类型去分派对应的加载方法:


2.11、refresh

        执行run中的refreshContext()方法:

        调用refresh方法,加载配置、初始化所有单例 、重新启动应用程序上下文。

        在这之前还会判断,如果this.registerShutdownHook为true,那么会通过 shutdownHook.registerApplicationContext(context);方法为当前的应用程序上下文注册一个关机钩子(shutdown hook)这个钩子用于在应用程序关闭时执行一些清理操作或释放资源。


2.12、执行初始化方法

        run方法中的callRunners 用于执行初始化方法中编写的逻辑:

        “2.2、封装args参数”的作用就体现在此:

        ApplicationRunner需要将args参数包装成为ApplicationArguments类型:

        虽然无论是ApplicationRunner还是CommandLineRunner,调用callRunner() 方法时传递的args参数类型都是ApplicationArguments。但是不代表CommandLineRunner需要ApplicationArguments参数类型:

        会取出原本的String args 参数

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

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

相关文章

【微信小程序开发】微信小程序注册,配置开发者工具

准备工作 微信小程序小程序开发流程 开发过程注册小程序开发者工具开发界面介绍 微信小程序 一种新的开发能力&#xff0c;可以在微信内被便捷的获取和传播&#xff0c;具有出色的用户体验 地址&#xff1a;https://mp.weixin.qq.com/ 注册微信小程序 在进行开发之前我们应该…

从开发角度理解漏洞成因(02)

文章目录 文件上传类需求文件上传漏洞 文件下载类需求文件下载漏洞 扩展 留言板类&#xff08;XSS漏洞&#xff09;需求XSS漏洞 登录类需求cookie伪造漏洞万能密码登录 持续更新中… 文章中代码资源已上传资源&#xff0c;如需要打包好的请点击PHP开发漏洞环境&#xff08;SQL注…

如何使用高德地图的 Loca 展示 gpx 文件的 3D 路径,Loca.LineLayer

如何使用高德地图的 Loca 展示 gpx 文件的 3D 路径&#xff0c;Loca.LineLayer 找寻了好久&#xff0c;终于将这个展示 3D 路径的功能实现了。 在线实例&#xff1a; http://kylebing.cn/tools/map/#/gpx/gpx-viewer-3d 这里是用于展示 gpx 路径&#xff0c;关于 gpx 的相关知…

Voice Conversion、DreamScene、X-SLAM、Panoptic-SLAM、DiffMap、TinySeg

本文首发于公众号&#xff1a;机器感知 Voice Conversion、DreamScene、X-SLAM、Panoptic-SLAM、DiffMap、TinySeg Converting Anyones Voice: End-to-End Expressive Voice Conversion with a Conditional Diffusion Model Expressive voice conversion (VC) conducts speak…

速卖通自养号测评海外环境:成本、步骤、技巧全掌握

相信不少涉足跨境业务的企业和商家都对速卖通耳熟能详。作为当下炙手可热的跨境电商平台&#xff0c;速卖通在国内电商市场渐趋饱和的背景下&#xff0c;吸引了众多国内卖家的目光。他们纷纷入驻速卖通&#xff0c;希望借助这一平台的力量&#xff0c;成功打通跨境业务渠道。然…

腾讯会议崩溃解决

突然腾讯会议就罢工了,腾讯会议的主界面可以登陆上去,不会异常退出: 这时无论是通过别人提供的会议号“加入会议” 还是 “快速会议”,都会出现下面的异常,并崩溃退出: 在网上搜“SteinwayMSVCRT”导致的腾讯会议的错误,会告诉你使用金山毒霸的XX医生解决,下载了金山毒…

新代数控Syntec网络IP配置设定教程

点击面板【维护】→【网络设定】→【IP地址取得方法&#xff1a;直接指定IP地址】→【IP地址&#xff1a;输入采集需要设定的IP】→【子网掩码&#xff1a;255.255.255.0】→【预设网关】 输入方法&#xff1a;点击面板上的【ENTER】输入键&#xff0c;输入相关参数即可。

git使用注意事项事项

以下操作均在gitee平台上实现 文章目录 1、本地仓库和远程仓库有冲突2、git提交自动忽略某些文件3、git无法push提交到远程仓库 1、本地仓库和远程仓库有冲突 在web端修改了文件内容或者删除了文件&#xff0c;本地仓库需要重新把远程仓库拉取到本地&#xff0c;或者强制提交到…

Mars3d实现用一个button控制一个map.control的显示与隐藏

原生js,想做一个button,控制比如compass的显示与隐藏 点一下显示 再次单击的时候就隐藏掉 写了一个function控制显示隐藏 function addCompass(){ if(compass.showtrue) { compass.showfalse; } else{ compass.showtrue; } } 功能示例(Vue版) | Mars3D三维可视化平台 | 火星…

面试中算法(无序数组排序后最大相邻差)

有一个无序整型数组&#xff0c;求该数组排序后的任意两个相邻元素的最大差值&#xff1b;要求时间复杂度和空间复杂度尽可能低。 &#xff08;1&#xff09;任意一种时间复杂度为O (nlogn&#xff09;的排序算法&#xff08;如快速排序&#xff09;给原数组排序&#xff0c;然…

知识库工具:付费的HelpLook AI知识库比免费的牵牛易帮好在哪里

在知识管理的领域中&#xff0c;选择合适的知识库工具对于企业来说很重要。市面上有很多知识库产品&#xff0c;有付费的和免费的&#xff0c;但是还是有很多企业会选择使用付费的&#xff0c;而不是免费的。这是为什么呢&#xff1f;这就是今天要探讨的问题&#xff0c;下面就…

机器学习(二) ----------K近邻算法(KNN)+特征预处理+交叉验证网格搜索

目录 1 核心思想 1.1样本相似性 1.2欧氏距离&#xff08;Euclidean Distance&#xff09; 1.3其他距离 1.3.1 曼哈顿距离&#xff08;Manhattan Distance&#xff09; 1.3.2 切比雪夫距离&#xff08;Chebyshev distance&#xff09; 1.3.3 闵式距离&#xff08;也称为闵…

1.4 初探JdbcTemplate操作

实战目的 掌握Spring框架中JdbcTemplate的使用&#xff0c;实现对数据库的基本操作。理解数据库连接池的工作原理及其在实际开发中的重要性。通过实际操作&#xff0c;加深对Spring框架中ORM&#xff08;对象关系映射&#xff09;的理解。 关键技术点 JdbcTemplate操作&…

AI人才争夺战,华尔街入局:豪掷百万美元年薪抢人 | 最新快讯

量子位公众号 QbitAI 继硅谷之后&#xff0c;华尔街也入局“AI 人才争夺大战”。 他们的目标非常明确——抢的就是高精尖的 AI 专家。 △图源&#xff1a;Business Insider 现在这条“街”上&#xff0c;不论是银行、对冲基金还是私募股权公司都已纷纷下场&#xff0c;可谓是豪…

Netty进阶-协议设计与解析

Netty进阶 一、黏包半包的深入理解&#xff08;本质原因&#xff1a;TCP是流式协议&#xff0c;消息无边界&#xff09;1、TCP滑动窗口2、协议设计与解析2.1、Redis协议2.2、HTTP协议2.3、自定义协议2.3.1、自定义协议要求2.3.2、自定义消息对象&#xff08;编解码器、消息抽象…

谷歌明年6月关闭 Google Fit 运动记录API,要求开发者迁移至Android Health平台 | 最新快讯

5 月 6 日消息&#xff0c;谷歌近日发布官方新闻稿&#xff0c;宣布将在明年 6 月使用 Android Health 平台取代 Google Fit 运动记录 API&#xff0c;开发人员应当尽早启动迁移计划。 谷歌自 2022 年起逐渐扩大对 Android Health 平台的投资&#xff0c;旨在减少平台碎片化&am…

Python turtle库 实现 随机彩色文字平面批量输出

# -*- coding: utf-8 -*- """ Spyder Editor This is a temporary script file. """ import turtle import random import turtle as t t.colormode(255) turtle.bgcolor("white") h255 l50#字号 m60#间隔 n500 t.penup() turtle.hide…

2024-05-06 问AI: 介绍一下深度学习中的LSTM网络

文心一言 当谈到深度学习中的LSTM&#xff08;Long Short-Term Memory&#xff09;网络时&#xff0c;它是一种特殊的循环神经网络&#xff08;RNN&#xff09;架构&#xff0c;旨在解决传统RNN在处理长序列时遇到的梯度消失和梯度爆炸问题。LSTM网络因其能够捕捉序列数据中的…

面试官:关于HTTPS/HTTP2/HTTP3你懂多少?

公众号&#xff1a;程序员白特&#xff0c;欢迎一起交流学习~ HTTPS是什么 HTTP为什么不安全&#xff1f; https被认为是通信安全的http&#xff0c;除了http多了s和默认端口改成了443之外&#xff0c;其他都是沿用的http&#xff08;除了明文和不安全&#xff09;&#xff0…

Qt QInputDialog详解

1.简介 QInputDialog是一个对话框类&#xff0c;用于从用户那里获取一个单一的值。这个值可以是字符串、数字、或者一个列表中的选项。QInputDialog提供了一个方便的方式来快速创建一个输入对话框&#xff0c;无需自己从头开始构建。 QInputDialog支持多种输入类型&#xff1…