spring boot原理分析

news2024/11/19 1:47:07

总体流程在这里插入图片描述

prepareEnvironment里会生成基本的propertySource列表,当然后续还可能会改,比如apollo会在refreshContext时添加自己的propertySource。

prepareContext里会调initializer初始化ApplicationContext,接着加载bean定义。

refreshContext是重中之重,最终会调用到ApplicationContext的refresh方法,它会创建ConfigurableListableBeanFactory类型的bean管理类对象,在此之上做各种定制处理,包括:

  • BeanFactoryPostProcessor,spring容器加载了bean的定义文件之后,在bean实例化之前执行的定制行为
  • BeanPostProcessor,在spring容器实例化bean之后,在执行bean的初始化方法前后,添加定制行为

spring.factories配置文件

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.sources = new LinkedHashSet();
        this.bannerMode = Mode.CONSOLE;
        this.logStartupInfo = true;
        this.addCommandLineProperties = true;
        this.addConversionService = true;
        this.headless = true;
        this.registerShutdownHook = true;
        this.additionalProfiles = new HashSet();
        this.isCustomEnvironment = false;
        this.lazyInitialization = false;
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
       //从spring.factories文件中找出key为ApplicationContextInitializer的类并实例化后设置到SpringApplication的initializers属性中。这个过程也就是找出所有的应用程序初始化器
    this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
     从spring.factories文件中找出key为ApplicationListener的类并实例化后设置到SpringApplication的listeners属性中。这个过程就是找出所有的应用程序事件监听器    
    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
    //这里就是咱们的XXXApplication类
        this.mainApplicationClass = this.deduceMainApplicationClass();
    }

spring.factories的作用:

其中指定的 系统组件可在springboot启动时加载,比如:

其中的ApplicationContextInitializer和ApplicationListener是无条件加载。

其中的@Configuration组件则依赖@EnableAutoConfiguration注解才会加载。但一旦我们配置了@EnableAutoConfiguration,这些@Configuration组件就可以无视@ComponentScan规定的扫描路径(默认只是我们的业务包),强行加载。

springboot机制会遍历整个 spring-boot 项目的 classpath 下 的 spring.factories文件。也就是说我们可以在自己的 jar 中配置 spring.factories 文件,不会影响到其它地方的配置,也不会被别人的配置覆盖。

spring-boot-autoconfigure里的spring.factories的例子:

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\

ApplicationEvent

SpringApplication启动事件列表:

  • ApplicationStartingEvent:SpringApplication准备启动事件
  • ApplicationEnvironmentPreparedEvent :SpringApplication环境准备事件
  • ApplicationContextInitializedEvent:ApplicationContext初始化完成事件
  • ApplicationPreparedEvent :ApplicationContext加载bean定义完成事件
  • ApplicationStartedEvent :ApplicationContext实例化bean完成事件
  • ApplicationReadyEvent : SpringApplication就绪事件(可以提供服务了)
  • ApplicationFailedEvent : SpringApplication启动失败事件

当然,Spring Boot还有其他事件,例如:

WebServerInitializedEvent事件

30s一次的eureka心跳事件:

RefreshScopeRefreshedEvent

ApplicationRunListener

ApplicationRunListener里会持有所有的ApplicationListener:

public EventPublishingRunListener(SpringApplication application, String[] args) {
        this.application = application;
        this.args = args;
    //ApplicationEventMulticaster其实是SimpleApplicationEventMulticaster
        this.initialMulticaster = new SimpleApplicationEventMulticaster();
        Iterator var3 = application.getListeners().iterator();

        while(var3.hasNext()) {
            ApplicationListener<?> listener = (ApplicationListener)var3.next();
            //持有所有的ApplicationListener
            this.initialMulticaster.addApplicationListener(listener);
        }

    }

ApplicationListener

我们可以派生GenericApplicationListener类,实现自己的自定义ApplicationListener,只要实现两个方法即可:

  • supportsEventType
  • onApplicationEvent

这是GenericApplicationListener的定义:

public interface GenericApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered {

	/**
	 * Determine whether this listener actually supports the given event type.
	 * @param eventType the event type (never {@code null})
	 */
	boolean supportsEventType(ResolvableType eventType);

	......

	/**
	 * Determine this listener's order in a set of listeners for the same event.
	 * <p>The default implementation returns {@link #LOWEST_PRECEDENCE}.
	 */
	@Override
	default int getOrder() {
		return LOWEST_PRECEDENCE;
	}

}

一些用于spring启动阶段的ApplicationListener只能通过spring.factories文件配置生效,spring启动完成后的ApplicationEvent则可通过bean注册ApplicationListener来响应

常见的ApplicationListener列表:

0 = {BootstrapApplicationListener@2943} 
1 = {LoggingSystemShutdownListener@2944} 
2 = {CloudFoundryVcapEnvironmentPostProcessor@2945} 
3 = {ConfigFileApplicationListener@2946} 
4 = {AnsiOutputApplicationListener@2947} 
5 = {LoggingApplicationListener@2948} 
6 = {BackgroundPreinitializer@2949} 
7 = {ClasspathLoggingApplicationListener@2950} 
8 = {RestartListener@2951} 
9 = {CustomizedAppListener@2952} //这个是我用来测试的listener
10 = {DelegatingApplicationListener@2953} 
11 = {ParentContextCloserApplicationListener@2954} 
12 = {EnableEncryptablePropertiesBeanFactoryPostProcessor@2955} 
13 = {ServiceApplicationStartingListener@2956} 
14 = {ClearCachesApplicationListener@2957} 
15 = {FileEncodingApplicationListener@2958} 
16 = {LiquibaseServiceLocatorApplicationListener@2959} 

BootstrapApplicationListener的问题我们在“环境配置加载了两次”的地方再讲,这块比较复杂。

ConfigFileApplicationListener和EnableEncryptablePropertiesBeanFactoryPostProcessor,前者加载application.yml配置文件,后者则对配置进行加密(但不是在prepareEnvironment里做的,而是在refreshContext里做的)。

ApplicationContextInitializer

ApplicationContext容器初始化器,接口如下:

public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
    void initialize(C applicationContext);
}

我们也可以派生该接口,定制我们自己的行为,比如ApolloApplicationContextInitializer,负责构建apollo的ApolloBootstrapPropertySources:

public void initialize(ConfigurableApplicationContext context) {
    //检查apollo.bootstrap.enabled是否设置,设置了才做真正的初始化动作
        ConfigurableEnvironment environment = context.getEnvironment();
        if (!(Boolean)environment.getProperty("apollo.bootstrap.enabled", Boolean.class, false)) {
            logger.debug("Apollo bootstrap config is not enabled for context {}, see property: ${{}}", context, "apollo.bootstrap.enabled");
        } else {
            logger.debug("Apollo bootstrap config is enabled for context {}", context);
            this.initialize(environment);
        }
    }

    protected void initialize(ConfigurableEnvironment environment) {
        
        if (environment.getPropertySources().contains("ApolloBootstrapPropertySources")) {
            //如果环境里的ApolloBootstrapPropertySources已就绪,打个日志就结束
            DeferredLogger.replayTo();
        } else {
            String namespaces = environment.getProperty("apollo.bootstrap.namespaces", "application");
            logger.debug("Apollo bootstrap namespaces: {}", namespaces);
            List<String> namespaceList = NAMESPACE_SPLITTER.splitToList(namespaces);
            ConfigUtil configUtil = (ConfigUtil)ApolloInjector.getInstance(ConfigUtil.class);
            Object composite;
            //如无ApolloBootstrapPropertySources,创建一个
            if (configUtil.isPropertyNamesCacheEnabled()) {
                composite = new CachedCompositePropertySource("ApolloBootstrapPropertySources");
            } else {
                composite = new CompositePropertySource("ApolloBootstrapPropertySources");
            }

            Iterator i$ = namespaceList.iterator();

            while(i$.hasNext()) {
                String namespace = (String)i$.next();
                //从apollo服务器获得namespace下的配置项列表
                Config config = ConfigService.getConfig(namespace);
                //将apollo的配置项加到ApolloBootstrapPropertySources里去
                ((CompositePropertySource)composite).addPropertySource(this.configPropertySourceFactory.getConfigPropertySource(namespace, config));
            }
            //最后将ApolloBootstrapPropertySources放到propertySource列表的开头(表示优先级最高) 
            environment.getPropertySources().addFirst((PropertySource)composite);
        }
    }

注意:ApplicationContextInitializer的配置也要通过spring.factories文件生效

常见的initializer列表:

1 = {BootstrapApplicationListener$AncestorInitializer@5536} 
2 = {SharedMetadataReaderFactoryContextInitializer@3017} 
3 = {ApolloApplicationContextInitializer@3018} 
4 = {DelegatingApplicationContextInitializer@3019} 
6 = {ContextIdApplicationContextInitializer@3021} 
7 = {ConditionEvaluationReportLoggingListener@3022} 
8 = {ConfigurationWarningsApplicationContextInitializer@3023} 
9 = {RSocketPortInfoApplicationContextInitializer@3024} 
10 = {ServerPortInfoApplicationContextInitializer@3025} 
11 = {PropertySourceBootstrapConfiguration@5537} 
12 = {EnvironmentDecryptApplicationInitializer@5538} 
13 = {EnableEncryptablePropertiesConfiguration$$EnhancerBySpringCGLIB$$cff3bf92@5539} 

refreshContext阶段

refreshContext实际调用的是AbstractApplicationContext.refresh方法,后者定义如下:

public void refresh() throws BeansException, IllegalStateException {
        synchronized(this.startupShutdownMonitor) {
            this.prepareRefresh();
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            this.prepareBeanFactory(beanFactory);

            try {
                this.postProcessBeanFactory(beanFactory);
                //处理BeanFactoryPostProcessor
                this.invokeBeanFactoryPostProcessors(beanFactory);
                //处理BeanPostProcessor
                this.registerBeanPostProcessors(beanFactory);
                this.initMessageSource();
                this.initApplicationEventMulticaster();
                //处理其他的bean
                this.onRefresh();
                this.registerListeners();
                //实例化所有的bean
                this.finishBeanFactoryInitialization(beanFactory);
                this.finishRefresh();
            } catch (BeansException var9) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
                }

                this.destroyBeans();
                this.cancelRefresh(var9);
                throw var9;
            } finally {
                this.resetCommonCaches();
            }

        }
    }

ConfigurableListableBeanFactory

默认实现是DefaultListableBeanFactory类,它其实同时实现了ConfigurableListableBeanFactory和BeanDefinitionRegistry两个接口

public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements ConfigurableListableBeanFactory, BeanDefinitionRegistry

本质上,它是一个bean的管理类,负责:

  • bean定义的增删改查
  • bean实例的查找与创建(其getBean方法在找不到bean实例时会调用init-method去创建出实例来)

bean定义的后处理

实现在PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors里。如前所述,它在refreshContext阶段里调用。

bean定义的后处理放在了实现下面两个接口的特殊bean里:

  1. BeanFactoryPostProcessor:用来修改Spring容器中已经存在的bean,使用ConfigurableListableBeanFactory对bean进行处理
  2. BeanDefinitionRegistryPostProcessor:继承BeanFactoryPostProcessor,使用BeanDefinitionRegistry对bean的定义进行处理

考虑到默认实现DefaultListableBeanFactory类,既是ConfigurableListableBeanFactory,也是BeanDefinitionRegistry,所以传给BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor的其实是同一个beanFactory对象。

注意

BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor也都是注册到ApplicationContext里的bean(不声明为bean不能被ApplicationContext识别!),不过是用来修改普通bean行为的“特殊bean”,所以它们的实例化时间肯定早于普通bean

执行顺序:BeanDefinitionRegistryPostProcessor接口的postProcessBeanDefinitionRegistry方法 早于 
BeanFactoryPostProcessor接口的postProcessBeanFactory方法

两者的执行时机都是在普通bean实例化之前

与BeanDefinitionRegistryPostProcessor只能修改bean定义不同,BeanFactoryPostProcessor甚至可以修改bean实例本身,因它可调到getBean方法!

@Configuration注解处理

特别的,@Configuration注解也是在bean的后处理这个阶段进行的,用的是ConfigurationClassPostProcessor这个bean,它实现了BeanDefinitionRegistryPostProcessor接口。而且,由于它同时实现了PriorityOrdered接口,所以它是第一个被执行的BeanDefinitionRegistryPostProcessor

ConfigurationClassPostProcessor处理完@Configuration注解后,会刷新相应的配置bean定义,等到finishBeanFactoryInitialization()里再统一实例化。

所以,@Configuration注解的处理在invokeBeanFactoryPostProcessors里,而其实例化则在finishBeanFactoryInitialization()里,更具体的是在DefaultListableBeanFactory.preInstantiateSingletons()里。

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

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

相关文章

深入源码理解redis数据结构(一)

文章目录 一. 动态字符串SDS二. IntSet三. Dict 一. 动态字符串SDS 我们都知道Redis中保存的Key是字符串&#xff0c;value往往是字符串或者字符串的集合。可见字符串是Redis中最常用的一种数据结构。不过Redis没有直接使用C语言的字符串&#xff0c;因为C语言字符串存在着很多…

LVS负载均衡集群--DR模式

一、LVS-DR集群介绍 LVS-DR&#xff08;Linux Virtual Server Director Server&#xff09;工作模式&#xff0c;是生产环境中最常用的一 种工作模式。 1、LVS-DR 工作原理 LVS-DR 模式&#xff0c;Director Server 作为群集的访问入口&#xff0c;不作为网关使用&#xff0…

《程序员面试金典(第6版)》面试题 16.11. 跳水板

题目描述 你正在使用一堆木板建造跳水板。有两种类型的木板&#xff0c;其中长度较短的木板长度为shorter&#xff0c;长度较长的木板长度为longer。你必须正好使用k块木板。编写一个方法&#xff0c;生成跳水板所有可能的长度。 返回的长度需要从小到大排列。 示例 1 输入&a…

Request和Response应用

ServletRequest应用 ServletRequest应用非常广泛&#xff0c;下面是一些例子&#xff1a; 获取请求参数&#xff1a;可以使用HttpServletRequest的getParameter()方法获取请求参数。 获取请求头信息&#xff1a;可以使用HttpServletRequest的getHeader()方法获取请求头信息。…

数据结构(六)—— 二叉树(3)

文章目录 题1 589 N 叉树的前序遍历2 226 翻转二叉树递归迭代 3 101 对称二叉树递归迭代 4 104 二叉树的最大深度层序遍历直接解决递归 5 111 二叉树的最小深度层序遍历递归 6 222 完全二叉树的节点个数递归遍历 7 110 平衡二叉树递归 题 递归三部曲 1、确定递归函数的参数和返…

如何使用 PyTorch 进行半精度、混(合)精度训练

https://featurize.cn/notebooks/368cbc81-2b27-4036-98a1-d77589b1f0c4 nvidia深度学习加速库apex简单介绍 NVIDIA深度学习加速库Apex是一个用于PyTorch的开源混合精度训练工具包&#xff0c;旨在加速训练并减少内存使用。Apex提供了许多用于混合精度训练的工具&#xff0c;…

【Python基础入门学习】Python函数与变量的使用

python语法 1. 函数的快速体验2. 函数的基本使用2.1 函数的定义2.2 函数的调用2.3 第一个函数演练2.4 PyCharm 的调试工具2.5 函数的文档注释 3. 函数的参数3.1 函数参数的使用3.2 函数参数的作用3.3 形参和实参 4. 函数的返回值5. 函数的嵌套使用6 使用模块中的函数6.1 第一个…

码出高效:Java开发手册笔记(线程池及其源码)

码出高效&#xff1a;Java开发手册笔记&#xff08;线程池及其源码&#xff09; 码出高效&#xff1a;Java开发手册笔记&#xff08;线程池及其源码&#xff09; 码出高效&#xff1a;Java开发手册笔记&#xff08;线程池及其源码&#xff09;前言一、线程池的作用线程的生命周…

剑指 Offer:003 前 n 个数字二进制中 1 的个数

题目&#xff1a; 给定一个非负整数 n&#xff0c;请计算 0 到 n 之间的每个数字的二进制表示中 1 的个数&#xff0c;并输出一个数组 示例&#xff1a; 1、 输入: n 2 输出: [0,1,1] 解释: 0 --> 0 1 --> 1 2 --> 10 2、 输入: n 5 输出: [0,1,1,2,1,2] 解释: 0 …

第2关:用flex生成PL语言的词法分析器

任务描述 经过上个任务的磨砺&#xff0c;相信大家已经熟悉了lex/flex的使用。这一次我们将利用flex工具生成PL语言的词法分析器&#xff0c;要求输入一个PL语言源程序文件demo.pl&#xff0c;输出一个文件tokens.txt&#xff0c;该文件包括每一个单词及其种别枚举值&#xff0…

【五一创作】Qt quick基础1(包含基本元素Text Image Rectangle的使用)

Qt quick基础1&#xff08;包含基本元素Text Image Rectangle的使用&#xff09; 目录 Qt quick基础1&#xff08;包含基本元素Text Image Rectangle的使用&#xff09;前言qt中有直接设计ui的拖拽式的widget&#xff0c;为什么还需要Qtquick?QML语言Qt 版本创建一个Qt quick项…

两分钟学会 制作自己的浏览器 —— 并将 ChatGPT 接入

前期回顾 分享24个强大的HTML属性 —— 建议每位前端工程师都应该掌握_0.活在风浪里的博客-CSDN博客2分享4个HTML5 属性&#xff0c;开发必备https://blog.csdn.net/m0_57904695/article/details/130465836?spm1001.2014.3001.5501 &#x1f44d; 本文专栏&#xff1a;开发…

一文解决MySQL突击面试,关键知识点总结

文章目录 MySQL重要知识点回顾一、索引1. 为什么需要索引2. 索引的结构3. 避免索引失效3.1 联合索引不满足最左匹配原则3.2 隐式转换3.3 like查询3.4 索引列存在运算或者使用函数3.5 优化器 4. 执行计划4.1 type4.2 key4.3 rows4.4 extra 5. 建立索引5.1 什么情况下应该建索引&…

【Unity入门】24.碰撞检测

【Unity入门】碰撞检测 大家好&#xff0c;我是Lampard~~ 欢迎来到Unity入门系列博客&#xff0c;所学知识来自B站阿发老师~感谢 &#xff08;一&#xff09;碰撞体 &#xff08;1&#xff09;Collider组件 上节课我们有学习到&#xff0c;unity的物理系统提供了更方便的碰撞…

SPSS如何制作基本统计分析报表之案例实训?

文章目录 0.引言1.制作在线分析处理报告2.制作个案摘要报告3.制作行形式摘要报告4.制作列形式摘要报告 0.引言 因科研等多场景需要进行绘图处理&#xff0c;笔者对SPSS进行了学习&#xff0c;本文通过《SPSS统计分析从入门到精通》及其配套素材结合网上相关资料进行学习笔记总结…

全注解下的SpringIoc 续5-Bean的作用域

Bean的作用域主要有以下5种&#xff1a; 因为globalSession的作用域实践中基本不使用&#xff0c;所以这里就不对其过多介绍了。 另外application的作用域也完全可以用singleton作用域来代替&#xff0c;所以这里也不对其过多介绍了。 所以&#xff0c;我们主要看看singleton、…

Java——IO:输入输出流技术

简介 在java中&#xff0c;数据的输入输出都是以流的方式来处理。 流&#xff08;Stream&#xff09;&#xff0c;是一个抽象的概念&#xff0c;是指一连串的数据&#xff08;字符或字节&#xff09;&#xff0c;是以先进先出的方式发送信息的通道。 JDK中与输入/输出有关的…

Renesas瑞萨A4M2和STM32 CAN通信

刚好拿到一块瑞萨开发板&#xff0c;捣鼓玩下CAN&#xff0c;顺便试下固件升级。 A4M2 工程创建 详细可以参考&#xff0c;我之前写的文章 Renesa 瑞萨 A4M2 移植文件系统FAT32 CAN0 配置信息&#xff0c;使能FIFO&#xff0c;接收标准帧 ID为0x50&#xff0c;数据帧。 代…

汇编语言学习笔记六

flag 寄存器 CF:进位标志位&#xff0c;产生进位CF1&#xff0c;否则为0 PF:奇偶位&#xff0c;如010101b&#xff0c;则该数的1有3个&#xff0c;则PF0,如果该数的1的个数为偶数&#xff0c;则PF1。0也是偶数 ZF:在相关指令执行后&#xff08;运算和逻辑指令&#xff0c;传送指…

yml、xml、json文件

目录 一、yml &#xff08;1&#xff09;注释 &#xff08;2&#xff09;内容语法 &#xff08;3&#xff09;取名规范 二、xml &#xff08;1&#xff09;注释 &#xff08;2&#xff09;内容语法 声明头 标签 关于cdata 三、json &#xff08;1&#xff09;注释 …