spring高级篇(九)

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>() {@Overridepublic 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>() {@Overridepublic 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>() {@Overridepublic 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.mzph.cn/web/7045.shtml

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

相关文章

MySQL商城数据表(80-84)

80商品规格值表 DROP TABLE IF EXISTS niumo_spec_items; CREATE TABLE niumo_spec_items (itemId int(11) NOT NULL AUTO_INCREMENT COMMENT 自增ID,shopId int(11) NOT NULL DEFAULT 0 COMMENT 店铺ID,catId int(11) NOT NULL DEFAULT 0 COMMENT 类型ID,goodsId int(11) NOT…

【Linux IO基础】缓冲区

概念 缓冲区的主要作用是提高效率 --- 提高使用者的效率&#xff0c;因为有缓冲区的存在&#xff0c;我们可以积累一部分再统一发送&#xff0c;提高发送的效率。 刷新方式 缓冲区因为能够暂存数据&#xff0c;必定要有一定的刷新方式&#xff1a; 一般策略&#xff1a; 无…

Flask应用的部署和使用,以照片分割为例。

任务是本地上传一张照片&#xff0c;在服务器端处理后&#xff0c;下载到本地。 服务器端已经封装好了相关的程序通过以下语句调用 from amg_test import main from test import test main() test() 首先要在虚拟环境中安装flask pip install Flask 文件组织架构 your_pro…

基于Spring Boot的民宿管理平台设计与实现

基于Spring Boot的民宿管理平台设计与实现 开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/idea 系统部分展示 前台首页功能界面图&#xff0c;在系统首页可以查看首页…

【软件开发规范篇】JAVA后端开发编程规范

作者介绍&#xff1a;本人笔名姑苏老陈&#xff0c;从事JAVA开发工作十多年了&#xff0c;带过大学刚毕业的实习生&#xff0c;也带过技术团队。最近有个朋友的表弟&#xff0c;马上要大学毕业了&#xff0c;想从事JAVA开发工作&#xff0c;但不知道从何处入手。于是&#xff0…

BeanUtils.copyProperties作用

文章目录 一、作用二、举例&#xff08;1&#xff09;新建两个实体类&#xff08;2&#xff09;pom引入依赖&#xff08;3&#xff09;main方法测试&#xff08;4&#xff09;结果输出&#xff08;5&#xff09;结论 一、作用 用来做对象间的copy。 二、举例 &#xff08;1&…

Ubuntu部署前后端分离项目(前端vue,后端jar包)

一. Vue部署 1. 服务器安装node.js 2. 安装nginx 3. 上传vue包 将打包后的vue静态资源包dist文件夹&#xff0c;上传到服务器指定目录&#xff0c;并给该目录赋予相应权限。 4. 配置nginx (1) 创建vue项目配置文件 sudo vim /etc/nginx/conf.d/your-vue-project.conf (2)…

视频号小店在行业内的门槛高不高?有门槛是好事还是坏事?

大家好&#xff0c;我是电商小V 现在伴随着时代的慢慢发展&#xff0c;很多人都是想找一个好一点的创业项目&#xff0c;现在找创业项目都是找一些稍微有门槛的项目&#xff0c;没有门槛的话&#xff0c;要不然刚开始去做&#xff0c;项目就泛滥了&#xff0c;项目的红利期直接…

【AI】深度学习框架的期望与现实 机器学习编译尚未兑现其早期的一些承诺……

深度学习框架的期望与现实 机器学习编译尚未兑现其早期的一些承诺…… 来自&#xff1a;Axelera AI 资深软件工程师 Matthew Barrett 原帖是linkedin帖子&#xff1a; https://linkedin.com/posts/matthew-barrett-a49929177_i-think-its-fair-to-say-that-ml-compilation-ac…

GPT带我学-设计模式12-状态模式

啥是状态模式 状态模式是一种行为型设计模式&#xff0c;它允许一个对象在其内部状态发生改变时改变其行为。状态模式将对象的状态封装成不同的类&#xff0c;并使得对象在运行时可以动态地改变状态&#xff0c;从而改变对象的行为。状态模式的主要目的是促进代码的复用和灵活…

Terraform资源地址

在编码时有时会需要引用一些资源的输出属性或是一些模块的输出值&#xff0c;这都涉及到如何在代码中引用特定模块或是资源。另外在执行某些命令行操作时也需要显式指定一些目标资源&#xff0c;这时要掌握Terraform的资源路径规则。 一个资源地址是用以在一个庞大的基础设施中…

Python_4-远程连接Linux

文章目录 使用Python通过SSH自动化Linux主机管理代码执行ls结果&#xff1a;文件传输&#xff1a; 使用Python通过SSH自动化Linux主机管理 在系统管理与自动化运维中&#xff0c;SSH&#xff08;Secure Shell&#xff09;是一个常用的协议&#xff0c;用于安全地访问远程计算机…

FTP协议与工作原理

一、FTP协议 FTP&#xff08;FileTransferProtocol&#xff09;文件传输协议&#xff1a;用于Internet上的控制文件的双向传输&#xff0c;是一个应用程序&#xff08;Application&#xff09;。基于不同的操作系统有不同的FTP应用程序&#xff0c;而所有这些应用程序都遵守同…

富格林:累积经验阻挠黑幕之手

富格林认为&#xff0c;近年来现货黄金投资市场越发火热&#xff0c;许多投资新手纷纷涌入现货黄金市场中。不过&#xff0c;在这需要提醒大家的是要提防黑幕阻挠我们顺利盈利&#xff0c;选择正规可靠的平台进行开户&#xff0c;这样可以保证投资环境的安全稳定。下面富格林将…

水泥分类和使用方式 宁波水泥菱湖新阳水泥厂325鄞州东钱湖春晓咸祥海螺425水泥镇海骆驼庄市

水泥分类和使用方式 宁波水泥菱湖新阳水泥厂325鄞州东钱湖春晓咸祥海螺425水泥镇海骆驼庄市 水泥是一种重要的建筑材料&#xff0c;广泛应用于建筑、桥梁、隧道等各种土木工程项目中。根据不同的分类标准&#xff0c;水泥可以分为多种类型&#xff0c;每种类型的水泥都有其特定…

怎么给word文件名批量替换部分文字?word设置批量替换文字教程

批量替换Word文件名中的几个字&#xff0c;对于经常处理大量文件的人来说&#xff0c;是一项非常实用的技能。以下是一个详细的步骤指南&#xff0c;帮助你快速完成这项任务。 首先&#xff0c;你需要准备一个可以批量重命名文件的工具。市面上有很多这样的工具可供选择&#x…

win10禁止自动更新的终极方法

添加注册表值 1.运行&#xff0c;输入regedit 2.打开注册表编辑器依次进入以下路径“计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings”。 3.在Settings项中&#xff0c;新建DWORD&#xff08;32位&#xff09;值(D)&#xff0c;重命名为以下命名“Fl…

easy_ssti_ctfshow_2023愚人杯

https://ctf.show/challenges#easy_ssti-3969 2023愚人杯有提示app.zip&#xff0c;访问 https://1f660587-5340-4b20-b929-c4549d9a5d4b.challenge.ctf.show/app.zip得到压缩包&#xff0c;拿到一个py文件 可以看到参数名是name&#xff0c;对参数进行筛选&#xff0c;包含ge…

人力资源管理新视野:挖掘员工潜力,共筑卓越未来

在21世纪的商业环境中&#xff0c;企业的成功不再仅仅依赖于资本、技术和市场策略&#xff0c;而更多地依赖于其人力资源的有效管理。人力资源管理的新视野正致力于挖掘员工的潜力&#xff0c;为企业创造持续的价值&#xff0c;共筑卓越的未来。 一、员工潜力的挖掘 员工是企业…

Linux如何查看JDK的安装路径

方法1 echo $JAVA_HOME 如果配置了系统就会输出配置为JAVA_HOME的jdk的的位置&#xff0c;如果没有配置就没有输出&#xff0c;推荐使用方法2 方法2(推荐) 关键命令分别为 1. which java 2. ls -lrt /usr/bin/java &#xff08;路径依照自己的输出灵活去改&…