spring5源码 -- IOC容器设计理念和核心注解的作用

 

可以学习到什么?

0. spring整体脉络

1. 描述beanfactory

2. beanfactory和ApplicationContext的区别

3. 简述SpringIoC的加载过程

4. 简述Bean的生命周期

5. Spring中有哪些扩展接口及调用机制

 

一. spring源码整体脉络介绍及源码编译

 1.1. 什么是IOC

ioc是控制反转,这是一种设计理念,用来解决的是层和层之间,类和类之间的耦合问题.

比如,现在有A,B两个类,在A类中引用了B类. 那么如果有一天,B类要被替换掉,我们会怎么办呢?如果B类被引用了100次,我们要替换100次?

现在呢,A是直接调用B,如果我们间接的调用B,将B包装起来,如果以后将B换成C,只需要在包装类里面替换就可以了. 我们不需要修改A类. 这就是控制反转.

 

Spring使用了ioc,Spring.ioc(A,B) 将A和B的引用都存在ioc中,spring会帮我们维护好,完全不用担心.

当我们在A中要使用B的时候,使用B对应的接口,然后使用@Autowired注解

A {
   @Autowired
   private IB b;  
}

什么时候把B换掉了,不痛不痒的,只需要把新的类放到IoC中就可以了.

1.2. Spring源码的整体脉络梳理

Spring IoC是一个容器,在Spring Ioc中维护了许多Bean

那这些bean是如何被注册到IoC中的呢? 换句话说,我们自定义的类,是如何作为一个bean交给IoC容器去管理的呢?

先来回忆,我们在开发spring的时候的步骤: 

第一步: 配置类. 配置类可以使用的方式通常由
      1) xml配置
      2) 注解配置
      3) javaconfig方式配置
第二步: 加载spring上下文
      1) 如果是xml,则new ClassPathXmlApplicationContext("xml");
      2) 如果是注解配置: 则new AnnotationConfigApplicationContext(config.class)

第三步: getBean() 
我们会讲自定义的类,通过xml或者注解的方式注入到ioc容器中.

在这一步,会将xml或注解中指定的类注入到IoC容器中. 

1.2.1 那么,到底是如何将一个类注入到ioc中的呢? 

下面就来梳理一下整个过程. 

第一问: 一个类要生产成一个Bean,最重要最核心的类是什么?

beanfactory

 

第二问: beanfactory是什么呢?

beanfactory是Spring顶层的核心接口--使用了简单工厂模式. 通常都是根据一个名字生产一个实例,根据传入的唯一的标志来获得bean对象,但具体是穿入参数后创建,还是穿入参数前创建,这个要根据 具体情况而定,根据名字或类型生产不同的bean. 

一句话总结: beanfactory的责任就是生产Bean

来看下面这段代码

public static void main( String[] args ) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.getBean(***);
    }

这段代码实现的功能是,读取当前文件所在目录及其子目录中的文件,然后获取指定名称的bean,整个流程如下图所示: 

 

首先,通过ClassPathXmlApplicationContext或者AnnotationConfigApplicationContext去读取配置,然后将其交给beanfactory.

第三. beanfactory调用getBean()方法,将Bean注入到IoC容器中

我们发现,配置的读取,可能是xml方式,也可能是annotation的方式,不同的方式读取应该使用的是不同的工具. 那么这些工具读取的结果应该是统一的,然后才能交给beanfactory去处理.

因为在beanfactory中是不会对这些异同点进行处理的. beanfactory的作用只有一个,就是个生产Bean

1.2.2 那么,不同的工具读取配置是如何统一的呢?

我们知道,读取配置这一块,应该会有一个不同的实现. 将xml和注解方式读取成统一的东西,放入到beanfactory中. 这个东西是谁呢?就是BeanDefinition(Bean定义) 

什么意思呢? 如下图:

 

 看绿色框框住的部分. 这个含义是: 通过不同的工具,可能是xmlApplicationContext,可能是annotationApplicationContext工具 读取的配置,最后都会构造成BeanDefinition对象. 然后将BeanDefinition传递给beanfactory,beanfactory统一处理BeanDefinition对象,调用getBean()方法,将其放入IoC容器中.

 

1.2.3 那么又是是如何读取配置统一构造成BeanDefinition的呢?

我们来举个例子,现在有一个人,比如说我刚买了一个房子,我要装修. 需要一个衣柜,这时候,我会找到一个衣柜店. 然后告诉他我的需求,柜子的颜色,款式格式什么样. 然后衣柜店记录我的需求,这个时候,他不会自己生产,他会通知工厂,让工厂来生产. 工厂按照什么生产呢,衣柜店有一个设计师, 他们的设计师. 会按照我的需求设计出一张图纸. 然后将图纸交给工厂. 工厂按照图纸要求生产Bean. 

整个过程如下图:

 

 

 入口是"我"

1. 我有一个需求,打一个柜子,找到衣柜店

2. 我告诉衣柜店我的需求,款式,然后衣柜店的设计师按照我的要求,设计出一张图纸

3. 衣柜店将图纸给到工厂,工厂按照图纸生产柜子

这是制造衣柜的过程. 其中在画图纸的时候,画一张就给工厂给一张,这样效率太低了. 我们可以画了n张,一起给工厂. 所以,在设计图纸这块是一个容器,存放多张图纸

 

后面,如果我还想定制一个橱柜店. 那么,就告诉设计师我的橱柜的颜色,就可以了. 流程和上面都是一样的. 

 

 

整个这个过程,就类似于我们的bean生产的过程

 

1. 定义了一个带有@Component注解的类,我找到衣柜店,衣柜店就类似于ApplicationContext.

2. 我告诉ApplicationContext我的需求,我要懒加载@Lazy,设置单例模式还是多例模式@Scope. 对应的就是定制柜子的颜色,款式. 然后衣柜店里的设计师BeanDefinitionRegistry根据我的需求设计出图纸,也就是构造成BeanDefinition. 不同的BeanDefinitionRegistry设计出不同的BeanDefinition,然后将他们都放在容器中.

3. 衣柜店ApplicationContext统一将一摞图纸BeanDefinitionMap交给工厂,  然后工厂按照要求生产Bean,然后将生成的bean放入到IoC容器中.

 

这是一个带有@Component的类被加载的过程. 

 

衣柜店要要想生意好,那么他要去拉活呀,所以还需要好的销售. 销售要去扫楼盘,去联系,哪些人有装修的需求. 挨个询问. 

可是问了100个人,可能只有10个人有装修的需求. 于是还要有一个接待,这个接待要联系客户,看看哪些是有意向的客户,将其筛选出来. 然后定制家具.

这里多了两类人: 销售和接待. 具体工作如下.

 

销售就相当于我们的BeanDefinitionReader,他的作用是去扫楼盘,找到潜在客户. 对应的就是BeanDefinitionReader去读取xml配置或者Annotation注解. 

xml中的配置有很多,注解也有很多,并不都是我们的目标. 于是有了接待

接待要去扫描所有潜在客户. 将有意向的客户扫描出来. 这就类似于我们的BeanDefinitionScanner,去扫描潜在客户,最后将带有@Component注解的类筛选出来

这就是后面需要定制家具的客户了

BeanDefinitionReader对应的就去读取配置类,看看有哪些需求需要搞装修.


它本身也是一个抽象类,可以看到他有AnnotationBeanDefinitionReader和XmlBeanDefinitionReader


我们配置了配置包,去扫描这个包下所有的类,然后将扫描到的所有的类交给BeanDefinitionScanner,它会去过滤带有@Component的类. 

在和上面的流程连接起来,就是整个配置文件被加载到IoC的过程了. 

 

1.3. ApplicationContext和factorybean的区别

1. factorybean功能就是生产bean. 他生产bean是根据BeanDefinition来生产的. 所以,一次只能生产一个

2. ApplicationContext有两种. 一种是xmlApplicationContext,另一种是annotationApplicationContext,他传入的参数是一个配置文件. 也就是可以加载某个目录下所有带有@Component的类

他们两个都各有使用场景. 使用ApplicationContext的居多.

 

另一个区别: 就是后面会说到的,ApplicationContext有两个扩展接口,可以用来和外部集成. 比如和MyBatis集成.

 

 

 

 

1.4. Bean的生命周期

 

 

如上图,beanfactory拿到BeanDefinition,直接调用getBean()就生产Bean了么? 

不是的,生产Bean是有一个流程的. 下面我们来看看Bean的生命周期

 

第一步: 实例化. bean实例化的时候从BeanDefinition中得到Bean的名字,然后通过反射机制,将Bean实例化. 实例化以后,这是还只是个壳子,里面什么都没有.

第二步: 填充属性. 经过初始化以后,bean的壳子就有了,bean里面有哪些属性? 在这一步填充


第三步: 初始化. 初始化的时候,会调用initMethod()初始化方法,destory()初始化结束方法

这个时候,类就被构造好了.

第四步: 构造好了的类,会被放到IoC的一个Map中. Map的key是beanName,value是bean实例. 这个Map是一个单例池,也就是我们说的一级缓存


第五步: 我们就可以通过getBean(user"),从单例池中获取雷鸣是user的类了.

 

 在构造bean的过程中,还会有很多细节的问题,比如循环依赖.

 

 

 A类里面调用了B类,所以beanfactory在构造A的时候,会去构造B. 然后在构造B的时候,发现,B还依赖了A. 这样,就是循环依赖. 这是不可以的. 

Spring是如何解决循环依赖的问题的呢? 

设置出口. 比如A在构造的过程中,那么设置一个标记,正在构造中. 然后构造B,B在构造的过程中应用了A,有趣构造A,然后发现A正在构造中,那么,就不会再次构造A了. 

后面还会详细讲解Spring是如何解决循环引用的. 这里我们需要知道的是: Spring使用的是三级缓存解决循环引用的问题

 其实,bean是存在一级缓存里面,循环引用使用的是三级缓存来解决的. 其实,一、二、三级缓存就是Map。

 

1.5. Spring中的扩展接口

有两个非常重要的扩展接口. beanfactoryPostProcessor(Bean工厂的后置处理器) 和 BeanDefinitionRegistryPostProcessor

这两个接口是干什么的呢? 

我们在这个图里面,看到了设计师要设计出图纸,然后把图纸交给工厂去生产. 那么设计师设计出来的图纸,有没有可能被修改呢?

当然是可以被修改的. 只要还没有交给工厂,就可以修改.

beanfactoryPostProcessor(Bean工厂的后置处理器)的作用就是修改BeanDefinition.

1. beanfactoryPostProcessor: 修改BeanDefinition.

是一个接口,我们的类可以实现这个接口,然后重写里面的方法

 DefinedPost implements beanfactoryPostProcessor {

    /**
     * 重写Bean工厂的后置处理器
     * @param beanfactory
     * @throws BeansException
     */
    @Override
     postProcessbeanfactory(ConfigurableListablebeanfactory beanfactory) throws BeansException {
        // beanfactory 拿到工厂了,就可以获取某一个Bean定义了
        GenericBeanDefinition car = (GenericBeanDefinition) beanfactory.getBeanDefinition(Car);
         拿到了car,然后修改了Car的类名为com.example.tulingcourse.Tank. 那么后面在获取的Bean里面,将其转换为Car,就会报错了
        car.setBeanClassName(com.example.tulingcourse.Tank);
    }
}

第一步: 实现了beanfactoryPostProcessor接口,然后需要重写里面的方法

第二步: 我们发现重写方法直接给我们了beanfactory,bean工厂

第三步: 拿到bean工厂,我们就可以根据名称获取BeanDefinition,也就是bean定义了. 

第四步: 我们修改了bean定义中的类名为Tank. 

 

这时候会发生什么呢? 从bean工厂中构建的car,取出来以后转换成Car对象,会报错,

 main(String[] args) {
    

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.);
    
        Car car = context.getBean("car",Car.class); // 这里会报错,因为已经被修改
        System.out.println(car.getName());
    }

 

执行流程: 当spring启动的时候,就会去执行AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TulingCourseApplication.class);

然后ApplicationContext回去扫描所有实现了beanfactoryPostProcessor对象的类,然后执行postProcessbeanfactory方法

 

beanfactoryPostProcessor被使用到的场景非常多,在集成其他组件的时候,比如集成mybatis

 

2. BeanDefinitionRegistryPostProcessor 注册BeanDefinition

这是一个Bean定义注册的后置处理器.BeanDefinitionRegistryPostProcessor本事是实现了beanfactoryPostProcessor 接口

 

 我们来看个demo

 DefinedPost implements BeanDefinitionRegistryPostProcessor {

     postProcessbeanfactory(ConfigurableListablebeanfactory beanfactory) throws BeansException {
        获取某一个Bean定义了
        GenericBeanDefinition car = (GenericBeanDefinition) beanfactory.getBeanDefinition();
        );
    }

    @Override
     postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {

    }
}

一个类实现了BeanDefinitionRegistryPostProcessor,需要重写postProcessBeanDefinitionRegistry方法,这个方法直接将BeanDefinitionRegistry就给我们了. 

然后使用beanDefinitionRegistry.registerBeanDefinition(); 就可以添加图纸了

在这里可以注册新的bean,也可以删除注册的bean. 多注册一个,bean工厂就要多构建一个. 

 

总结:

    beanfactoryPostProcessor和BeanDefinitionRegistryPostProcessor这两个扩展类是很重要的类,这对于向外部扩展起到了很大的的作用,比如: 集成mybatis

beanfactoryPostProcessor和BeanDefinitionRegistryPostProcessor是在ApplicationContext中的两个扩展接口. 这也是ApplicationContext和beanfactory的区别之一,因为有了这两个扩展节点,就可以和外部做集成. 比如Mybatis集成.  比如: 扫描配置类,就是通过 这两个扩展点的方式实现的.

 

这个扩展点的作用:

1. 除了IoC,其他的扩展,比如AOP,和MyBatis集成,都要用到这两个扩展点. 之所以Spring能够有序不乱的和很多外部组件整合,都是这两个扩展点的功能

 

1.6 Bean的扩展点

除了ApplicationContext有扩展点,  在Spring IoC中的bean也有扩展点. BeanPostProcessor(Bean的后置处理器). 如果使用在getBean()之前,那么可以阻止构建Bean,还可以自定义构建Bean.

 

BeanPostProcessor使用的场景有很多. 在Bean实例化之前和之后会被调用.  在填充属性之前和之后也会被调用,初始化之前和之后也会调用. 有些过程不只调用一次. 整个过程一共会调用9次. 在每一个过程都可以扩展Bean.

 

思考: Spring加入AOP是如何实现呢?

集成AOP肯定不会和IoC糅合在一块了. AOP就是通过BeanPostProcessor(Bean后置处理器)整合进来的.

AOP的实现方式有两种: 一种是CGLIB,另一种是JDK. 

假如说要进行集成,会在那个步骤继承呢? 比如要加日志,使用AOP的方式加. 我们通常是在初始化之后加AOP. 在这里将AOP集成进来.

 

如上图: 当面试的时候面试官问你,Bean的生命周期,我们不能只说实例化-->填充属性-->初始化. 还需要说初始化的时候,还有一些列的aware.

1.7. Spring IOC的加载过程

 

 

对照上图,我们来简述ioc的加载过程 

我们将一个类加载成Bean,不是一步到位的,需要经历一下的过程. 

1. 首先,我们要将类加载成BeanDefinition(Bean定义)

  加载成bean定义,有以下几个步骤:

  1) 使用BeanDefinitionReader加载配置类,此时是扫描所有的xml文件或者项目中的注解. 这里面有些使我们的目标类,有些不是

  2) 使用BeanDefinitionScanner扫描出我们的目标类. 

  3) 使用BeanDefinitionRegistry注册bean到BeanDefinitionMap中.

2. 然后,ApplicationContext可以调用beanfactoryPostProcessor修改bean定义,还可以调用BeanDefinitionRegistryPostProcessor注册bean定义

3. 将BeanDefinition交给beanfactory处理,beanfactory调用getBean()生成Bean或者调用Bean(getBean()有两个功能). 

4. 成产bean的时候,首先会实例化,然后填充属性(主要是读取@Autowire,@Value等注解). 在初始化Bean,这里会调用initMethod()方法和初始化销毁方法destroy(). 初始化的时候还会调用一堆的Aware,而且在bean生成的过程中 会有很多扩展点,供我们去扩展.

5. 将生产出的Bean放入到Map中,map是一个一级缓存池. 后面,我们可以通过getBean("user")从缓存池中获取bean

 

 

 

1.9 Spring源码编译过程演示

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

as

相关文章

Spring Cloud为Spring Boot应用程序提供Netflix OSS集成。 提供的功能模块包括服务发现(Eureka),断路...
Spring Cloud 学习笔记;maven配置;入门学习;基于Spring Boot 实现;服务端配置,客户端配置;
可以毫不夸张地说,这篇文章介绍的 Spring/SpringBoot 常用注解基本已经涵盖你工作中遇到的大部分常用的...
Spring中各种方式进行日期时间处理,有作用于单个实体的,也有作用于全局的,有作用于请求入参的,有作...
跨域资源共享(Cross-origin resource sharing)(CORS)是W3C的标准,大部分的浏览器都实现了这个标准...
Spring Boot使创建基于Spring的应用程序变得轻松,大部分的SpringBoot应用程序都只需要很少的Spring配置...