Spring IoC Container 原理解析

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 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. Inversi

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

关键对象

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

现实世界

这里我们以饭店举例

网络异常,图片无法展示
|

几个关键的对象:

海鲜加工饭店

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

出单系统

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

订单复核员

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

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

后厨

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

虚拟世界

网络异常,图片无法展示
|

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

现实世界

虚拟世界

说明

海鲜加工饭店

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的类图继承关系

网络异常,图片无法展示
|

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的属性:

属性

类型

说明

parentName

ChildBeanDefinition

String

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

只有ChildBeanDefinition才有该属性值

beanClass

AbstractBeanDefinition

Object

可以为string 也可以为 class<?>

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

scope

String

值:

singleton

prototype

refresh (spring cloud)

默认为空,为空时就当单例处理]

singleton

详见@Scope注解;

@Scope注解的使用还是有点窍门的,例如@RefreshScope是什么原理?

lzyInit

Boolean

是否延迟加载


ApplicationContext.refresh()时不加载,getBean()时才去加载

详见@Lazy

autowireMode

AbstractBeanDefinition

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

qualifiers

AbstractBeanDefinition

Map<String, AutowireCandidateQualifier>

Register a qualifier to be used for autowire candidate resolution, keyed by the qualifier's type name

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

@Qualifier 需和@Autowired一起使用

instanceSupplier

AbstractBeanDefinition

Supplier<?>

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

isFactoryBean

RootBeanDefinition

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

是由什么系统声明的bean

ROLE_APPLICATION

ROLE_SUPPORT

ROLE_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

网络异常,图片无法展示
|

出单系统必须实现
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的类的继承关系

网络异常,图片无法展示
|

典型常用的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的生命周期,这里再回顾一下我们的虚拟世界

网络异常,图片无法展示
|

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环境使用,全球海鲜加工饭店中的佼佼者。

类图:

网络异常,图片无法展示
|

生命周期

注意这里举例的是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有如下这些:

网络异常,图片无法展示
|

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.

相关文章
|
11天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
39 2
|
29天前
|
搜索推荐 Java Spring
Spring Filter深度解析
【10月更文挑战第21天】Spring Filter 是 Spring 框架中非常重要的一部分,它为请求处理提供了灵活的控制和扩展机制。通过合理配置和使用 Filter,可以实现各种个性化的功能,提升应用的安全性、可靠性和性能。还可以结合具体的代码示例和实际应用案例,进一步深入探讨 Spring Filter 的具体应用和优化技巧,使对它的理解更加全面和深入。
|
26天前
|
XML 缓存 Java
搞透 IOC、Spring IOC ,看这篇就够了!
本文详细解析了Spring框架的核心内容——IOC(控制反转)及其依赖注入(DI)的实现原理,帮助读者理解如何通过IOC实现组件解耦,提高程序的灵活性和可维护性。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
|
18天前
|
安全 Java 测试技术
Java开发必读,谈谈对Spring IOC与AOP的理解
Spring的IOC和AOP机制通过依赖注入和横切关注点的分离,大大提高了代码的模块化和可维护性。IOC使得对象的创建和管理变得灵活可控,降低了对象之间的耦合度;AOP则通过动态代理机制实现了横切关注点的集中管理,减少了重复代码。理解和掌握这两个核心概念,是高效使用Spring框架的关键。希望本文对你深入理解Spring的IOC和AOP有所帮助。
31 0
|
1月前
|
XML Java 数据格式
Spring IOC容器的深度解析及实战应用
【10月更文挑战第14天】在软件工程中,随着系统规模的扩大,对象间的依赖关系变得越来越复杂,这导致了系统的高耦合度,增加了开发和维护的难度。为解决这一问题,Michael Mattson在1996年提出了IOC(Inversion of Control,控制反转)理论,旨在降低对象间的耦合度,提高系统的灵活性和可维护性。Spring框架正是基于这一理论,通过IOC容器实现了对象间的依赖注入和生命周期管理。
71 0
|
2月前
|
SQL 监控 druid
springboot-druid数据源的配置方式及配置后台监控-自定义和导入stater(推荐-简单方便使用)两种方式配置druid数据源
这篇文章介绍了如何在Spring Boot项目中配置和监控Druid数据源,包括自定义配置和使用Spring Boot Starter两种方法。
|
1月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
177 2
|
3月前
|
缓存 Java Maven
Java本地高性能缓存实践问题之SpringBoot中引入Caffeine作为缓存库的问题如何解决
Java本地高性能缓存实践问题之SpringBoot中引入Caffeine作为缓存库的问题如何解决
|
16天前
|
缓存 IDE Java
SpringBoot入门(7)- 配置热部署devtools工具
SpringBoot入门(7)- 配置热部署devtools工具
27 2
 SpringBoot入门(7)- 配置热部署devtools工具
|
13天前
|
存储 运维 安全
Spring运维之boot项目多环境(yaml 多文件 proerties)及分组管理与开发控制
通过以上措施,可以保证Spring Boot项目的配置管理在专业水准上,并且易于维护和管理,符合搜索引擎收录标准。
25 2

推荐镜像

更多
下一篇
无影云桌面