什么是ioc什么是di,什么是ioc平台

  

  IoC和DI的基本概念我们都很熟悉。我们直接看martin fowler的原文,里面已经有DI和spring使用的例子了。   

  

  《Inversion of Control Containers and the Dependency Injection pattern》   

  

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

  

  这里我们只关注作为思维的一些关键概念,并摘抄一些原文:   

  

  “因此,我认为我们需要为这种模式取一个更具体的名字。控制反转是一个过于通用的术语,因此人们会感到困惑。经过与各种IoC支持者的大量讨论,我们最终确定了名称依赖注入。”   

  

  “我认为我们需要给这个模型起一个能更好解释其特征的名字。控制面太广,经常让人摸不着头脑。在与一些IoC爱好者讨论后,我们将这种模型称为依赖注入。”   

  

  所以IoC是一个抽象的概念,常用来描述一个框架,而DI是一个具体的实现方式。其实你可以去spring官网看看,你用这两个字也是很有讲究的。   

  

  “当这些容器谈论它们如何如此有用,因为它们实现了‘控制反转’时,我最终感到非常困惑。控制反转是框架的共同特征,所以说这些轻量级容器特殊是因为它们使用了控制反转,就像说我的车特殊是因为它有轮子一样。”   

  

  “当这些容器的设计者说这些容器之所以这么有用,是因为它们实现了‘控制反转’。我深感困惑,控制反转是IoC框架的共有特征,如果说一个框架以实现了控制反转为特点相当于说我的汽车有轮子.”   

  

  如果依赖注入只是框架最基础的功能,那么spring的高级和核心功能是什么?   

  

  我们去看看春天官网:   

  

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

  

  关于设计理念的第一点:   

  

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

  

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

  

  所以spring的核心是做基于IoC的各类基础设施的提供者,这里的“基础设施”也可以理解为各类中间件的抽象集成。   

  

  DI是最基本的功能,是所有其他功能模块的基础。所以基于DI的各类应用才是spring的核心竞争力。   

  

  IoC在平时可能看不到什么作用,但是在关键对接或者架构上作用很大:   

  

  比如事务管理,无论jdbc还是oracle的事务实现代码,我们都统一使用spring transaction(当然是结合AOP),只需要修改相关的数据库配置。   

  

  比如注册中心所依赖的eureka,想要切换到nacos或者consul,那么代码就不需要更改。只需要修改相关的包引用和配置文件相关的配置,代码不需要移动。   

  

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

  

  为了更好地理解下面的内容,我们简单地将马丁福勒文章中的一些代码贴在这里。   

  

没有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 配置类:

  

@Configurationpublic 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

  

@Componentpublic @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自动引入的核心

  

@EnableAutoConfigurationpublic @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

  

为什么要用代理?

  

@Configurationpublic 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方法替代

  

applyPropertyValuesBeanDefinition里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)

  

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

  

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.

相关文章