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

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: 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("car");
    }


这段代码实现的功能是, 读取当前文件所在目录及其子目录中的文件, 然后获取指定名称的bean, 我们来看看getBean("")方法的源码

@Override
public Object getBean(String name) throws BeansException {
    assertBeanFactoryActive();
    return getBeanFactory().getBean(name);
}

可以看到, getBeanFactory(), 首先获得bean工厂, 然后通过bean工厂再去获取指定名称的bean对象.


这说明: bean对象并不是ioc容器创建的, 而是bean工厂创建的. ioc不负责创建bean或者获取bean. 都是bean工厂负责.

 

整个流程如下图所示:

1187916-20200914175424563-369802685.png

 

首先, 先自定义一个java类, 通过ClassPathXmlApplicationContext或者AnnotationConfigApplicationContext去读取配置文件.


然后, 将其交给BeanFactory.


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

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

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

 

所以, 在这里使用了一个设计模式: 策略设计模式, xml方式也好, annotation方式也好, 都只是一种实现方式而已.

 

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


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

什么意思呢? 如下图:

1187916-20200914180413699-382436833.png

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

 

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


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

整个过程如下图:

1187916-20200914181726838-294997247.png

入口是"我"

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个人有装修的需求. 于是还要有一个接待, 这个接待要联系客户, 看看哪些是有意向的客户, 将其筛选出来. 然后定制家具.

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

1187916-20200914183220218-2094318433.png

销售就相当于我们的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的生命周期

1187916-20200915093425256-1677004238.png

如上图, 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的过程中, 还会有很多细节的问题, 比如循环依赖.

 1187916-20200915094519322-1463587484.png


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


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

1187916-20200914181726838-294997247.png

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


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


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


1. BeanFactoryPostProcessor: 修改BeanDefinition.


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

public class DefinedPost implements BeanFactoryPostProcessor {
    /**
     * 重写Bean工厂的后置处理器
     * @param beanFactory
     * @throws BeansException
     */
    @Override
    public void 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对象, 会报错,


public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
    
        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 接口

1187916-20200915104717409-843849612.png

我们来看个demo

public class DefinedPost implements BeanDefinitionRegistryPostProcessor {
    /**
     * 重写Bean工厂的后置处理器
     * @param beanFactory
     * @throws BeansException
     */
    @Override
    public void 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");
    }
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
    }
}

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


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

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

 

总结:

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

1187916-20200915105658308-661577869.png

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的加载过程

1187916-20200916095730926-797592240.png


对照上图, 我们来简述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源码编译过程演示

相关文章
|
10小时前
|
监控 Java 应用服务中间件
Spring Boot 源码面试知识点
【5月更文挑战第12天】Spring Boot 是一个强大且广泛使用的框架,旨在简化 Spring 应用程序的开发过程。深入了解 Spring Boot 的源码,有助于开发者更好地使用和定制这个框架。以下是一些关键的知识点:
19 6
|
10小时前
|
Java 应用服务中间件 测试技术
深入探索Spring Boot Web应用源码及实战应用
【5月更文挑战第11天】本文将详细解析Spring Boot Web应用的源码架构,并通过一个实际案例,展示如何构建一个基于Spring Boot的Web应用。本文旨在帮助读者更好地理解Spring Boot的内部工作机制,以及如何利用这些机制优化自己的Web应用开发。
15 3
|
10小时前
|
运维 Java 程序员
Spring5深入浅出篇:基于注解实现的AOP
# Spring5 AOP 深入理解:注解实现 本文介绍了基于注解的AOP编程步骤,包括原始对象、额外功能、切点和组装切面。步骤1-3旨在构建切面,与传统AOP相似。示例代码展示了如何使用`@Around`定义切面和执行逻辑。配置中,通过`@Aspect`和`@Around`注解定义切点,并在Spring配置中启用AOP自动代理。 进一步讨论了切点复用,避免重复代码以提高代码维护性。通过`@Pointcut`定义通用切点表达式,然后在多个通知中引用。此外,解释了AOP底层实现的两种动态代理方式:JDK动态代理和Cglib字节码增强,默认使用JDK,可通过配置切换到Cglib
|
10小时前
|
XML Java 程序员
Spring特性之二——IOC控制反转
Spring特性之二——IOC控制反转
14 4
|
10小时前
|
JSON 前端开发 Java
【JAVA进阶篇教学】第七篇:Spring中常用注解
【JAVA进阶篇教学】第七篇:Spring中常用注解
|
10小时前
|
存储 前端开发 Java
Spring Boot自动装配的源码学习
【4月更文挑战第8天】Spring Boot自动装配是其核心机制之一,其设计目标是在应用程序启动时,自动配置所需的各种组件,使得应用程序的开发和部署变得更加简单和高效。下面是关于Spring Boot自动装配的源码学习知识点及实战。
15 1
|
10小时前
|
JavaScript Java 开发者
Spring Boot中的@Lazy注解:概念及实战应用
【4月更文挑战第7天】在Spring Framework中,@Lazy注解是一个非常有用的特性,它允许开发者控制Spring容器的bean初始化时机。本文将详细介绍@Lazy注解的概念,并通过一个实际的例子展示如何在Spring Boot应用中使用它。
20 2
|
10小时前
|
传感器 人工智能 前端开发
JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式
智慧校园电子班牌,坐落于班级的门口,适合于各类型学校的场景应用,班级学校日常内容更新可由班级自行管理,也可由学校统一管理。让我们一起看看,电子班牌有哪些功能呢?
101 4
JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式
|
10小时前
|
前端开发 Java
SpringBoot之自定义注解参数校验
SpringBoot之自定义注解参数校验
19 2
|
10小时前
|
监控 Kubernetes Docker
【Docker 专栏】Docker 容器内应用的健康检查与自动恢复
【5月更文挑战第9天】本文探讨了Docker容器中应用的健康检查与自动恢复,强调其对应用稳定性和系统性能的重要性。健康检查包括进程、端口和应用特定检查,而自动恢复则涉及重启容器和重新部署。Docker原生及第三方工具(如Kubernetes)提供了相关功能。配置检查需考虑检查频率、应用特性和监控告警。案例分析展示了实际操作,未来发展趋势将趋向更智能和高效的检查恢复机制。
【Docker 专栏】Docker 容器内应用的健康检查与自动恢复