Spring IoC Container 原理解析

news2024/11/17 21:39:50

IoC、DI基础概念

关于IoC和DI大家都不陌生,我们直接上martin fowler的原文,里面已经有DI的例子和spring的使用示例

《Inversion of Control Containers and the Dependency Injection pattern》

https://www.martinfowler.com/articles/injection.html

我们这里只关注一些重点概念做为思考,摘一部分原文:

“As a result I think we need a more specific name for this pattern. Inversion of Control is too generic a term, and thus people find it confusing. As a result with a lot of discussion with various IoC advocates we settled on the name Dependency Injection.”

“我想我们需要给这个模式起一个更能说明其特点的名字。控制反转太宽泛了,因而常常让人迷惑。通过和一些IoC爱好者商讨之后我们把该模式叫做依赖注入”

所以说IoC是抽象的概念,常用于形容一个框架,DI则是具体实现的模式,其实可以去spring官网,看到在使用这两个词时也很讲究。

“When these containers talk about how they are so useful because they implement "Inversion of Control" I end up very puzzled. Inversion of control is a common characteristic of frameworks, so saying that these lightweight containers are special because they use inversion of control is like saying my car is special because it has wheels.”

“当这些容器的设计者说这些容器是如此的有用,因为他们实现了“控制反转”。而我却深感迷惑,控制反转是IoC框架的共有特征,如果说一个框架以实现了控制反转为特点相当于说我的汽车有轮子。”

如果说依赖注入其实只是框架最基本的功能,那么什么才是spring高级、核心功能呢?

我们去看看spring官网:

https://docs.spring.io/spring-framework/docs/current/reference/html/overview.html#overview-philosophy

关于设计理念的第一点:

“Provide choice at every level. Spring lets you defer design decisions as late as possible. For example, you can switch persistence providers through configuration without changing your code. The same is true for many other infrastructure concerns and integration with third-party APIs”

“在各个级别提供选择。Spring允许您尽可能推迟设计决策。例如,您可以通过修改配置切换持久层提供程序,而无需更改代码。许多其他基础设施以及与第三方API的集成也是如此。”

所以说spring的核心在于基于IoC为基础理念来做各种基础设施的提供者,这里的“基础设施”也可以理解为对各种中间件的抽象整合。

DI是最基础的一个功能,所有其他功能模块的地基。所以说基于DI的 各式各样的应用全家桶才是spring的核心竞争力。

IoC在平时可能看不到什么作用,但是在关键性的对接或者架构层面作用就大了:

例如事务的管理,不管jdbc还是oracle的事务实现代码如何,我们统一使用spring transaction(当然也结合了AOP),只需要修改相关的数据库配置就可以。

例如注册中心依赖的eureka,想要切换到nacos或者consul,代码同样不用改,修改相关的包引用,修改配置文件相关的配置就好了,代码都不用动。

结论:看完上面的描述还是感觉很虚,就像martin fowler说的IoC容器实现依赖注入并没什么特殊的,spring通过依赖注入为我们提供哪些支撑,以及我们如何运用依赖注入将各种服务组装成我们的系统才是更关键的,这也正是本文的关注点。

为了让下文更好的被理解,我们这里还是简单的贴一些martin fowler文章里的代码

没有IoC框架时:

需要自己进行对象的初始化,依赖对象的设置

 
 

class MovieLister... private MovieFinder finder; public MovieLister() { finder = new ColonDelimitedMovieFinder("movies1.txt"); }

如果需要修改finder实现,则需要直接修改MovieLister(高层)的代码,这样其实就是我们常说的高层依赖底层,底层实现换了,高层代码就要变。

有IoC框架时:

我们直接站在巨人肩膀,例如服务定位器一样可以实现DI,但是我们这里不去关心

我们举例习以为常的依赖注入的编码步骤:

1、 编写配置(xml文件、注解、java config)

2、 加载配置,通过配置生产对象(即时或者懒加载都可以)

3、 获取注入好的对象

Java config 配置类:

 
 

@Configuration public class MovieConfig{ @Bean MovieLister movieLister(MovieFinder movieFinder){ return new MovieLister(movieFinder); } @Bean MovieFinder movieFinder(){ return new ColonDelimitedMovieFinder (“movies1.txt”) } }

代码:

 
 

public void testWithSpring() throws Exception { ApplicationContext ctx = new AnnotationConfigApplicationContext(MovieConfig.class); MovieLister lister = (MovieLister) ctx.getBean("movieLister"); }

关键对象

为了更好的方便理解,我们尝试着将现实世界的对象一一映射到虚拟世界

现实世界

这里我们以饭店举例

编辑切换为居中

添加图片注释,不超过 140 字(可选)

几个关键的对象:

海鲜加工饭店

有自己的招牌和特色,不过注意该饭店所有食材均需要客户自带。

出单系统

客户点菜下单(到前台或者找服务员都行,因为自己带的菜还要交给饭店嘛)后自动将客户的订单打印出来,打印出来的订单除了菜名还会加上:序号、桌位号、菜品的口味等。职责就是简洁清晰的打印出订单信息,供厨师或其他人使用。

订单复核员

我们这个餐厅必须要做一个步骤,就是订单出单后再去找客户确认订单,订单确认了才能交给厨房去做,这样做的目的一来是为了避免客户误点或沟通失误,二来通过确认的沟通也提升了用户体验。

为什么我们这里这个角色叫订单复核员,而不叫服务员呢,因为我们的印象里服务员能干很多其他的事情,这样的话反而弱化了订单确认的这个关键动作。

后厨

根据送过来的食材和订单做菜,后厨关注的是如何根据订单和食材来把菜做好。

虚拟世界

图片上传失败

​重试

直接看这个图可能会有一点懵,我们后面再详细一一进行说明和讲解,请注意,我们本章节后文都是围绕该图进行讲解,此图非常重要。

现实世界

虚拟世界

说明

海鲜加工饭店

ApplicationContext

对标海鲜加工饭店,厨房里的厨子都是他的打工仔,他制定出精美的菜单来吸引食客,@Component、@Configuration、@Bean都是它的金字招牌菜,其实就是对厨子进行了包装,且有门面吸引客源。

海鲜菜单

Java config

Spring IoC独有的招牌菜:@Component@ComponentScan @Bean @Import等美味

后厨

BeanFactory

根据食材和订单做菜;没有饭店实体店则基本只能做点路边摊小买卖。

出单系统

BeanDefinitionRegistryPostProcessor

相当于出单系统,把客户想要的菜给转化到订单上,例如ConfigurationClassPostProcessor将@Component @Configuration注解类转化为BeanDefinition;基于java config就离不开他也是BeanFactoryPostProcessor的子类

订单复核员

BeanFactoryPostProcessor

确认订单信息时菜还没做,例如可以允许客户对订单做一些信息修改

订单

BeanDefinition

即订单信息,后厨要看着订单来做菜

成品

Bean

最终的产物

从上面的例子我们大致能够区分出了BeanFactory和ApplicationContext的区别。

Spring可以让我们参与到任意一个角色中:客户、海鲜加工饭店老板、出单系统、订单复核员、后厨,可以参与到其中任意环节中。

那我们可以做些什么有趣事情呢?

例如可以制定我们特色的菜单,像mybatis的@Mapper特色菜。

确认订单时给所有订单信息里加赠饮料(xml声明的bean的属性里${xx}占位符的替换)等

初步理解了这些关键对象之后,我们再深入到各个环节,看看各个环节都是怎么干的

BeanFactory

给我提供订单信息和原材料我就做,订单和食材缺一不可

让我们先聚焦后厨,因为后厨是饭店的核心。

在spring framework中,Bean的生命周期在Beanfactory里就已经闭环了

ApplicationContext只是加一些料,例如扫描java config转义成BeanDefinition给到BeanFactory,然后再添加一些BeanPostProcessor等。

注意本文重点关注的是基于主流java config配置的实现,其实xml文件的配置原理也类似,不是本文重点不做探讨。

BeanFactory的生命周期是什么,其实就是用 BeanDefinitionRegistryPostProcessor、BeanFactoryPostProcessor 这两个接口的实现类,往BeanFactory注册BeanDefinition和修改BeanFactory里的BeanDefinition及其他信息。

BeanDefinition

订单信息在虚拟世界长什么样的?它起到了承上启下非常关键的作用。

为什么一开始要先讲BeanDefinition呢,不管基于什么配置方式都需要生成BeanDefinition,在Bean的生命周期中,第一步也是getMergedBeanDefinition。

我们先来看一下BeanDefinition的类图继承关系

编辑切换为居中

添加图片注释,不超过 140 字(可选)

BeanDefinition实现类与各个场景的对应,这里我们只关注java config场景的

配置场景

BeanDefinition实现类

说明

@Component及其子类,例如:@Configuration@Service

AnnotatedGenericBeanDefinition

@Bean

ConfigurationClassBeanDefinition

@ComponentScan扫描到的类:

ScannedGenericBeanDefinition

RootBeanDefinition

将parentName的bean进行合并整合的结果

常用于xml配置文件声明的bean

GenericBeanDefinition

RootBeanDefinition(merged bean definition)

Bean生命周期中最重要的BeanDefinition实现类

因为不管中间过程中是什么BeanDefinition,不管玩出什么花样

最终在bean生命周期中都会变成 RootBeanDefinition

当整合成RootBeanDefinition 后,故而spring 会给我们留了一个统一的后置处理器: MergedBeanDefinitionPostProcessor

BeanDefinition的属性:

属性

类型

说明

parentNameChildBeanDefinition

String

相当于java的继承父类,spring最后会把所有类型的bean都重新揉到一起形成一个新的RootBeanDefinition;只有ChildBeanDefinition才有该属性值

beanClassAbstractBeanDefinition

Object

可以为string 也可以为 class<?>也可以为空,为空时基本都是要通过factoryBeanName、factoryMethodName去实例化对象

scope

String

值:singletonprototyperefresh (spring cloud)默认为空,为空时就当单例处理]singleton详见@Scope注解;@Scope注解的使用还是有点窍门的,例如@RefreshScope是什么原理?

lzyInit

Boolean

是否延迟加载ApplicationContext.refresh()时不加载,getBean()时才去加载详见@Lazy

autowireModeAbstractBeanDefinition

int

这里其实就是DI的几种注入方式了,目前java config已经很灵活,直接注解的形式去定义就行了AUTOWIRE_NO(默认值)默认装配模式, 目前非xml配置都是使用这种方式,然后程序员使用注解手动注入AUTOWIRE_BY_NAME通过set方法,并且 set方法的名称需要和bean的name一致AUTOWIRE_BY_TYPE通过set方法,并且再根据bean的类型,注入属性,是通过类型配置AUTOWIRE_CONSTRUCTOR通过构造函数注入Spring @Bean注解的autowire属性可以给我们去设置该属性,也能通过BeanFactoryPostProcessor去修改BeanDefinition的autowireMode;不过一般也用不到去修改这个。@Bean因为是注解在方法上,所以是AUTOWIRE_CONSTRUCTOR@Autowired 是在AutowiredAnnotationBeanPostProcessor里默认通过类型判断去找对应的bean,类似于AUTOWIRE_BY_TYPE,也是在这里处理@Qualifier注解的bean name查找。因为@Autowired只是一个属性值不是BeanDefinition,所有没有autowireMode属性一说

dependsOn

String[]

依赖了哪些bean,加载该bean时会先去加载依赖的bean,再来加载该bean详见@DependsOn()

autowireCandidate

Boolean

设置当前bean在被其他对象作为自动注入对象的时候,是否作为候选bean,默认true如果设为false则别的bean引用不到该bean@Scoped(“”)值不为空时,bean原始的BeanDefinition会被设置为autowireCandidate =false,会新生成一个新的beanClass为代理类class的BeanDefinition设置autowireCandidate=true,即替换了掉原来的beanDefinition

primary

Boolean

设置是不是最优先的候选bean只对使用者产生影响,对原始对象的构造不起任何影响,原始对象该生成bean还是生成;只是后面被其他地方用于时,BeanFactory去判断选择@Primary

qualifiersAbstractBeanDefinition

Map<String, AutowireCandidateQualifier>

Register a qualifier to be used for autowire candidate resolution, keyed by the qualifier's type name只对使用者产生影响,对原始对象的构造不起任何影响,原始对象该生成bean还是生成;只是后面被其他地方用于时,BeanFactory去判断选择@Qualifier 需和@Autowired一起使用

instanceSupplierAbstractBeanDefinition

Supplier<?>

实例化对象的提供者,基本用不上自定义

isFactoryBeanRootBeanDefinition

Boolean

是否实现了FactoryBean接口,只要没实现FactoryBean接口的都是false;

factoryBeanName

String

与isFactoryBean无关可以是任意的beanName,且不需要实现FactoryBean接口

factoryMethodName

String

与isFactoryBean无关通过该方法取bean,需要结合factoryBeanName进行使用

constructorArgumentValues

ConstructorArgumentValues

构造函数的定义,包含参数顺序等

propertyValues

MutablePropertyValues

常用于xml声明的bean;将xml该bean的所有property标签键值对放这里;也可以用于spring内部上下文传递一些bean的信息,就像Servlet HttpRequest.setAttribute(key,value)

initMethodName

String

初始化方法名

destroyMethodName

String

Bean销毁时触发的方法名

role

int

是由什么系统声明的beanROLE_APPLICATIONROLE_SUPPORTROLE_INFRASTRUCTURE

description

String

Bean的描述,常用于xml配置里的<description>节点

isSingleton

boolean

判断scope.equals(“singleton”)

IsPrototype

boolean

判断scope.equals(“prototype”)

isAbstract

String

是否抽象类

FactoryBean接口相关

两种方式:

实现了spring FactoryBean接口的BeanDefinition属性isFactoryBean=true(RootBeanDefinition,merge beandefinition后可见)

例如@Configuration注解生成的bean,mybatis的mapper都是用到了FactoryBean接口的内容

注意factoryBeanName、factoryMethodName 的使用是另一种实现方式,此时isFactoryBean=false

BeanDefinitionRegistryPostProcessor

根据菜单和客户下单时的信息,生成订单给后厨

作用于BeanFactory,是BeanFactory给外界留的门,具体的执行是在ApplicationContext的生命周期里。

在spring里就是将@Configuration、@Bean等配置信息解析生成BeanDefinition注册到BeanFactory。

其继承自BeanFactoryPostProcessor

编辑

添加图片注释,不超过 140 字(可选)

出单系统必须实现 BeanDefinitionRegistryPostProcessor接口方法

(ApplicationContext饭店自己也可以给后厨下单 例如 ClassPathXmlApplicationContext,就可以在applicationContext生命周期里(refresh方法内,下单给后厨之前)解析xml往Beanfactory里注册BeanDefinition)

因为也继承了BeanFactoryPostProcessor接口,所以一般也自带了确认订单的功能

例如一些典型的实现:

1、java config的关键实现类: ConfigurationClassPostProcessor

2、mybatis的 MapperScannerConfigurer,将@Mapper接口注册成BeanDefinition

 
 

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor

注:MapperScannerConfigurer只是Mybatis mapper注入的其中一种方式

ConfigurationClassPostProcessor

最重要的 BeanDefinitionRegistryPostProcessor实现类没有之一

我们用的java config都是经它之手转变为BeanDefinition,因为 BeanDefinitionRegistryPostProcessor继承自BeanFactoryPostProcessor,所以也实现了postProcessBeanFactory方法。本文的重点在于IoC的过程理解,所以本章节主要还是描述该Processor源码的实现过程。

ConfigurationClassPostProcessor其postProcessBeanFactoryRegistry方法负责注册bean,将@Configuration @ComponentScan等注解解析成BeanDefinition注册到BeanFactory;postProcessBeanFactory方法负责修改BeanDefinition,将@Configuration注解的类的BeanDefinition的beanClass替换为代理后的class name。

该 BeanDefinitionRegistryPostProcessor也不是从石头里蹦出来的,后面ApplicationContext的生命周期会讲到是何时触发的。

postProcessBeanFactoryRegistry

循环当前所有的BeanDefinition,找那些有@Configuration注解的类进行处理,也可以有内部类,一样会去加载,不过排序是跟着外层的@Configuration一起走,@Configuration也可以加@Order注解用于将所有@Configuration的bean进行排序后按从小到大的顺序加载

也可以结合@Conditional注解使用,如果不满足Conditional条件,则不加载该Configuration

一、找到当前BeanFactory里所有@Configuration注解的BeanDefinition并通过@Order进行排序

二、循环找到的BeanDefinition,将其转化为ConfigurationClass

2.1先处理如果class有嵌套类,如果有@Configuration 或@Component 注解,同样转化为独立的ConfigurationClass

2.2 @PropertySources @PropertySource的处理,添加PropertySource至environment

2.3 @ComponentScans @ComponentScan的处理,扫描java config注解。将扫描到的class转换为BeanDefinition;然后再调用上面第一步,类似于递归调用

2.4 @Import的处理

注解value值是ImportSelector接口实现类的,直接调用接口方法selectImports拿到返回String[],class类名集合,再次去调用processImports方法

注解value值是 ImportBeanDefinitionRegistrar接口实现类的,添加至ConfigutaionClass的属性importBeanDefinitionRegistrars【关键】

注解value不是上面两种接口实现类的,直接再走判断注解转化ConfiurationClass的处理

2.5 @ImportResource的处理,添加至ConfigutaionClass的属性ImportedResource

2.6找到@Bean注解的method,添加至ConfigurationClass的属性beanMethod

2.7找当前class的接口,如果有@Bean注解的method,添加至ConfigurationClass的属性beanMethod(因为java8的interface有default method的存在,所以接口方法也可以生成bean)

2.8找当前class的父类,如果有@Configuration 或@Component 注解,同样转化为独立的ConfigurationClass(同嵌套类的处理)

三、将所有ConfigurationClass转换为BeanDefinition注册到BeanFactory

四、将所有ConfigurationClass的beanMethod转换为BeanDefinition注册到BeanFactory.

五、将所有ConfigurationClass的ImportedResource转换为BeanDefinition注册到BeanFactory.

六、将所有ConfigurationClass的 importBeanDefinitionRegistrars,调用其registerBeanDefinitions方法进行BeanDefinition的注册,同BeanDefinitionRegistryPostProcessor,也是用于注册BeanDefinition【关键】

另外@Conditional可以结合@Configuration、@Bean注解进行使用,不满足条件的不加载为BeanDefinition

@Configuration

 
 

@Component public @interface Configuration {

ConfigurationClass对象关键属性(解析@Configuration、@Component注解的类后得到的结果)

属性

类型

说明

metadata

AnnotationMetadata

@Configuration类的注解集合

beanName

String

Pojo类名

beanMethods

Set<BeanMethod>

@Bean 的方法集合

importedResources

Map<String, Class<? extends BeanDefinitionReader>>

待加载的xml配置文件

importBeanDefinitionRegistrars

Map<ImportBeanDefinitionRegistrar, AnnotationMetadata>

待加载的ImportBeanDefinitionRegistrar接口用于注册BeanDefinition

@Bean

这里@Configuration或者@Component注解里的@Bean注解的方法,会注册为BeanDefinition,然后其BeanClass为空,factoryBeanName为其@Configuration或者@Component类的beanName;factoryMethodName就是 @Bean注解的方法名。

这里就是用到了BeanDefinition内容里的factoryBeanName和factoryMethodName进行处理,委托其他类(@Configuration类)来生成bean。

@Import

对后面理解spring boot AutoConfiguration至关重要

一、注解value值是ImportSelector接口实现类的,直接调用接口方法selectImports拿到返回String[],class类名集合,再次去进行扫描

二、注解value值是 ImportBeanDefinitionRegistrar接口实现类的,会调用其registerBeanDefinitions方法,进行BeanDefinition注册; 等于实现了 BeanDefinitionRegistryPostProcessor的功能。

三、注解value值不是上面两种接口实现类的,直接扫描该类

Spring boot starter自动引入的核心

 
 

@EnableAutoConfiguration public @interface SpringBootApplication {

 

@Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration {

 

public class AutoConfigurationImportSelector implements ImportSelector {

该selector会去扫描所有spring.factories文件里

org.springframework.boot.autoconfigure..EnableAutoConfiguration 属性的类

Mybatis @MapperScan

 
 

@Import(MapperScannerRegistrar.class)

Feign @EnableFeignClients

 
 

@Import(FeignClientsRegistrar.class)

这里一般都是注册的GenericBeanDefinition

postProcessBeanFactory

这里涉及到AOP的知识

处理java-config相关的注解 @Configuration @ComponentScan @Import等将他们转换成BeanDefinition并注册到BeanFactory中,具体的实现也涉及到AOP知识。因为我们关注的是整体的运作流程。

将@Configuration注解转换的BeanDefinition,BeanClass属性改成 AOP代理后生成的class,这样后面bean生命周期实例化的就是代理后的class

为什么要用代理?

 
 

@Configuration public class MovieConfiguration { @Bean public MovieLister movieListener(){ return new MovieLister (this.movieFinder ()); } @Bean public MovieFinder movieFinder(){ return new ColonDelimitedMovieFinder ("movies1.txt"); } }

而且此处通过AOP增强后,也不会面临内部方法调用AOP失效的问题(为什么?movieListener这里面调用movieFinder不是已经在super class了吗,其实并不是,这个父类其实也是被代理的,cglib的实践,因为只要是@Bean注解的方法都被代理拦截了,如果方法名没被代理的话,那就真是直接执行super的原始代码),如上最后movieListener里的movieFinder属性,和下面movieFinder方法生成的bean,是同一个bean;感兴趣的可以去看看源码

如何查看cglib增强后的代码,可以再main函数第一行加上如下设置即可

 
 

public static void main(String[] args) { System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"C://");

@Component注解(包括其子类@Service @Controller)的类也可以声明 @Bean,区别是什么呢,就是是LITE 轻量模式,不会像上面这样生成代理(@Configuration是FULL模式),即上面的 movieListenner里的movieFinder对象和 BeanFactory里的bean movieFinder 是两个不同的对象。

BeanFactoryPostProcessor

有个客户突然跑过来后厨说 我刚刚下单忘了说清蒸鲈鱼不要鱼

接单后可以对订单做的事情,主要是对订单的修改,取消基本上不可能的,就像你到饭店吃饭,中途想问问服务员有个菜没做的话能不能帮我退掉,这时候服务员肯定给你讲正在做了不能退了。

作用于BeanFactory,是BeanFactory给外界留的门,具体的执行是在ApplicationContext的生命周期里。

经典实用场景,BeanDefinition属性值变量的替换

对BeanDefinition属性值进行修改,不管哪里注册的,都会走这里

经典常用的:

PropertySourcesPlaceholderConfigurer

BeanDefinition里面如下属性值变量的替换都在这里执行(@Autowired和@Value里的变量并不在这进行替换 、SpEL也不在此处处理)

替换BeanDefinition属性值的范围和顺序如下:

parentName

beanClassName

factoryBeanName

factoryMethodName

scope

propertyValues

constructorArgumentValues

Bean的生命周期

实在是编不下去了,这个用做菜来举例的话,我头发要白一大片。这里我们就不以做菜举例了,只能以类的创建来举例了

正常创建类的流程:

 
 

(1)MovieListener listener = new MovieListener(); MovieFinder finder = new ColonDelimitedMovieFinder("movies1.txt"); (2)listener.setFinder(finder); listener.setStartTime(new Date()); (3)listener.init();

对应上面三个步骤,我们分为三个方法:

1、实例化

2、populateBean属性赋值(@Autowired的属性依赖注入都是在这里处理)

3、初始化(属性赋值完后,我们做好准备工作对外开始服务)

所以spring为了让我们参与到bean的实例化与初始化过程,特意给我们留了口

为什么要划分出多一个第二步populateBean方法呢,拆分的更细其实也是有益处的,例如spring cloud的结合@RefreshScope和@ConfigurationProperties使用的bean,就用到了bean生命周期中的destroy和initializeBean 初始化的方法,跳过了实例化和属性赋值,直接去触发

ConfigurationPropertiesBindingPostProcessor进行@ConfigurationProperties注解类的属性值的绑定(这里的属性赋值并不是@Autowired @Value这样的属性赋值,而是@ConfigurationProperties(prefix = "xxx")注解类对应的属性);因为是再次第三步初始化的initializeBean,所以第二步依赖注入的属性不会被改变,节省了成本

BeanPostProcessor

在bean的生命周期中,我们能参与到哪些环节呢,spring给我们的最主要的还是通过三类BeanPostProcessor去参与进去。

BeanPostProcessor的类的继承关系

编辑切换为居中

添加图片注释,不超过 140 字(可选)

典型常用的BeanPostProcessor如下:

CommonAnnotationBeanPostProcessor

spring中最重要的BeanPostProcessor之一

@Resource

注解属性的赋值,找到对应的bean进行属性赋值

@WebServiceRef

不常用,不解释

继承自 InitDestroyAnnotationBeanPostProcessor又包含如下两个注解的处理

@PostConstruct

执行该注解对应的方法,在何时执行请看“虚拟世界”的图例

@PreDestroy

销毁时执行改注解方法,我们要搞清楚如何出生的,再去想如何没的,这里不做解释。

AutowiredAnnotationBeanPostProcessor

Spring boot中最重要的BeanPostProcessor 没有之一

@Autowired

常见问题:

为什么spring bean 一级缓存里没有 ResourceLoader、ApplicationContext、ApplicationEventPublisher、BeanFactory相关的bean,@Autowired为什么能注入对象进去呢?

也是这里进行了代码相关的类型判断,如果是上面4种类型的,直接找到对应的对象赋值了。

@Value

${xx} #{xx} 占位符的区别是什么 就是普通属性替换和SpEL的执行

@Qualifier

需配合@Autowired使用,@Autowired在找相关的bean时,也会按@Qualifier指定的bean name去查找。

ConfigurationPropertiesBindingPostProcessor

Spring boot中重要的BeanPostProcessor

@ConfigurationProperties注解类的处理

AnnotationAwareAspectJAutoProxyCreator

AOP里用到,Spring AOP 最重要的BeanPostProcessor没有之一

@Aspect的处理,将aop扫描到的方法类进行生成aop代理,这里我们也不做过多的解释,属于AOP的内容。

下面的章节我们将正式进入bean的生命周期,这里再回顾一下我们的虚拟世界

添加图片注释,不超过 140 字(可选)

getMergeBeanDefinition

合并BeanDefinition

为什么要合并BeanDefinition,我们这里用词还是跟spring源码里的方法名保持了一致,是为了方便大家看源码时能更好去对应上。

因为java面向对象设计的概念,类是可以继承的

如下这种xml声明方式,parent就有用,生成bean时会将parent bean的属性也进行注入,这样就能使用到父类的属性和方法。

 
 

<bean id="movieListener" class="spring.MovieListener" parent="abstractMovieListener"> </bean> <bean id="abstractMovieListener" class="spring.AbstractMovieListener"> </bean>

Java config基于@Component注解时基本用不上了,因为生成的都不是ChildBeanDefinition

怎么合并,去看一下AbstractBeanDefinition的构造函数就知道各种其他类型的BeanDefinition如何转换为RootBeanDefinition,我们这里就不贴源码了。

Instantiation 实例化

什么叫实例化 new MovieListener() 这样吗

涉及到static 方法块是否需要被执行,以及构造函数是否需要被执行,spring默认都是会执行到

postProcessBeforeInstantiation

由 InstantiationAwareBeanPostProcessor触发

这里可以干些什么,有什么使用场景,如果在这里有返回对象,则会直接跳到 BeanPostProcessor.postProcessAfterInitialization方法去,等于是直接跳到生命周期的末尾,期间的生命周期都不会执行

createBeanInstance

这里呢,有什么场景暂时没发现,不过spring也给我们参与的机会=>BeanDefinition里的instanceSuplier

postProcessMergedBeanDefinition

由 MergedBeanDefinitionPostProcessor触发

这里可以干些什么,有什么使用场景,spring为什么要给我们留这个口?

在 BeanDefinitionRegistryPostProcessor注册BeanDefinition可以是任意类型,但是在bean的生命周期里(实例化之前),都会转变为RootBeanDefinition;所以这里spring给我们留了一个口,让我们还去访问RootBeanDefinition,可以用于获取信息和修改BeanDefinition信息;

首先搞清楚什么是:merged bean definition

Return a RootBeanDefinition for the given bean, by merging with the

parent if the given bean's definition is a child bean definition.

在BeanDefinition生成的时候就已经merge过了,不管如何,实例化之前会拿到mergedBeanDefinition(实际是RootBeanDefinition类型)

既然可以用于修改BeanDefinition,为什么该后置处理不在对象实例化之前给我们去调用呢?如果是修改BeanDefinition,那么其实是跟BeanFactoryPostProcessor去修改BeanDefinition是有歧义的。如果想要修改Bean的实例化的类,还是得去BeanFactoryPostProcessor,这里只能影响实例化之后的生命周期。

这里其实就是给最后一次机会,能够去修改BeanDefinition(注意这里bean已经实例化了)

AutowiredAnnotationBeanPostProcessor 和

CommonAnnotationBeanPostProcessor

在这里 postProcessMergedBeanDefinition 就是去提前缓存了每个类的 @Autowired、@Value等注解属性信息,后面postProcessProperties直接使用而已。 不到这里加缓存后面再去取也可以

到这里我们主要分析了 applyMergedBeanDefinitionPostProcessors这段代码的作用,它的执行时机是在创建对象之后,属性注入之前。按照官方的定义来说,到这里我们仍然可以使用这个方法来修改bd的定义,那么相对于通过BeanFactoryPostProcessor的方式修改bd,applyMergedBeanDefinitionPostProcessors这个方法影响的范围更小,BeanFactoryPostProcessor影响的是整个Bean的生命周期,而applyMergedBeanDefinitionPostProcessors只会影响属性注入之后的生命周期。

polulateBean 属性赋值

为什么属性赋值要单独拿出来,其实是属于初始化里面的吗;在spring里还真不是,不过要注意的是spring cloud的@RefreshScope是直接调用了destroy方法之后直接调用初始化方法,跳过了属性赋值,其实也就是跳过了@Autowired @Value等的属性赋值处理保留原有的。

postProcessProperties

由 InstantiationAwareBeanPostProcessor触发

给bean的属性赋值

AutowiredAnnotationBeanPostProcessor 和

CommonAnnotationBeanPostProcessor

就是在此处分别给@Autowired @Value、@Resource属性通过反射赋值

postProcessPropertiesValues(Deprecated)

由 InstantiationAwareBeanPostProcessor触发

目前已经作废,被上面postProcessProperties方法替代

applyPropertyValues

BeanDefinition里propertyValues的SpEL的处理在这里,通过java config配置的类基本上已经用不到这里了

Initialization 初始化

什么叫初始化,简单点说就是执行类的各种方法

各类Aware接口方法

用于接收各类的spring对象

postProcessBeforeInitialization

由BeanPostProcessor触发

CommonAnnotationBeanPostProcessor

@PostConstruct注解的方法在此处调用执行

InitializingBean.afterPropertySet

调用实现了了InitializingBean接口的bean,调用其afterPropertySet方法

postProcessAfterInitialization

由BeanPostProcessor触发

目前常用的使用场景就是AOP AnnotationAwareAspectJAutoProxyCreator,将对象进行代理后返回代理对象,后面使用的都是代理对象。

invokeCustomInitMethod

触发BeanDefinition里 initMethod方法,由xml定义的bean就可以设置方法名,基于java config的已经用不到了,可以用上面 InitializingBean.afterPropertySet或者@PostConstruct作为代替实现

二级缓存

每个bean在创建过程中,实例化后将对象(引用类型)放入二级缓存(实例化完成),初始化完成后再将对象放入一级缓存,同时删除二级缓存(实例化+初始化均完成,完整的bean)

解决对象之间循环依赖问题

例如A 依赖B ,B依赖A

A先创建的话:

A先实例化=》放入二级缓存(存储实例化,但未初始化的对象)

A初始化时,发现属性需要注入B

B实例化=》放入二级缓存

B初始化时,发现属性需要注入A,从二级缓存取,取到A

B初始化完成=》放入一级缓存(存储实例化、初始化都完成了的完整体)

A初始化完成=》放入一级缓存

就这样,最后A,B都放入了一级缓存;在spring IoC container概念中,只需要了解到二级缓存就足矣,涉及到AOP的时候,再来看第三级缓存就明白用途了。

三级缓存

在没有AOP之前,二级缓存足以,AOP加入之后,为了不影响原有的二级缓存,特意加上第三级缓存。对象代理后先放入三级缓存而不是二级缓存。

加上这个是为了跟AOP解耦,不影响原有IoC二级缓存的基础上,独立再加一层,即解决了问题,也实现了解耦。

ApplicationContext

如何衔接spring bean生命周期是本文关注的重点

也是实现了相关BeanFactory接口的,其实就是增强了BeanFactory,变成了跟应用相关的;等于是在厨子的上层加了自己的菜单设计

例如@Autowired @Component @Bean 这都是他们旗下的设计的一些特色菜,给客户使用

以后下单不用描述那么;

其实就是往上面BeanFactory里添加一个postProcessor;

我们只要知道何时何地添加了哪些postProcessor

AnnotationConfigServletWebServerApplicationContext

目前最关键最常用的ApplicationContext没有之一(spring boot web servlet环境使用)

最常用的莫过于该ApplicationContext,spring boot servlet环境使用,全球海鲜加工饭店中的佼佼者。

类图:

编辑切换为居中

添加图片注释,不超过 140 字(可选)

生命周期

注意这里举例的是spring boot web servlet环境使用的 AnnotationConfigServletWebServerApplicationContext

构造函数

ApplicationContext由spring boot自动创建,构造函数里会添加java config必须的Processor

添加

ConfigurationClassPostProcessor(BeanDefinitionRegistryPostProcessor)

其会扫描到@SpringBootApplication注解里的@Import(AutoConfigurationImportSelector.class)

会去把spring.factories里的所有AutoConfiguration类当成@Configuration进行解析处理

EventListenerMethodProcessor(BeanFactoryPostProcessor)

基于@EventListener 注解的listener类的处理,将其添加至事件广播类中

添加

AutowiredAnnotationBeanPostProcessor(BeanPostProcessor)

CommonAnnotationBeanPostProcessor(BeanPostProcessor)

refresh

refresh方法包含如下各个子步骤

prepareBeanFactory


添加 ApplicationContextAwareProcessor(BeanPostProcessor)

postProcessBeforeInitialization方法用于额外的aware接口(除BeanNameAware、BeanClassLoaderAware、BeanFactoryAware之外的)在这里进行属性赋值

EnvironentAware

MessageSourceAware

ApplicationContextAware

postProcessBeanFactory


添加了 WebApplicationContextServletContextAwareProcessor(BeanPostProcessor)

用于ServletContextAware、ServletConfigAware,同BeanFactoryAware使用方式

invokeBeanFactoryPostProcessors


1、执行所有 BeanDefinitionRegistryPostProcessor

ConfigurationClassPostProcessor就是在此处执行

2、执行所有BeanFactoryPostProcessor

所有的排序规则都是优先@PriorityOrdered注解或PriorityOrdered接口实现由小到大

其次@Ordered注解或Ordered接口实现由小到大

最后是没实现排序的(不保证顺序)

registerBeanPostProcessors


将所有BeanDefinition class type为的BeanPostProcessor的bean找出来,添加到BeanFactory供其使用

initMessageSource


c18n相关

initApplicationEventMulticaster


初始化ApplicationContext的事件广播类,可以多线程或者同步广播,默认为同步

onRefresh


ServletWebServerApplicationContext (AnnotationConfigServletWebServerApplicationContext的父类)重写了onRefresh方法里去创建了内置servlet容器

registerListeners


将所有listener的bean(例如实现ApplicationListener接口的bean)注册到事件广播类中,用于后面事件发布时去触发到这些listener

finishBeanFactoryInitialization


这里触发 beanFactory.preInstantiateSingletons,即轮询beanDefinition进行bean的生命周期

finishRefresh


启动webServer,发布相关事件

各个环节添加了哪些东西,我们再来给“虚拟世界”加一些注释巩固一下

图片上传失败

​重试

Spring Boot 之 IoC

其实这里已经不涉及DI的实现了,我们主要关注如何衔接ApplicationContext的生命周期

生命周期

从源头SpringApplication.run(xxx.class, args);进来之后,即开始了spring boot的生命周期

Spring boot的启动流程是 执行流程+事件驱动来执行,其中事件驱动,是取spring.factories里的Listener去触发相关的事件监听,spring boot web servlet 环境默认的Listener有如下这些:

编辑

添加图片注释,不超过 140 字(可选)

Spring boot会每个关键阶段发布对应的事件去触发listener参与spring boot的构建过程,整体的生命周期如下:

关键内容都在图里了(右键-新标签页打开 可查看大图)

starting


主要是触发listener初始化第三方日志组件,用于后面设置level group等

An ApplicationStartingEvent is sent at the start of a run but before any processing, except for the registration of listeners and initializers.

environmentPrepared


核心步骤, spring.application.profile 就是在这个步骤触发listener进行加载

在这里发布event触发listener去加载对应profile的 properties yaml 文件到environment

An ApplicationEnvironmentPreparedEvent is sent when the Environment to be used in the context is known but before the context is created.

printBanner

打印banner信息

createApplicationContext

此处就跟上面ApplicationContext的构造函数衔接上

ApplicationContextInitializer.initialize(context)

为ApplicationContext加料

contextPrepared


An ApplicationContextInitializedEvent is sent when the ApplicationContext is prepared and ApplicationContextInitializers have been called but before any bean definitions are loaded.

contextLoaded


An ApplicationPreparedEvent is sent just before the refresh is started but after bean definitions have been loaded.

refreshContext

进入 ApplicationContext.refresh()方法

started


An ApplicationStartedEvent is sent after the context has been refreshed but before any application and command-line runners have been called.

ApplicationRunner

调用实现了ApplicationRunner接口的bean,然后调用其run方法

调用实现了ApplicationRunner接口的bean,然后调用其run方法

CommandLineRunner

调用实现了CommandLineRunner接口的bean,然后调用其run方法

running


An ApplicationReadyEvent is sent after any application and command-line runners have been called.

failed


An ApplicationFailedEvent is sent if there is an exception on startup.

                                    资源获取:

大家点赞、收藏、关注、评论啦 、查看👇🏻👇🏻👇🏻微信公众号获取联系方式👇🏻👇🏻👇🏻

 精彩专栏推荐订阅:下方专栏👇🏻👇🏻👇🏻👇🏻

每天学四小时:Java+Spring+JVM+分布式高并发,架构师指日可待

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

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

相关文章

关系抽取(三)实体关系联合抽取:CasRel

目录 关系抽取两大类方法 CasRel&#xff08;HBT&#xff09;——ACL2020 1. 基本思想 2. 模型细节 2.1 头实体识别层 2.2 关系、尾实体联合识别层 2.3 原理解释 3. 实验 NLP 关系抽取 — 概念、入门、论文、总结 - 知乎 (zhihu.com) 关系抽取两大类方法 按模型结构…

Git应用详解第七讲:Git refspec与远程分支的重要操作

这一节来介绍本地仓库与远程仓库的分支映射关系: git refspec 。彻底弄清楚本地仓库到底是如何与远程仓库进行联系的。 一、 Git refspec refspec 是 Reference Specification 的缩写,字面意思就是 具体的引用 。它其实是 一种格式 , git 通过这种格式来表示 本地分支 与 …

盘点|国内5款主流低代码开发平台介绍

国内主流低代码开发平台有哪些&#xff1f; 低代码开发平台作为解决企业数字化转型的有力手段&#xff0c;得到越来越多的关注&#xff0c;但企业在选型的时候也很苦恼&#xff0c;到底该选哪家&#xff0c;不同低代码平台到底有啥区别&#xff1f;各自侧重点是什么&#xff1…

R和Python机器学习:广义线性回归glm,样条glm,梯度增强,随机森林和深度学习模型分析

使用R和Python进行分析的主要好处之一是&#xff0c;它们充满活力的开源生态系统中总是有新的和免费提供的服务。 去年&#xff0c;我们与一家公司进行了短暂的咨询工作&#xff0c;该公司正在构建一个主要由基于R和Python机器学习分析的应用程序。 如今&#xff0c;越来越多…

在M1Mac上为GIMP安装G‘MIC插件

tags: GIMP MacOS Tips 写在前面 在Mac上使用GIMP也有段时间了, 虽然用起来还是一股理科风, 但是还是不影响使用, 之前就看过一个介绍GMIC插件的视频, 其中的滤镜效果确实很不错, 但是一直安装失败(可能是m1的原因), 这次看到GIMP更新到了Apple silicon的原生支持: 2.10.32-1…

深度测评FL Studio性能,多年Fl Studio使用感受分享

前阵子世界级电音盛会Tomorrowland在比利时如期举行&#xff0c;拉开了疫情下Rave文化复兴的帷幕。而国内&#xff0c;也推出了如《超感星电音》等电子音乐综艺&#xff0c;在节目上大家也更多地了解到了电子音乐的制作过程。节目中最被大家看好的制作人Carta所使用的FL Studio…

【数字信号去噪】基于matlab变分贝叶斯卡尔曼滤波器数字信号滤波【含Matlab源码 2256期】

⛄一、变分贝叶斯卡尔曼滤波器数字信号滤波 1变分贝叶斯 在参数估计的问题中,在获得了观测样本的数据集Z后,根据贝叶斯准则,核心是参数集 θ 的后验概率密度函数的计算 而式( 1) 计算的一个难点在于分母,边缘似然概率密度函数p( Z) 的计算。正如引言所述,通常情况下p( Z) 的计…

Windows系统反斜杠(倒斜杠 \ )和 Linux系统正斜杠(斜杠 / )

/撇是正斜杠&#xff0c;\捺是反斜杠&#xff08;Windows单词的第一笔&#xff0c;即字母W的第一笔&#xff0c;就是反斜杠&#xff09;。 1.单正斜杠&#xff08;斜杠/&#xff09;和双正斜杠&#xff08;//&#xff09; 1.1 在linux系统中的路径分隔符 ../表示上一级路径 …

Jenkins + GitBlit自动构建

多人协同过程中&#xff0c;避免不了多人提交&#xff0c;但没及时构建代码&#xff0c;导致代码被复盖。。。。 然后有了现在的自动构建【GitBlit配置groovy进行关联即可 &#xff0c;或GitHub使用WebHook实现】 一、 安装Jenkins【Git Plugin】 GitBlit &#xff0c;且可以正…

Java对象内存空间大小计算

一、查看基础类型的对象内存大小 八股文中很明确的告诉你了基础类型的大小 &#xff0c;如下图&#xff1a; 类型值大小&#xff08;byte&#xff09;对象内存大小(byte)备注byte116char216int416float416long824double1624 很明显基础类型值的大小和内存大小不一致&#xff…

从入门到精通,收下这 22 个 Python 学习网站

今天一并给大家整理推送&#xff0c;希望能帮你在这条道路上&#xff0c;走得更顺畅&#xff0c;走得更远&#xff0c;更稳… 0. 学习整体思路 我做为一个过来人&#xff0c;有一些经验想要分享&#xff1a; 前期&#xff1a;花点时间选一门口碑上佳的入门电子文字教程&…

大数据:数据策略之CAP理论和BASE理论

一、CAP 理论 1.1 基本概念 1. 一致性 在分布式环境中&#xff0c;一致性是指数据在多个节点之间能够保持一致的特性。如果在某个节点上执行变更操作后&#xff0c;用户可以立即从其他任意节点上读取到变更后的数据&#xff0c;那么就认为这样的系统具备强一致性。 2. 可用…

使用OpenCV的函数hconcat()、vconcat()实现图像或矩阵的连接

使用OpenCV的函数hconcat()、vconcat()实现图像或矩阵的连接 函数hconcat()在水平方向上连接图像或矩阵&#xff1b; 函数vconcat()在垂直方向上连接图像或矩阵。 两个函数的原型和使用方法一模一样&#xff0c;所以在下面的函数原型介绍中&#xff0c;只介绍函数hconcat()的…

C++ std::nothrow

我们知道&#xff0c;当我们写程序时&#xff0c;栈区的内存是非常小的&#xff0c;如果是写那种大型的项目&#xff0c;不够用是很正常的&#xff0c;所以我们需要在堆区申请内存&#xff1b; 但是如果在堆区申请了大量的内存&#xff0c;导致没有空间了&#xff0c;那么程序…

刨根问底 Kubernetes -- CNI (三)Multus

文章目录Multus 概述Multus 使用Multus DaemonSet 的作用Multus 的使用Multus 的 处理1. 从 input 加载 netConf, 将 cni 配置加载到 netConf.Delegates2. 加载委托插件&#xff08;delegate&#xff09;并将其添加至 multus 配置2.1. 尝试解析 Pod 注解中 multus 配置2.2. 获取…

实践案例丨CenterNet-Hourglass论文复现

摘要&#xff1a;本案例是CenterNet-Hourglass论文复现的体验案例&#xff0c;此模型是对Objects as Points 中提出的CenterNet进行结果复现。本文分享自华为云社区《CenterNet-Hourglass (物体检测/Pytorch)》&#xff0c;作者&#xff1a;HWCloudAI。 目标检测常采用Anchor的…

【正点原子FPGA连载】第二十七章 MDIO接口读写测试实验 摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0

1&#xff09;实验平台&#xff1a;正点原子MPSoC开发板 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id692450874670 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html 第二十七章 MDIO…

字典类型和字典函数、字典方法

字典类型 (无序&#xff0c;不能重复) 通过任意键信息查找一组数据中值信息的过程叫映射&#xff0c; Python语言中通过字典实现映射。 Python语言中的字典可以通过大括号({})建立&#xff0c;建立模式如下&#xff1a; {<键1>:<值1>,<键2>:<值2>,...,…

[附源码]Python计算机毕业设计SSM健身房管理系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

p15~p22基本链表容器和高级链表容器迭代器

STL一、自制链表容器/基本链表容器1.1 首/尾部增删节点1.2 获取首/尾部的元素1.3 清空链表7 / 判空链表 / 链表大小81.4 缺省构造0/拷贝构造10/析构函数91.5 输出流操作符重载二、迭代器原理2.1 迭代器概念2.2 迭代器的分类三、迭代器实现3.1 正向非常迭代类3.2 正向非常迭代器…