spring源码学习——前置知识

文章目录

  • 前言
  • 1、BeanFactory与ApplicationContext
  • 2、常用注解及回调方法
    • 1、Bean注册常用注解
    • 2、组件扫描注解
    • 3、依赖注入方式
  • 3、回调注入
  • 4、普通Bean和工厂Bean
  • 5、Bean生命周期初始化销毁方法。
  • 6、spring的事件机制&监听器
  • 7、模块装配
  • 8、条件装配
  • 9、BeanDefinition
    • 1、什么是BeanDefinition?
    • 2、为什么需要BeanDefinition?
    • 3、AbstractBeanDefinition
    • 4、BeanDefinition是如何生成的
    • 5、设计BeanDefition的意义
  • 10、BeanDefinitionRegistry
    • 1、BeanDefinitionRegistry概述
    • 2、BeanDefinition增、删流程
  • 11、后置处理器
    • 1、Bean的后置处理器
      • 1、概述
      • 2、BeanPostProcessor的设计
      • 3、BeanPostProcessor的扩展接口
    • 2、Bean工厂的后置处理器
      • 1、概述
      • 2、使用
    • 3、BeanDefinitionRegistryPostProcessor
      • 1、概述
      • 2、三种后置处理器对比


前言

本文主要讲解spring源码分析需要的一些前置知识,如果已经掌握可以进行跳过。


1、BeanFactory与ApplicationContext

通过类图结构可以发现ApplicaionContext是一个接口,并且它是BeanFactory的子接口。

spring源码学习——前置知识_第1张图片

通过下面这张功能对比图可以发现,BeanFactory提供了一个抽象的配置和对象的管理机制,ApplicationContext是BeanFactory的子接口,ApplicationContext包含BeanFactory的所有功能,并且还扩展了好多特性。主要扩展了如下功能:

  • AOP的支持(AnnotationAwareAspectJAutoProxyCreator 作用于 Bean 的初始化之后)
  • 配置元信息(BeanDefinition、Environment、注解等)
  • 资源管理(Resource抽象)
  • 事件驱动机制(ApplicationEvent、ApplicationListener)
  • 消息与国际化(LocaleResolver)
  • Environment抽象

spring源码学习——前置知识_第2张图片

2、常用注解及回调方法

1、Bean注册常用注解

在类上标注@Configuration注解表示这个类是一个配置类,在方法上标注@Bean注解,方法返回一个Person类型的对象,这个是向IOC容器注册一个类型为Person,Id为person的bean。方法的返回值代表注册的类型,方法名代表Bean的id,也可以直接在注解中指定bean名称。

spring源码学习——前置知识_第3张图片

组件注册注解,在类上标注@Component注解,代表该类会被注册到IOC容器中作为一个bean,如果没有指定bean的名字,它的默认规则是"类名的首字母小写",@Component还有很多衍生注解,spring在迎合MVC三层架构,额外提供了三个注解:@Controller@Service@Repository,分别代表表现层、业务层、持久层。这三个注解的作用与@Component完全一致,底层也是@Component

spring源码学习——前置知识_第4张图片
spring源码学习——前置知识_第5张图片

2、组件扫描注解

如果像上面只声明了组件,那么在启动IOC容器的时候,是感知不到有@Component存在的,会报错NoSuchBeanDefinitionException,所以spring给我们提供了@ComponentScan,来提供组件扫描,可以在配置类上标注一个@ComponentScan,并制定要扫描的路径,它就可以扫描注定路径包及其子包下的所有@Component组件。如果不指定扫描路径,会默认扫描本类及所在包及子包下的所有@Component组件

spring源码学习——前置知识_第6张图片

3、依赖注入方式

set方式注入Bean属性

spring源码学习——前置知识_第7张图片

构造器方式注入bean属性

spring源码学习——前置知识_第8张图片

注解注入

  1. @Autowired:可以在Bean的属性/setter方法上标注@Autowired注解,IOC容器会按照属性对应的类型从容器着对应类型的Bean赋值到对应属性上实现自动注入。
  • 如果注入的Bean不存在,可以在@Autowired注解上加一个属性:required=false。
  • 如果有多个相同类型的Bean,但是beanName不一样,可以使用@Qualifier注定注入Bean的名称。其实不使用Qualifier注解,可以直接修改属性名为你要注入的beanName也行。
  • 多个相同类型Bean的全部注入,可以通过使用Listxxx把一组相同类型的注入进来。
  • @Autowired注入逻辑:
    先拿属性对应的类型,去 IOC 容器中找 Bean ,如果找到了一个,直接返回;如果找到多个类型一样的 Bean , 把属性名拿过去,跟这些 Bean 的 id 逐个对比,如果有一个相同的,直接返回;如果没有任何相同的 id 与要注入的属性名相同,则会抛出 NoUniqueBeanDefinitionException 异常。

spring源码学习——前置知识_第9张图片

注解注入:

  1. JSR250-@Resource@resource也是用来属性注入的注解,它与@Autowired的不同之处在于:@Autowired是按照类型注入,@Resource是直接按照属性名/Bean的名称注入的。其实这个注解就相当于标注@Autowired@Qualifier

spring源码学习——前置知识_第10张图片

依赖注入的注入方式

spring源码学习——前置知识_第11张图片

不同注解注入对比

spring源码学习——前置知识_第12张图片

常见依赖注入问题:

  1. 依赖注入的目的和优点?
    首先,依赖注入作为 IOC 的实现方式之一,目的就是解耦,我们不再需要直接去 new 那些依赖的类对象(直接依赖会导致对象的创建机制、初始化过程难以统一控制);而且,如果组件存在多级依赖,依赖注入可以将这些依赖的关系简化,开发者只需要定义好谁依赖谁即可。除此之外,依赖注入的另一个特点是依赖对象的可配置:通过 xml 或者注解声明,可以指定和调整组件注入的对象,借助 Java 的多态特性,可以不需要大批量的修改就完成依赖注入的对象替换(面向接口编程与依赖注入配合近乎完美)。
  2. 谁把什么注入给谁了?
    由于组件与组件之间的依赖只剩下成员属性 + 依赖注入的注解,而注入的注解又被 SpringFramework 支持,所以这个问题也好回答:IOC 容器把需要依赖的对象注入给待注入的组件。
  3. 使用setter注入还是构造器注入?
    SpringFramework 4.0.2 及之前是推荐 setter 注入,理由是一个 Bean 有多个依赖时,构造器的参数列表会很长;而且如果 Bean 中依赖的属性不都是必需的话,注入会变得更麻烦;
    4.0.3 及以后官方推荐构造器注入,理由是构造器注入的依赖是不可变的、完全初始化好的,且可以保证不为 null ;
    当然 4.0.3 及以后的官方文档中也说了,如果真的出现构造器参数列表过长的情况,可能是这个 Bean 承担的责任太多,应该考虑组件的责任拆解。

3、回调注入

回调注入的根接口是一个叫Aware的接口,它是一个空接口,底下有一系列的子接口

spring源码学习——前置知识_第13张图片

常用的回调接口:

spring源码学习——前置知识_第14张图片

使用示例:这里演示最常用的ApplicationContextAware接口回调的使用示例
这里我们实现了ApplicationContextAware接口,在这个bean初始化完成后,Spring就回把ApplicationContext传给他,我们就可以干一些事情,比如下面的打印beanName

spring源码学习——前置知识_第15张图片

4、普通Bean和工厂Bean

之前我们创建的Bean都是普通Bean,这里重点说一下FactoryBean的使用和场景。

  1. 场景:Bean的创建需要指定一些策略,或者依赖特殊的场景来分别创建,或者一个对象的创建过程过于复杂,这个时候就可以借助FactoryBean来使用工厂方法创建对象。就比如Mybatis就是借助FactoryBean来创建SqlSessionFactory

  2. 是什么:从下图看到,FactoryBean本身是一个接口,它本身是一个创建对象的工厂,如果Bean实现了FactoryBean接口,则它本身不会在实际的业务逻辑中起作用,而是由创建的对象起作用。

spring源码学习——前置知识_第16张图片

FactoryBean使用:
例:下面这个Demo是小孩要买玩具,由一个玩具生产工厂来给这个小孩造玩具。这里就可以自动注入小孩,然后根据小孩喜欢什么玩具来生产什么玩具出来。

spring源码学习——前置知识_第17张图片

FactoryBean常见问题:

  1. 如何取出FactoryBean本体:如果直接根据bean的id去容器中获取,那么取出来的是成产出来的bean,取FactoryBean的方式需要在Bean的id前面加"&"符号
    在这里插入图片描述
  2. BeanFactoryFactoryBean的区别:
  • BeanFactory :SpringFramework 中实现 IOC 的最底层容器(此处的回答可以从两种角度出发:从类的继承结构上看,它是最顶级的接口,也就是最顶层的容器实现;从类的组合结构上看,它则是最深层次的容器,ApplicationContext 在最底层组合了 BeanFactory )
    FactoryBean :创建对象的工厂 Bean ,可以使用它来直接创建一些初始化流程比较复杂的对象

5、Bean生命周期初始化销毁方法。

回想Servlet规范,Servlet提供了两个方法initdestory,用来初始化和销毁Servlet,是留给用户的回调函数,它是由父类/接口定义好,由第三方框架或容器来调用。我们来看下spring提供的初始化和销毁的回调方法。

InitializingBean&DisposableBean(这里不讲xml对应的init-method和destory-method)
这里实现这两个接口,然后在初始化和销毁的回调方法中做我们自己的逻辑处理。

spring源码学习——前置知识_第18张图片

JSR250规范提供的@PostConstruct@PreDestory注解,可以直接标注在Bean的方法上,然后在初始化后和销毁前会进行回调。

spring源码学习——前置知识_第19张图片

上面两种声明周期同时存在顺序:顺序为@PostConstruct → InitializingBean → init-method,这里没有提到init-method,因为现在很少使用xml方式来进行bean注册了。

spring源码学习——前置知识_第20张图片

6、spring的事件机制&监听器

观察者模式,也称为发布订阅模式,观察者模式关注的点是某一个对象被修改/做出某些反应/发布一个信息等会自动通知他的对象(订阅者)
spring中的事件驱动核心分为四个主体:事件源、事件、广播器、监听器

  • 事件源:发布事件的对象
  • 事件:事件源发布的信息/做出的动作
  • 广播器:事件真正广播给监听器的对象【及ApplicationContext】
    ApplicationContext接口实现ApplicationEventPublisher接口,具备事件广播器的发布事件的能力
    ApplicationEventMulticaster组合了所有监听器,具备事件广播器的广播事件的能力
  • 监听器:监听的对象

spring内部提供了很多事件,如ContextRefreshedEvent等,我们不使用这些,来自定义事件开发,这里我们自定义一个事件继承ApplicationEvent

spring源码学习——前置知识_第21张图片

这里我们编写了一个监听器,实现了ApplicationListener,并定义我们要监听RegisterSuccessEvent事件。

spring源码学习——前置知识_第22张图片

这里也可以使用@EvnetListener定义监听器,在方法的参数上表示你要监听什么事件,这里可以使用@Order注解调整监听器的触发顺序,标注这个注解默认是Integer.MAX_VALUE,代表最靠后,越小越靠前。

spring源码学习——前置知识_第23张图片

使用ApplicationEventPublisher发布事件,这里我们实现ApplicationEventPublisherAware接口帮我们自动注入ApplicationEventPublisher,然后我们就可以在业务逻辑中通过ApplicationEventPublisher来广播事件。

spring源码学习——前置知识_第24张图片

7、模块装配

  1. 什么是模块?
    通常模块可以理解为一个一个可以分解、组合、更换的独立单元,模块与模块之间可能存在一定的依赖,模块的内部通常是高内聚的,一个功能可以看成一个模块,一个组件也可以看成一个模块。
  2. 什么是模块装配?
    模块是功能单元,那么模块装配就是把一个模块需要的核心功能组件都装配好
  3. spring中的模块装配?
    在springCloud中我们集成一个组件,都是通过@Enablexxx注解,我们来剖析一下这种注解到底做了什么使用(其实就是自定义注解+@Import导入组件)

场景模拟:现在有一个酒馆,酒馆里有吧台,调酒师、服务员和老板,现在我们要装配这酒馆需要的全部组件,通过一个注解,把这些组件全部填充到酒馆中。

spring源码学习——前置知识_第25张图片

这里先解析一下上面的@Import注解,这个是最核心的注解,通过注释我们可以知道他可以导入配置类、ImportSelector的实现类、ImportBeanDefinitionRegistrar的实现类或者普通类。

spring源码学习——前置知识_第26张图片

这里导入配置类和普通类就不说了,重点看一下ImportBeanDefinitionRegistrar和ImportSelector的装配方式。

  1. ImportSelector:这里实现了他的selectImports方法,返回的是String数组,他需要你返回一组全限定类名,然后把这组类注册到Spring容器中。
  2. ImportBeanDefinitionRegistrar:这里实现了registerBeanDefinitions,是手动构造BeanDefinition然后注册到容器中(BeanDefinition后续会说,是bean的元数据信息)

spring源码学习——前置知识_第27张图片
spring源码学习——前置知识_第28张图片

8、条件装配

@Profile实现环境装配,profile提供了一种基于环境的配置:根据当前项目的运行时环境不同,可以动态的注册当前运行环境匹配的组件。

我们给BartenderConfiguration上添加profile,也就是注册调酒师的配置上只有当前环境处于城市才生效,默认的profile是default。

spring源码学习——前置知识_第29张图片

我们可以在spring启动的时候添加vmoptions设置启动的profile为city

spring源码学习——前置知识_第30张图片

由于Profile控制的面太大,是整个项目的运行环境,无法根据bean维度的因素决定是否装配,这个时候就可以使用@Conditional注解了。
被标注@Conditional的bean要注册到IOC容器时,必须全部满足@Conditional上指定的所有条件才可以,这里在注解中需要传入一个Condition接口的实现类数组,还需要编写条件匹配类做匹配依据。

spring源码学习——前置知识_第31张图片

这里编写了一个条件匹配规则类,用来判断IOC容器中是否包含Boss对象。

spring源码学习——前置知识_第32张图片

然后就可以把这个规则类放入Conditional,来判断装配的时候如果存在boss类才进行装配。

spring源码学习——前置知识_第33张图片

spring也帮我们封装好了一些通用的Conditional扩展注解,使我们不用自己编写规则类。
如下所示:

  • @ConditionalOnBean:仅仅在当前上下文中存在某个对象时,才会实例化一个Bean。
  • @ConditionalOnClass:某个class位于类路径上,才会实例化一个Bean
  • @ConditionalOnExpression:当表达式为true的时候,才会实例化一个Bean。
  • @ConditionalOnMissingBean:仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean。
  • @ConditionalOnMissingClass:某个class类路径上不存在的时候,才会实例化一个Bean。
  • @ConditionalOnNotWebApplication:不是web应用,才会实例化一个Bean。
  • @ConditionalOnBean:当容器中有指定Bean的条件下进行实例化。
  • @ConditionalOnMissingBean:当容器里没有指定Bean的条件下进行实例化。
  • @ConditionalOnClass:当classpath类路径下有指定类的条件下进行实例化。
  • @ConditionalOnMissingClass:当类路径下没有指定类的条件下进行实例化
  • @ConditionalOnWebApplication:当项目是一个Web项目时进行实例化。
  • @ConditionalOnNotWebApplication:当项目不是一个Web项目时进行实例化。
  • @ConditionalOnProperty:当指定的属性有指定的值时进行实例化。
  • @ConditionalOnExpression:基于SpEL表达式的条件判断。
  • @ConditionalOnJava:当JVM版本为指定的版本范围时触发实例化。
  • @ConditionalOnResource:当类路径下有指定的资源时触发实例化。
  • @ConditionalOnJndi:在JNDI存在的条件下触发实例化。
  • @ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者有多个但是指定了首选的Bean时触发实例化。

9、BeanDefinition

1、什么是BeanDefinition?

BeanDefinition是一种配置元信息,描述了Bean的定义信息,整体包含以下几个部分:

  • Bean 的类信息 - 全限定类名 ( beanClassName )
  • Bean 的属性 - 作用域 ( scope ) 、是否默认 Bean ( primary ) 、描述信息 ( description ) 等
  • Bean 的行为特征 - 是否延迟加载 ( lazy ) 、是否自动注入 ( autowireCandidate ) 、初始化 / 销毁方法 ( initMethod / destroyMethod ) 等
  • Bean 与其他 Bean 的关系 - 父 Bean 名 ( parentName ) 、依赖的 Bean ( dependsOn ) 等
  • Bean 的配置属性 - 构造器参数 ( constructorArgumentValues ) 、属性变量值 ( propertyValues ) 等

2、为什么需要BeanDefinition?

如果只是仅仅需要实例化一个bean那么只需要Class就行了,但是Class无法完成bean的抽象,如果上面的一些特性,bean的作用域,是否是懒加载的,注入模型等等,这些是class无法表示的,所以BeanDefinitiion就是存储这些和bean相关的元信息的。

3、AbstractBeanDefinition

作为Bean Definition的抽象实现,他内部定义好了一些属性,具体如下

  // bean的全限定类名
    private volatile Object beanClass;

    // 默认的作用域为单实例
    private String scope = SCOPE_DEFAULT;

    // 默认bean都不是抽象的
    private boolean abstractFlag = false;

    // 是否延迟初始化
    private Boolean lazyInit;
    
    // 自动注入模式(默认不自动注入)
    private int autowireMode = AUTOWIRE_NO;

    // 是否参与IOC容器的自动注入(设置为false则它不会注入到其他bean,但其他bean可以注入到它本身)
    // 可以这样理解:设置为false后,你们不要来找我,但我可以去找你们
    private boolean autowireCandidate = true;

    // 同类型的首选bean
    private boolean primary = false;

    // bean的构造器参数和参数值列表
    private ConstructorArgumentValues constructorArgumentValues;

    // bean的属性和属性值集合
    private MutablePropertyValues propertyValues;

    // bean的初始化方法
    private String initMethodName;

    // bean的销毁方法
    private String destroyMethodName;

    // bean的资源来源
    private Resource resource;

4、BeanDefinition是如何生成的

  1. 通过 xml 加载的 BeanDefinition ,它的读取工具是 XmlBeanDefinitionReader ,它会解析 xml 配置文件,最终来到 DefaultBeanDefinitionDocumentReader 的 doRegisterBeanDefinitions 方法,根据 xml 配置文件中的 bean 定义构造 BeanDefinition ,最底层创建 BeanDefinition 的位置在 org.springframework.beans.factory.support.BeanDefinitionReaderUtils#createBeanDefinition 。
  2. 通过模式注解 + 组件扫描的方式构造的 BeanDefinition ,它的扫描工具是 ClassPathBeanDefinitionScanner ,它会扫描指定包路径下包含特定模式注解的类,核心工作方法是 doScan 方法,它会调用到父类 ClassPathScanningCandidateComponentProvider 的 findCandidateComponents 方法,创建 ScannedGenericBeanDefinition 并返回。
  3. 通过配置类 + @Bean 注解的方式构造的 BeanDefinition 最复杂,它涉及到配置类的解析。配置类的解析要追踪到 ConfigurationClassPostProcessor 的 processConfigBeanDefinitions 方法,它会处理配置类,并交给 ConfigurationClassParser 来解析配置类,取出所有标注了 @Bean 的方法。随后,这些方法又被 ConfigurationClassBeanDefinitionReader 解析,最终在底层创建 ConfigurationClassBeanDefinition 并返回。

5、设计BeanDefition的意义

像我们平时先编写Class再new出对象一样,spring需要对其中的bean进行定义抽取,只有抽取成统一类型/格式的模型,才能在后续的bean对象管理时进行统一管理,也或者对特殊的bean进行特殊化的处理,所以需要BeanDifinition这个抽象化的模型。

10、BeanDefinitionRegistry

1、BeanDefinitionRegistry概述

  1. BeanDefinitionRegistry中存放了所有BeanDefinition
  2. BeanDefinitionRegistry中维护了BeanDefinition,包含了Definition的增、删、改等API

spring源码学习——前置知识_第34张图片

2、BeanDefinition增、删流程

这里注册的流程是实现ImportBeanDefinitionRegistrar,这样可以在方法上拿到BeanDefinitionRegistry,然后就调用registerBeanDefinition注册BeanDefinition到容器中

spring源码学习——前置知识_第35张图片

这里移除的流程是需要实现BeanFactoryPostProcessor,并重写postProcessBeanFactory方法,这里Bean的后置处理器后续会说,下面就是移除调用容器中所有性别为male的Person。

spring源码学习——前置知识_第36张图片

11、后置处理器

1、Bean的后置处理器

1、概述

  1. BeanPostProcessor是一个容器扩展点,是一个回调机制的扩展点,核心工作就是在bean的初始化前后做一些额外的处理(预初始化bean的属性值、注入特定的依赖、生成代理对象等)
  2. BeanPostProcessor的执行可以指定先后顺序(可以配置多个BeanPostProcessor,摒并且通过实现Ordered接口或者@Order注解配置执行顺序)
  3. BeanPostProcessor在IOC容器间互不影响(不同IOC容器的BeanPostProcessor不会互相起作用)

2、BeanPostProcessor的设计

BeanPostProcessor是一个接口,定义了两个方法,postProcessBeforeInitialization 方法会在任何 bean 的初始化回调(例如 InitializingBean 的 afterPropertiesSet 或自定义 init-method )之前执行,而 postProcessAfterInitialization 方法会在任何 bean 的初始化回调(例如 `InitializingBean 的 afterPropertiesSet 或自定义 init-method )之后。
Bean的初始化阶段的全流程:
BeanPostProcessor#postProcessBeforeInitialization → @PostConstruct → InitializingBean → init-method → BeanPostProcessor#postProcessAfterInitialization

spring源码学习——前置知识_第37张图片
spring源码学习——前置知识_第38张图片

3、BeanPostProcessor的扩展接口

InstantiationAwareBeanPostProcessor

  1. postProcessBeforeInstantiation方法:在Bean的实例化之前执行
  2. postProcessAfterInstantiation方法:在Bean的实例化之后处理
  3. postProcessProperties:在属性赋值之前触发,PropertyValues是一组field-value的键值对,可以参与属性的赋值
  4. postProcessPropertyValues:已经废弃,被postProcessProperties方法代替

spring源码学习——前置知识_第39张图片
spring源码学习——前置知识_第40张图片

SmartInstantiationAwareBeanPostProcessor继承了上面的InstantiationAwareBeanPostProcessor,又额外扩展了3个方法

  1. predictBeanType:预测bean的类型(不能预测时返回null)
  2. determineCandidateConstructors :根据 bean 对应 Class 中的构造器定义,决定使用哪个构造器进行对象实例化,这个方法很重要,如果 bean 没有声明任何构造器,则此处会拿到默认的无参构造器;如果声明了多个构造器,则该处会根据 IOC 容器中的 bean 和指定的策略,选择最适合的构造器
  3. getEarlyBeanReference :提早暴露出 bean 的对象引用(用以解决spring的循环依赖)

spring源码学习——前置知识_第41张图片

DestructionAwareBeanPostProcessor(bean的销毁前拦截处理),bean销毁的回调方法

spring源码学习——前置知识_第42张图片

MergeDefinitionPostProcessor
postProcessMergedBeanDefinition方法:发生在bean的实例化之后,自动注入之前,这个是为了在属性赋值和自动注入之前,把要注入的属性都收集好,才能顺序进行注入的逻辑。
在spring容器中有一个非常重要的MergeDefinitionPostProcessor的实现就是AutowiredAnnotationBeanPostProcessor,他负责给bean实现注解的自动注入,注入的依据就是postProcessMergedBeanDefinition后整理的标记。

spring源码学习——前置知识_第43张图片

2、Bean工厂的后置处理器

1、概述

他操作的是bean的配置元信息,也就是BeanDefinition,可以在bean实例的初始化之前修改beanDefinition信息。

在这里插入图片描述

2、使用

这里可以在postProcessBeanFactory方法中,通过beanFactory拿到所有的beanDefinition,然后获取到指定bean的Definition,然后修改定义信息,下图是增加属性.

spring源码学习——前置知识_第44张图片

BeanPostProcessorBeanFactoryPostProcessor对比

spring源码学习——前置知识_第45张图片

3、BeanDefinitionRegistryPostProcessor

1、概述

这个可以允许在BeanFactoryPostProcessor之前注册其他的BeanDefinition,所以执行时机比BeanFactoryPostProcessor更早,一般用于修改,删除,增加BeanDefinition

在这里插入图片描述

2、三种后置处理器对比

spring源码学习——前置知识_第46张图片

你可能感兴趣的:(spring源码分析,spring,学习,java)