前言

本文档是 D瓜哥 阅读 Spring 源码以及相关文档时的笔记。对学习内容做一些总结和提炼,分享出来也方便大家一起学习,共同进步。

友情支持

如果您觉得这个笔记对您有所帮助,看在D瓜哥码字的辛苦上,请友情支持一下,D瓜哥感激不尽,😜

支付宝

微信

有些打赏的朋友希望可以加个好友,欢迎关注D瓜哥的微信公众号,这样就可以通过公众号的回复直接给我发信息。

wx jikerizhi
公众号的微信号是: jikerizhi (“极客日志”全拼)因为众所周知的原因,有时图片加载不出来。如果图片加载不出来可以直接通过搜索微信号来查找我的公众号。

官网及版本库

本文档的版本库托管在 Github 上,另外单独发布。

“地瓜哥”博客网

https://www.diguage.com/ 。D瓜哥的个人博客。欢迎光临,不过,内容很杂乱,请见谅。不见谅,你来打我啊,😂😂

本文档官网

https://diguage.github.io/spring-framework/ 。为了方便阅读,这里展示了处理好的文档。阅读请点击这个网址。

本文档版本库

https://github.com/diguage/spring-framework 。由于组织方式的特殊性,坦白讲,不建议大家发 PR。有问题,欢迎发 Issue 讨论。

待解问题

  1. Spring 中的事件发布和处理的处理流程是一个什么样的?有哪些实质性的使用场景或案例?

  2. Spring 在关闭时,还未提交的事务,怎么处理?

1. 开发环境搭建

古人云,千里之行始于足下。既然想开始 Spring 源码分析,首先就需要把 Spring 的源代码下载下来。当然,也可以通过在使用 Spring 的项目中关联 Spring 源码的方式来搞。D瓜哥更偏向前者,所以采用直接下载源码的方式来搞。

俗话说,磨刀不误砍柴工。搞 Spring 源码分析,如果没有一套得心应手的工具,怎么能玩得好呢?所以,本章就从零开始,从安装 Git 开始,一路下来到顺利运行 Spring 的单元测试来介绍一下整套的环境如何搭建。

D瓜哥最常用的环境是 {var_os_mac} + IntelliJ IDEA(以下简称为 IDEA);但是,对国内程序员来说,可能绝大部分使用的还是 {var_os_win} + Eclipse(含 MyEclipse,以下统称为 Eclipse)。所以,在文中以 {var_os_mac} + IDEA 为主,同时也会兼顾一下使用 {var_os_win} + Eclipse 的情况。

Linux 方面,D瓜哥窃以为使用 {var_os_linux} 会比较多一些。所以,会以 {var_os_linux} 为样板来描述。其他版本的 Linux 发行版,请自行解决。

1.1. 安装 Git

由于 Spring 的源代码是使用 Git 来做版本管理的,而且是托管在 Github 上。所以,需要先安装相应的版本管理工具 Git。本节D瓜哥就来介绍一下 Git 的安装。

1.1.1. 在 Mac OSX 上安装 Git

在 Mac OSX 系统上安装 Git 非常简单。只需一条命令即可:

1
brew insatll git
没有使用过 Homebrew,请参考 Homebrew — OS X 不可或缺的套件管理器

1.1.2. 在 Windows 上安装 Git

在 Windows 上安装 Git,也比较简单了,直接去 Git for Windows 下载最新版,然后下一步下一步就OK了。

由于国内的特殊网络状况,有可能下载可能会很慢甚至失败。实在不行,请“科学上网”。

1.1.3. 在 Ubuntu 上安装 Git

在 Ubuntu 上安装 Git,相对来说,稍微麻烦一点点,需要多执行几个命令。命令如下:

1
2
3
sudo apt-add-repository ppa:git-core/ppa (1)
sudo apt-get update                      (2)
sudo apt-get install git                 (3)
1 这是添加 Git 的软件源;
2 更新软件源,这样可以应用上第一步安装的软件源,并且可以安装到最新版;
3 安装 Git。

如果在执行第一步时,提示找不到命令时,请执行 sudo apt-get install -y python-software-properties

add-apt-repository 命令可以向本地软件源中添加PPA软件库提供的软件地址,然后就可以使用 apt-get 更新安装、更新软件。而 add-apt-repository 是由 python-software-properties 这个工具包提供的。所以要先安装 python-software-properties 就能使用 add-apt-repository

1.1.4. 配置 Git

经过上一节的内容后,Git 已经安装好了。但是,在使用之前,需要做一些简单的配置。Mac OSX、Ubuntu 上直接打开终端,在 Windows 上打开刚刚安装的 Git Bash,然后执行如下命令:

1
2
git config --global user.name <Your Name>    (1)
git config --global user.email <Your Email>  (2)
1 配置用户名;
2 配置电子邮箱。

然后,就可以正常使用了。由于 Git 不是本书的重点。这里就不做过多介绍了。等后续用到再视情况来介绍。

1.2. 下载 Spring 源码

安装、配置完 Git 之后,就可以来下载 Spring 的源代码了。继续在上节内容提到的终端中操作。首先,切换目录到指定目录下;然后,再使用如下命令来克隆 Spring 的源代码:

1
git clone https://github.com/spring-projects/spring-framework.git

Spring 源码仓库大概有 900+M,从 GitHub 下载很慢。可以通过码云的镜像下载: https://gitee.com/mirrors/Spring-Framework ,这样就会快很多。下载完成后,再将仓库地址更新为 GitHub 上的仓库地址即可。

从本节开始,后续章节中 SPRING_HOME 就代表 Spring Framework 项目的根目录。

1.3. 将 Spring 导入到 Eclipse 中

在 Mac OSX 、 Ubuntu 上执行 SPRING_HOME/import-into-eclipse.sh;在 Windows 上 {var_spring_home}/import-into-eclipse.bat,并参考相关的输出内容来导入到 Eclipse 中。

1.4. 将 Spring 导入到 IntelliJ IDEA

古语有云:“工欲善其事,必先利其器!”尤其是 Java 平台,对调试的支持堪称完美,相关的工具又 是很好很强大。做 Spring 源码分析,也必须有得心应手的工具相辅相成才能事半功倍。

在 Java 开发领域中,众所周知的集成开发工具有三种:Eclipse、IDEA、Netbeans。D瓜哥在工作中,自己用过 Eclipse、IDEA。平时见到的开发工具也是以这两款为主。所以,重点介绍一下 Spring 如何导入 到这两款工具,以及如何在其中运行相关代码。本节介绍如何导入到 IDEA。

1.4.1. 正常导入

在 Spring 的版本库中,也有相关文档来介绍如何导入到 IDEA。我们先按照这个文档来进行操作一 遍:

  1. 在终端(Windows 系统上,推荐使用 Cmder)中,进入到 Spring 源码根目录 SPRING_HOME

  2. 预编译 spring-oxm 模块: ./gradlew cleanIdea :spring-oxm:compileTestJava;在 Windows 系统上,请执行 gradlew cleanIdea :spring-oxm:compileTestJava

  3. 导入到 IDEA 中:File → New → Project from Existing Sources…​ → 选中 Spring 源码 目录 → OK → import from external model → Gradle → Next → Finish,然后经过漫长的等待 后就成功导入到 IDEA 中了。

  4. 设计 Project 的 JDK 版本。这里分析 Spring 6.1.0-SNAPSHOT,要求 JDK 的版本最低为 JDK 8。

  5. 官方文档上显示,需要排除 spring-aspects,但是根据D瓜哥自己的运行测试情况来看,没有 发现什么问题,这里就不再排除。

按照文档来操作,可以顺利导入,但是在导入过程以及运行单元测试代码时会遇到一些问题。接下来, D瓜哥介绍一下如何解决这些问题。

1.4.2. 加快 Gradle 下载速度

遇到的第一个问题:在执行 预编译 spring-oxm 模块 时,耗时特别长。只是一味地打印出一些点点,不确定是否出现什么问题。

这是由于在首次执行命令时,相当于执行 Gradle 任务,这需要下载构建工具 Gradle。由于国内特殊的网络问题,有些地方下载得很慢,有些地方甚至根本下载不了。

其实,解决这个问题也很简单,基本思路是这样的:在 SPRING_HOME/gradle/wrapper/gradle-wrapper.properties 文件中的 distributionUrl 属性指明了所需的 Gradle 下载路径。初次执行 Gradle 命令时,需要下载对应版本的 Gradle。下载中 Gradle 会存放在 GRADLE_USER_HOME/wrapper/dists/ 的子目录下,Gradle 会在该目录下创建一个对应的目录(该目录不固定,中间有一段是一个自动生成的字符串。)。直接复制下来 distributionUrl 对应的下载链接,通过迅雷下载完成后,将 Gradle 压缩包复制到上述目录下,借此来“欺骗” Gradle,让其以为是自己下载完成的。终止运行的任务,然后再次执行 ./gradlew cleanIdea :spring-oxm:compileTestJava,你会发现,很快就能完成。

1.4.3. 自动手动建立 Gradle 执行任务。

上面的问题还好,即使不 Hack 一下,慢慢等待也能顺利完成。但是,把 Spring 导入到 IDEA 后, 分析 Spring 源代码,运行单元测试,进行单步调试,这是基本要求了。但是,在 IDEA 15.02以后 的版本中运行时,你就会发现,依赖的各种库都需要下载,典型的如 JUnit、Apache Commons Loggings。 实际这些库在导入时,都已经下载好了。你可以在 USER_HOME/.gradle/caches/modules-2/files-2.1 (可能后面的数字会有变化)目录中找到相应的库。但是,为什么 IDEA 却找不到呢?

其实,这是由于单元测试代码的运行方式导致的。在 IDEA 15.02 以后,运行单元测试时,默认使用 的是 Platform Test Runner。可以通过手动建立 Gradle 方式的测试运行配置来解决这个问题。 如下图:

manual new test
Figure 1. 手动建测试运行配置

上图中建立的整个测试类的运行配置。如果只想运行单个测试方法,只需要修改一下 Script parameters。将其修改为: --tests "org.springframework.beans.factory.DefaultListableBeanFactoryTests.testUnreferencedSingletonWasInstantiated" 即可。

新建完成后,就可以点击运行或者调试按钮来进行测试了。

1.4.4. 使用 Gradle 方式来运行单元测试

上面的方式治标不治本,而且相当麻烦。更简单也更根本的解决办法是将默认 Test Runner 修改 为 Gradle Test Runner。截图如下:

setting gradle test runner
Figure 2. 设置 Test Runner

经过上面的设置后,所有的单元测试都可以顺利执行了。

1.5. 修改 Maven 仓库地址

Spring 使用 Gradle 作为构建工具。Gradle 自身只是一个构建工具,官方没有提供依赖的仓库,它使用 Maven 的仓库。在 Spring 代码的 build.gradle 文件中可以看出,Spring 使用了自身搭建的开放仓库。具体代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
buildscript {
	repositories {
		maven { url "https://repo.spring.io/plugins-release" }
	}
	dependencies {
		classpath("org.springframework.build.gradle:propdeps-plugin:0.0.7")
		classpath("org.asciidoctor:asciidoctor-gradle-plugin:1.5.2")
		classpath("io.spring.gradle:docbook-reference-plugin:0.3.1")
		classpath("ws.antonov.gradle.plugins:gradle-plugin-protobuf:0.9.1")
	}
}

/// ......

repositories {
  maven { url "https://repo.spring.io/libs-release" }
  maven { url "https://repo.spring.io/milestone" }
  maven { url "https://repo.spring.io/snapshot" }   // reactor 2.0.6 snapshot
}

从上面的代码可以看出,Spring Framework 依赖的自身以及第三方库都是存在于它们自建的仓库中的。但是,由于特殊的国内网络原因,再下载这些依赖时,就会特别卡。

我们能否使用国内的依赖仓库来加快下载速度呢?回答是肯定的。由于 Gradle 并没有自建自己的仓库,而是使用的 Maven 仓库,这样就可以使用所有的 Maven 仓库。目前,开源中国搭建了 Maven 第三方库。我们就用这个仓库了。

根据 Gradle 的官方文档, Chapter 42. Initialization Scripts 中的描述,在 USER_HOME/.gradle/init.d/ 目录下,创建文件 init.gradle,然后在文件添加如下内容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
apply plugin:EnterpriseRepositoryPlugin

class EnterpriseRepositoryPlugin implements Plugin<Gradle> {

    private static String ENTERPRISE_REPOSITORY_URL = "{link_maven_repo}"

    void apply(Gradle gradle) {
        // ONLY USE ENTERPRISE REPO FOR DEPENDENCIES
        gradle.allprojects { project ->
            project.repositories {

                all { ArtifactRepository repo ->
                    if (!(repo instanceof MavenArtifactRepository) ||
                          repo.url.toString() != ENTERPRISE_REPOSITORY_URL) {
                        project.logger.lifecycle "Repository ${repo.url} removed."+
                            " Only $ENTERPRISE_REPOSITORY_URL is allowed"
                        remove repo
                    }
                }

                // add the enterprise repository
                maven {
                    name "STANDARD_ENTERPRISE_REPO"
                    url ENTERPRISE_REPOSITORY_URL
                }
            }
        }
    }
}
由于不需要专门配置 Gradle。所以,D瓜哥就使用 GRADLE_USER_HOME 来表示每个用户的 Gradle 根目录,该目录存放用户专属的 Gradle 的依赖,配置等等。从本节开始,以后章节使用 GRADLE_USER_HOME 来表示 Gradle 的用户根目录。GRADLE_USER_HOME 等价于 USER_HOME/.gradle/

这样,在执行 Gradle 命令,需要下载依赖时会自动替换掉其他的库,改用开源中国的库。

根据D瓜哥的经验来看,开源中国的 Maven 库并没有收录 Spring Framework 所有的依赖包。这样就会导致在下载依赖时失败。如果遇到这种情况,请把上面的脚步文件移动到其他目录(注意:不可以存放在 GRADLE_USER_HOME 目录下),然后多重试几次。如果还是不行,那就“科学上网”吧。

2. Spring 架构概述

Spring 架构简介这章,是否考虑暂时先放下,先搞代码,然后再来总结?

另外,考虑从下面几个章节开始搞起:

  • Spring 的前世今生(或者历史由来)

  • Spring 子模块简介

  • Spring 架构简介

Spring 框架是一个分层、分模块的架构。包括了一系列的模块。如下图所示:

spring overview
Figure 3. Spring 架构概要图

3. IoC 的实现原理

TODO: 感觉可以通过向 ClassPathScanningCandidateComponentProvider 中添加过滤注解来实现扩展功能。抽空尝试一下。

TODO: 除了 singleton 和 prototype 外,其他 Scope 类型的 Bean 实例是怎么缓存的?

TODO: default-autowire="byName" 等实现自动装配的功能在哪里实现的?怎么实现的?是否会遍历 Bean 的所有属性? org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean 中有相关类似实现。

autowiring 的实现过程:

  1. 对 Bean 的属性代调用 getBean()方法,完成依赖 Bean 的初始化和依赖注入。

  2. 将依赖 Bean 的属性引用设置到被依赖的 Bean 属性上。

  3. 将依赖 Bean 的名称和被依赖 Bean 的名称存储在 IOC 容器的集合中。

对属性的注入过程分以下两种情况:

  1. 属性值类型不需要强制转换时,不需要解析属性值,直接准备进行依赖注入。

  2. 属性值需要进行类型强制转换时,如对其他对象的引用等,首先需要解析属性值,然后对解析后的属性值进行依赖注入。

Spring IoC 容器是如何将属性的值注入到 Bean 实例对象中去的:

  1. 对于集合类型的属性,将其属性值解析为目标类型的集合后直接赋值给属性。

  2. 对于非集合类型的属性,大量使用了 JDK 的反射机制,通过属性的 Getter 方法获取指定属性注入以前的值,同时调用属性的 Setter 方法为属性设置注入后的值。

ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForBeanMethod 可以看出,对 @Bean 的处理复用了自定义 factory-method 的处理。在 spring-beans.xsd 文件中,可以找到对 factory-method 属性的说明。

3.1. 核心数据结构:BeanDefinition

林纳斯·托瓦兹(Linus Torvalds)说:“我从心底认为,优秀的程序员与平庸的程序员之间的区别,是在于认为自己的代码重要还是数据结构更加重要。平庸的程序员眼里只有代码,优秀的程序员则关注数据结构及之前的关系。” 也许很多人觉得 Spring 神秘莫测,但是如果了解了它的核心数据结构,很多问题迎刃而解。

Spring 中两个数据结构最核心:① BeanDefinition,用于表示 Bean 的定义;② BeanFactory,用于表示整个 IoC 容器。

在前面文章 Spring Bean 生命周期概述中,介绍了 Spring Bean 的生命周期。不知道大家有没有思考过 Spring 在内部是如何表示一个 Bean 的?本篇文章,就来聊一聊 BeanDefinition

3.1.1. 问题

使用 Spring 时,尤其是使用 XML 配置的时候,也许我们会这样的问题:

  1. Bean 怎么表示?

  2. Bean 的依赖怎么表示?

  3. init-method 方法怎么存储?

  4. Bean 的一些属性,比如 lazy-init 等,怎么表示?

  5. Bean 构造函数的参数怎么存储?

  6. …​

Java 也有类似的问题,比如怎么表示一个类?Java 通过反射 API 来解决这个问题:

  1. Class

  2. Method

  3. Field

  4. Constructor

  5. Annotation

但是,为什么 Spring 还要自己定义一套呢?主要原因是 Java 反射 API 不满足 Spring 的需求,比如,它没办法表示哪些类是 SCOPE_SINGLETON,哪些类是 SCOPE_PROTOTYPE

另外,Spring 的 Bean 抽象也并不是完全自定义的,它是基于 Java 反射 API 又增加了自定义功能,其核心 API 就是 BeanDefinition。下面,我们来仔细看一下它的继承体系以及内部核心属性。

3.1.2. 继承体系

BeanDefinition 继承体系
Figure 4. BeanDefinition 继承体系
  • AttributeAccessor: 提供对 BeanDefinition 属性操作能力。

  • AttributeAccessorSupport: 使用了 Map 进行属性的存储的。

  • BeanMetadataAttributeAccessor: 代表了一个 Bean 元数据的属性操作。

  • BeanMetadataElement: BeanDefinition 元数据,返回该 Bean 的来源。

  • BeanDefinition: 用来描述 Bean,里面存放 Bean 元数据,比如 Bean 类名、scope、属性、构造函数参数列表、依赖的 Bean、是否是单例类、是否是懒加载等一些列信息。

  • AbstractBeanDefinition: 抽象类统一实现了 BeanDefinition 定义的一部分操作,可以说是定义了 BeanDefinition 很多默认的属性。

  • RootBeanDefinition: 代表一个 XML,Java Config来的 BeanDefinition

  • AnnotatedBeanDefinition: 表示注解类型 BeanDefinition。有两个重要的属性:AnnotationMetadataMethodMetadata 分别表示 BeanDefinition 的注解元信息和方法元信息。实现了此接口的 BeanDefinition 可以获取到注解元数据和方法元数据。

  • ChildBeanDefinition: 可以让子 BeanDefinition 定义拥有从父母那里继承配置的能力。

  • GenericBeanDefinition: 是 Spring 2.5 之后才有的,这个的想法是用来替代 RootBeanDefinition/ChildBeanDefinition,而 RootBeanDefinition/ChildBeanDefinition 可以在 Spring 预加载的时候使用。

  • AnnotatedGenericBeanDefinition: 表示 @Configuration 注解注释的 BeanDefinition 类。是 AnnotatedBeanDefinition 的一个具体实现。传入指定类后,可以获取类中的注解。

  • ScannedGenericBeanDefinition: 表示 @Component@Service@Controller 等注解注释的 Bean 类。是 AnnotatedBeanDefinition 的另一个实现,与 AnnotatedGenericBeanDefinition 不同的是,ScannedGenericBeanDefinition 是通过扫描 class,然后操作 ASM 进行解析的。

3.1.3. 核心属性

下面主要介绍一下它的核心内部属性:

  1. Map<String, Object> attributes = new LinkedHashMap<>():配置的属性以及属性值。

  2. Object source:存储 Bean 来源,有时是 XML Element 对象。还可以是其他对象。

  3. Object beanClass:Bean 的类型定义,有时是 Class 类型的对象;有时是类的全限定名,此时就是 String 类型。

  4. abstractFlag = false:默认为 false。如果为 true,则表示不打算实例化该 Bean,仅仅作为其他 Bean 的父 Bean。一般与 parent 一起使用,设置 abstract 的 Bean 定义不需要创建实例,仅仅作为 parent 来进行一些通用的配置,后面的 Bean 通过设置 parent 来获取相应的配置信息,从而达到简化配置的目的。

  5. Boolean lazyInit:是否懒加载。

  6. int autowireMode = AUTOWIRE_NO:注入模式,默认为 AUTOWIRE_NO。一共有五个可选项:

    1. AUTOWIRE_NO — 不自动注入。

    2. AUTOWIRE_BY_NAME — 根据名称自动注入。

    3. AUTOWIRE_BY_TYPE — 根据类型自动注入。

    4. AUTOWIRE_CONSTRUCTOR — 自动根据构造函数注入。

    5. AUTOWIRE_AUTODETECT — 自动检测。

  7. int dependencyCheck = DEPENDENCY_CHECK_NONE:依赖检测。一共有四个可选项:

    1. DEPENDENCY_CHECK_NONE — 不进行依赖检测。

    2. DEPENDENCY_CHECK_OBJECTS — 只检测对象引用。

    3. DEPENDENCY_CHECK_SIMPLE — 只检测简单对象:基础类型、EnumCharSequenceNumberDateTemporalURIURLLocaleClass 以及这些类型的数组对象。

    4. DEPENDENCY_CHECK_ALL — 检测所有依赖。

  8. String[] dependsOn: 这个属性在 spring-beans.xsd / xsd:attribute / depends-on 中有说明。原文是这样说的:The names of the beans that this bean depends on being initialized. The bean factory will guarantee that these beans get initialized before this bean. 所以,这个属性并不是一个“需要注入的依赖属性”,而是 Bean 创建的前后依赖关系。这一点可能跟大多数人的认识不一样。

    Bean 属性的依赖存在 CommonAnnotationBeanPostProcessor#injectionMetadataCacheAutowiredAnnotationBeanPostProcessor#injectionMetadataCache 属性中。在 Spring Bean 生命周期概述 中描述的关于属性的依赖读取和注入也是靠这个两个属性来保存依赖关系的。

    深入剖析 Spring 核心数据结构:BeanFactory : Map<String, Set<String>> dependentBeanMap 中提到的 DefaultSingletonBeanRegistry#dependentBeanMapDefaultSingletonBeanRegistry#dependenciesForBeanMap 两个属性,保存了这个 dependsOn 依赖关系的正向关系和反向关系。当工厂销毁时,也会通过 dependentBeanMap 属性,先销毁依赖的 Bean,然后再销毁自身。

  9. boolean autowireCandidate = true:声明是否是其他依赖的候选 Bean;只会影响根据类型注入的情况,不会影响根据名称明确指明依赖的情况。

  10. boolean primary = false:是否是首选 Bean,标注了 @Primary 则为 true。当 A 类型的 Bean 需要注入 B 类型的实现类,并且 B 类型的实现类有多个,在按类型将 B 的实现类注入到 A 中时,优先注入该属性为 true 的实现类,当然如果同一个类的实现类有多个 primarytrue,则抛出异常。

  11. Map<String, AutowireCandidateQualifier> qualifiers = new LinkedHashMap<>()

  12. Supplier<?> instanceSupplier:产生对象的生产者。

  13. boolean nonPublicAccessAllowed = true:是否允许访问非 public 的构造器和方法。

  14. boolean lenientConstructorResolution = true:是否采用宽容模式来解析构造函数。如果是 false,则只要参数类型不匹配就抛出异常。

  15. String factoryBeanName:当 Bean 的创建方式是以工厂进行创建的时候,该方法设置工厂的名称。

  16. String factoryMethodName:工厂创建 Bean 时,设置创建 Bean 的方法名称。

  17. ConstructorArgumentValues constructorArgumentValues:构造函数参数值。

  18. MutablePropertyValues propertyValues: 获取类的属性和属性值的类 PropertyValue

  19. MethodOverrides methodOverrides = new MethodOverrides()

  20. String initMethodName:初始化方法名,对应 init-method 或者 @PostConstruct 标注的方法。

  21. String destroyMethodName:销毁方法名,对应 ` 或 `@PreDestroy 标注的方法。

  22. boolean enforceInitMethod = true:强制初始化方法,默认是 false。如果为 true,而且 initMethodName 为空,则报错。

  23. boolean enforceDestroyMethod = true:强制销毁方法,默认是 false。如果为 true,而且 destroyMethodName 为空,则报错。

  24. boolean synthetic = false:是否是合成的。

  25. int role = BeanDefinition.ROLE_APPLICATION:Bean 角色。可选项有三个:

    1. ROLE_APPLICATION — 为应用程序定义。

    2. ROLE_SUPPORT — 为应用程序定义的比较大的对象。

    3. ROLE_INFRASTRUCTURE — 内部定义的基础 Bean。

  26. String description:Bean 描述。

  27. Resource resource:Bean 的来源 Resource 对象。

  28. AnnotationMetadata metadata: 注解元信息。

  29. MethodMetadata factoryMethodMetadata:方法注解元信息。

BeanDefinition 的代码在 spring-framework/BeanDefinition.java 中。感兴趣,可以自己 clone 下来,把玩把玩。

下一篇文章,D瓜哥重点带大家了解一下 BeanFactory深入剖析 Spring 核心数据结构:BeanFactory

3.2. 核心数据结构:BeanFactory

深入剖析 Spring 核心数据结构:BeanDefinition 中,介绍了 BeanDefinition。网上很多文章介绍 BeanDefinition 的 API,D瓜哥却要反其道而行之,从内部属性来分析一下。下面我们开始。

3.2.1. 继承体系

Spring 非常好地遵循了面向对象的设计原则:面向接口编程。不放过任何可以提取出成接口的机会。虽然感觉似乎增加了类的继承关系,增加了一点的复杂度。但是,却带来了非常好的可扩展性。而 BeanFactory 的继承体系就是一个非常典型的例子。我们来看一下它的继承体系:

BeanFactory 继承体系
Figure 5. BeanFactory 继承体系
  • AliasRegistry:别名注册器。Spring 中,别名注册相关的功能就是从这里实现的。

  • SimpleAliasRegistry:别名注册器的一个简单实现,从内部属性可以看出,它是把别名映射信息存到一个 Map 中了。

  • DefaultSingletonBeanRegistry:默认的单例 Bean 注册器,从内部属性来说,也是基于 Map 实现的。

  • FactoryBeanRegistrySupportFactoryBean 注册器。

  • SingletonBeanRegistry:单例 Bean 注册器。

  • BeanDefinitionRegistryBeanDefinition 注册器。

  • BeanFactory:容器的基类。

  • ListableBeanFactory:在基本容器基础上,增加了遍历相关功能。

  • HierarchicalBeanFactory:在基本容器基础上,增加了父子上下级容器关联。

  • AutowireCapableBeanFactory:在基本容器基础上,增加了自动注入功能。

  • ConfigurableBeanFactory:对容器增加可配置性,比如父级容器、ClassLoaderTypeConverter 等。

  • ConfigurableListableBeanFactory:可配置可遍历容器。

  • AbstractBeanFactory:容器的抽象实现类,实现了容器的基础功能。

  • AbstractAutowireCapableBeanFactory:带自动装配功能的抽象容器类。

  • DefaultListableBeanFactory:这是 Spring 内部使用的默认容器实现。也是 Spring 中最重要的一个类。

3.2.2. 核心属性

3.2.2.1. Registry
  1. Map<String, String> aliasMap = new ConcurrentHashMap<>(16):别名到 Bean 名称的映射。

  2. Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256):Bean 名称到单例 Bean 的映射。可以理解成,这就是所谓的容器。

  3. Map<String, Object> earlySingletonObjects = new HashMap<>(16):Bean 到“未成熟”单例 Bean 的映射。该 Bean 对象只是被创建出来,但是还没有注入依赖。在容器解决循环依赖时,用于存储中间状态。

  4. Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16):Bean 名称到 Bean 的 ObjectFactory 对象的映射,在容器解决循环依赖时,用于存储中间状态。

    关于这三个属性的进一步说明,请移步: 源码剖析 Spring 循环依赖

  5. Set<String> registeredSingletons = new LinkedHashSet<>(256):已经被注册过的 Bean 名称集合。

  6. Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16)):正在创建的 Bean 名称集合。

  7. Set<String> inCreationCheckExclusions = Collections.newSetFromMap(new ConcurrentHashMap<>(16)):不需要检查的正在创建的 Bean 名称集合。

  8. Set<Exception> suppressedExceptions:存储创建过程中发现的异常。

  9. boolean singletonsCurrentlyInDestruction = false:是否正在销毁单例 Bean。

  10. Map<String, Object> disposableBeans = new LinkedHashMap<>():需要在销毁时释放资源的 Bean。在 AbstractBeanFactory#registerDisposableBeanIfNecessary 中可以看到,所有的单例 Bean 都通过 DisposableBeanAdapter 适配器添加到该属性中了。在 DefaultSingletonBeanRegistry#destroySingletonDefaultSingletonBeanRegistry#destroySingletons 中执行 destroy() 操作。

  11. Map<String, Set<String>> containedBeanMap = new ConcurrentHashMap<>(16):在 Bean 名称之间包含映射:Bean 名称到 Bean 所包含的一组 Bean 名称。

  12. Map<String, Set<String>> dependentBeanMap = new ConcurrentHashMap<>(64)

    该属性和下面的 dependenciesForBeanMap 属性的详细说明,请在 深入剖析 Spring 核心数据结构:BeanDefinition : String[] dependsOn 中查看。

  13. Map<String, Set<String>> dependenciesForBeanMap = new ConcurrentHashMap<>(64):与上面的 dependentBeanMap 属性正好一正一反的关系。两个相加,就是双向映射。

  14. Map<String, Object> factoryBeanObjectCache = new ConcurrentHashMap<>(16):由 FactoryBean 创建的单例对象的缓存。

3.2.2.2. BeanFactory
  1. BeanFactory parentBeanFactory:父容器。

  2. ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader():类加载器。

  3. ClassLoader tempClassLoader:临时类加载器。

  4. BeanExpressionResolver beanExpressionResolver:Bean 定义值中表达式的解析策略。

  5. ConversionService conversionService: Spring 3.0 以后出现,用于类型转换,用于替代 PropertyEditors

  6. Set<PropertyEditorRegistrar> propertyEditorRegistrars = new LinkedHashSet<>(4):属性编辑器注册器集合。

  7. Map<Class<?>, Class<? extends PropertyEditor>> customEditors = new HashMap<>(4):类型到自定义的属性编辑器的映射。

  8. TypeConverter typeConverter:类型转换器,用于覆盖默认的 PropertyEditor 机制。

  9. List<StringValueResolver> embeddedValueResolvers = new CopyOnWriteArrayList<>():内置的字符串值解析器列表。

  10. List<BeanPostProcessor> beanPostProcessors = new BeanPostProcessorCacheAwareList()BeanPostProcessor 列表。关于它的内容,在 Spring Bean 生命周期概述 中有详细地介绍。

  11. BeanPostProcessorCache beanPostProcessorCacheBeanPostProcessor 缓存,会根据类型,缓存到不同的列表中。

  12. Map<String, Scope> scopes = new LinkedHashMap<>(8)scope 字符串到具体 Scope 实例的映射。

  13. Map<String, RootBeanDefinition> mergedBeanDefinitions = new ConcurrentHashMap<>(256):Bean 名称到 RootBeanDefinition 的映射。

  14. Set<String> alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256)): 已经创建的 Bean 名称。

  15. ThreadLocal<Object> prototypesCurrentlyInCreation = new NamedThreadLocal<>("Prototype beans currently in creation"):正常创建的 Bean。

  16. InstantiationStrategy instantiationStrategy:Bean 实例创建策略。

  17. ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer():方法参数名的解析策略。

  18. boolean allowCircularReferences = true:是否循环依赖。

  19. boolean allowRawInjectionDespiteWrapping = false:在循环引用的情况下,是否注入原始 Bean 实例,即使注入的 Bean 最终被包装。

  20. Set<Class<?>> ignoredDependencyTypes = new HashSet<>():忽略的依赖类型。

  21. Set<Class<?>> ignoredDependencyInterfaces = new HashSet<>():忽略的依赖接口。

  22. NamedThreadLocal<String> currentlyCreatedBean = new NamedThreadLocal<>("Currently created bean"):正在创建的 Bean。

  23. ConcurrentMap<String, BeanWrapper> factoryBeanInstanceCache = new ConcurrentHashMap<>():工厂 Bean 实例。

  24. ConcurrentMap<Class<?>, Method[]> factoryMethodCandidateCache = new ConcurrentHashMap<>():工厂方法缓存。

  25. ConcurrentMap<Class<?>, PropertyDescriptor[]> filteredPropertyDescriptorsCache = new ConcurrentHashMap<>():过滤后的 PropertyDescriptor 缓存。

  26. Map<String, Reference<DefaultListableBeanFactory>> serializableFactories = new ConcurrentHashMap<>(8):可序列化的 DefaultListableBeanFactory

  27. boolean allowBeanDefinitionOverriding = true:是否运行 BeanDefinition 覆盖。

  28. boolean allowEagerClassLoading = true:是否允许类急切加载。

  29. Comparator<Object> dependencyComparator:依赖排序器。

  30. AutowireCandidateResolver autowireCandidateResolver = SimpleAutowireCandidateResolver.INSTANCE:注入候选者解析器。

  31. Map<Class<?>, Object> resolvableDependencies = new ConcurrentHashMap<>(16):依赖类型到合适的注入对象的映射。

  32. Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256):Bean 名称到 BeanDefinition 的映射。关于 BeanDefinition深入剖析 Spring 核心数据结构:BeanDefinition 有更详细的介绍。

  33. Map<String, BeanDefinitionHolder> mergedBeanDefinitionHolders = new ConcurrentHashMap<>(256):Bean 名称到 BeanDefinitionHolder 的映射。

  34. Map<Class<?>, String[]> allBeanNamesByType = new ConcurrentHashMap<>(64):类型到所有该类型的 Bean 名称的映射。

  35. Map<Class<?>, String[]> singletonBeanNamesByType = new ConcurrentHashMap<>(64):类型到所有该类型的单例 Bean 名称的映射。

  36. List<String> beanDefinitionNames = new ArrayList<>(256):Bean 名称列表。

  37. Set<String> manualSingletonNames = new LinkedHashSet<>(16)

  38. String[] frozenBeanDefinitionNames:冻结的 Bean 名称。

  39. boolean configurationFrozen:配置是否冻结。

从上面这些属性可以看出,所谓的容器,其实就是一个 Map 属性 Map<String, Object> singletonObjects。而 Bean 别名也是一个 Map 属性 Map<String, String> aliasMap。从别名到 Bean 实例只需要做两个 Map 查找就可以完成了。

在网上查了查资料,没有对这些属性做比较详细的介绍,这个文章也有很多不完善的地方,回头随着 D瓜哥对 Spring 代码的了解后续再逐步完善。

3.3. FactoryBean 详解

FactoryBeanTest
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package com.diguage.truman.beans;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Arrays;

/**
 * FactoryBean 测试
 *
 * @author D瓜哥 · https://www.diguage.com/
 * @since 2020-05-26 16:34
 */
public class FactoryBeanTest {
  private static final String FACTORY_BEAN_NAME = "userServiceFactoryBean";

  @Test
  public void test() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.register(Config.class);
    context.refresh();

    Object bean = context.getBean(FACTORY_BEAN_NAME);
    System.out.println(bean.getClass().getName());

    UserService userService = context.getBean(UserService.class);
    System.out.println(userService.getById(119L));

    // 实例不会缓存,每次调用 getBean 都会创建一个实例
    UserService userService2 = context.getBean(UserService.class);
    System.out.println(userService == userService2);

    System.out.println("-↓----");
    // &userServiceFactoryBean = FactoryBeanTest$UserServiceFactoryBean@c260bdc
    System.out.println("&userServiceFactoryBean = "
        + context.getBean("&userServiceFactoryBean")); (1)

    //  userServiceFactoryBean = FactoryBeanTest$UserService@75e01201
    System.out.println(" userServiceFactoryBean = "
        + context.getBean("userServiceFactoryBean"));  (2)
    System.out.println("-↑----");

    UserServiceFactoryBean factoryBean = context.getBean(UserServiceFactoryBean.class);
    System.out.println(factoryBean);
    System.out.println(Arrays.toString(context.getBeanDefinitionNames())
        .replaceAll(",", ",\n"));
  }

  @Configuration
  public static class Config {
    @Bean(FACTORY_BEAN_NAME)
    public UserServiceFactoryBean userServiceFactoryBean() {
      return new UserServiceFactoryBean();
    }
  }


  public static class UserService {
    public String getById(Long id) {
      return "Name-" + id;
    }
  }

  public static class UserServiceFactoryBean implements FactoryBean<UserService> {
    @Override
    public UserService getObject() throws Exception {
      return new UserService();
    }

    @Override
    public Class<?> getObjectType() {
      return UserService.class;
    }

    @Override
    public boolean isSingleton() {
      return true;
    }
  }
}
1 获取 FactoryBean 的实现类的实例。
2 获取 FactoryBeangetObject() 方法创建的实例。

FactoryBean 实现类创建的 Bean 实例,在实际 getBean 之前,不会创建。 会根据 isSingleton() 方法的返回值来决定创建之后是否缓存实例。

FactoryBean 实现类创建的 Bean 实例并不是存储在 singletonObjects 实例变量中(由 DefaultSingletonBeanRegistry 声明, AbstractBeanFactory 间接继承了 DefaultSingletonBeanRegistry),而是保存在 factoryBeanObjectCache 实例变量中(由 FactoryBeanRegistrySupport 声明, AbstractBeanFactory 直接继承了 FactoryBeanRegistrySupport)。具体情况,请参考类图: BeanFactory 继承体系及关键属性

3.4. Environment

Environment 主要用于读取当前应用运行环境的环境变量和一些配置信息。另外,常见的指定不同配置的 spring.profiles.active 的处理,也是由 Environment 来处理。

Diagram

3.5. ApplicationContext

总使用 AnnotationConfigApplicationContext 做实验,有可能会遗漏一些重要的细节。增加一个 XML 的示例。

XmlApplicationContextTest
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.diguage.truman.context;

import org.junit.jupiter.api.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author D瓜哥 · https://www.diguage.com
 * @since 2022-10-27 22:46:56
 */
public class XmlApplicationContextTest {

  @Test
  public void test() {
    ClassPathXmlApplicationContext context
        = new ClassPathXmlApplicationContext("classpath:com/diguage/"
        + "truman/context/XmlApplicationContextTest.xml");
    UserService userService = context.getBean(UserService.class);
    System.out.println(userService.getUserById(119L));
  }

  public static class UserService {
    public String getUserById(Long id) {
      return "user-" + id;
    }
  }
}
XmlApplicationContextTest.xml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
          http://www.springframework.org/schema/beans/spring-beans.xsd
          http://www.springframework.org/schema/context
          https://www.springframework.org/schema/context/spring-context.xsd">

  <!-- @author D瓜哥 · https://www.diguage.com -->

  <context:annotation-config/>

  <bean id="userService"
        class="com.diguage.truman.context.XmlApplicationContextTest.UserService"/>
</beans>

3.6. 启动流程概述

对于 Spring 启动流程和 Bean 的生命周期,总有一些小地方搞的不是很清楚,干脆直接通过修改代码增加日志输出,使用断点单步调试,把整个流程捋顺了一点点的。

除了加载配置文件或者基础配置类外,Spring 的启动过程几乎都被封装在 AbstractApplicationContext#refresh 方法中,可以说弄清楚了这个方法的执行过程,就摸清楚了 Spring 启动全流程,下面的流程分析也是以这个方法为骨架来展开的。

3.6.1. 流程概要

下面完整流程有些太复杂,所以,提炼一个简要的过程,方便糊弄面试官,哈哈哈😆

  1. 创建容器,读取 applicationContext.register(Config.class) 指定的配置。

  2. 准备 BeanFactory,注册容器本身和 BeanFactory 实例,以及注册环境配置信息等。

  3. 执行 BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry 注册 BeanDefinition。有三点需要注意:

    1. 目前只有一个 ConfigurationClassPostProcessor 实现类,Spring 中大量的 Bean 都是在这一步被该类注册到容器中的。

    2. 执行顺序是 ① PriorityOrderedOrdered ③ 普通的顺序来执行

    3. 在执行上一步是,如果发现注册了 BeanDefinitionRegistryPostProcessor 类型的 Bean,就会在循环里继续调用 postProcessBeanDefinitionRegistry 方法。MyBATIS 和 Spring 整合的 MapperScannerConfigurer 类就是在这一步执行的。

  4. 执行 BeanFactoryPostProcessor#postProcessBeanFactory 方法。目前只有一个 ConfigurationClassPostProcessor 实现类。

  5. 注册 CommonAnnotationBeanPostProcessorAutowiredAnnotationBeanPostProcessorBeanPostProcessor

  6. 注册 ApplicationEventMulticaster,用于广播事件的。

  7. 注册 ApplicationListener

  8. 预加载以及注册所有非懒加载的 Bean

Diagram
Diagram
Diagram
Diagram
Diagram
Diagram

3.6.2. 完整启动流程

  1. 调用 prepareRefresh() 方法,初始化属性源(property source)配置。

  2. 调用 obtainFreshBeanFactory() 获得 ConfigurableListableBeanFactory 对象。

  3. 调用 prepareBeanFactory,准备 BeanFactory,添加必要的 Bean;添加 ApplicationContextAwareProcessorApplicationListenerDetector 处理器;注册环境相关的 Bean。

  4. 下面通过 AbstractApplicationContext#invokeBeanFactoryPostProcessors 方法,开始执行 BeanDefinitionRegistryPostProcessorBeanFactoryPostProcessor 相关的方法。这个方法流程起始也很简单:

    目前,除了用户自定义的 BeanDefinitionRegistryPostProcessorBeanFactoryPostProcessor 外,Spring 内置的,只有 ConfigurationClassPostProcessor 一个类。所以,把这个类的实现摸清楚了,AbstractApplicationContext#invokeBeanFactoryPostProcessors 就可以跳过了。

    1. 首先,执行 BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry 方法,顺序如下:

      1. 用户手动添加的 BeanDefinitionRegistryPostProcessor

      2. 实现 PriorityOrdered 接口的 BeanDefinitionRegistryPostProcessor

      3. 实现 Ordered 接口的 BeanDefinitionRegistryPostProcessor

      4. 普通 BeanDefinitionRegistryPostProcessor,只要发现有新加入的,就循环调用。

    2. 然后,执行 BeanFactoryPostProcessor#postProcessBeanFactory 方法。顺序如下:

      1. 实现 BeanDefinitionRegistryPostProcessor 接口的类;

      2. 实现 BeanFactoryPostProcessor 接口的类。

  5. 先执行用户手动添加的 BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry(DefaultListableBeanFactory)

    关于 BeanDefinitionRegistryPostProcessor 的处理流程,D瓜哥在 Spring 扩展点概览及实践:BeanDefinitionRegistryPostProcessor 中有更详细的描述,不了解的朋友请参考那篇文章的介绍。

  6. 创建 ConfigurationClassPostProcessor 对象,并针对该对象依次执行

    1. 构造函数

    2. ApplicationListenerDetector#postProcessMergedBeanDefinition(RootBeanDefinition, ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor)

    3. 调用用户手动添加的 BeanPostProcessor#postProcessBeforeInitialization 方法

    4. ApplicationContextAwareProcessor#postProcessBeforeInitialization(ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor)

    5. ApplicationListenerDetector#postProcessBeforeInitialization(ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor)

    6. 执行 init 方法

    7. 调用用户手动添加的 BeanPostProcessor#postProcessAfterInitialization 方法

    8. ApplicationContextAwareProcessor#postProcessAfterInitialization(ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor) — 由于 ApplicationContextAwareProcessor 并没有该方法,所以不执行。

    9. ApplicationListenerDetector#postProcessBeforeInitialization(ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor)

  7. 执行 ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry(DefaultListableBeanFactory) — 在这里,处理 @Configuration@Import@ImportResource@Bean 和 。

  8. 执行用户手动添加的 BeanDefinitionRegistryPostProcessor#postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)

  9. 执行 ConfigurationClassPostProcessor#postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) — 在这里给 @Configuration 标注的类,生成 cglib 增强后的代理类。注意:在这里,还增加了一个 ImportAwareBeanPostProcessor 后置处理器。

    因为 ConfigurationClassPostProcessor 是一个 InstantiationAwareBeanPostProcessor 实例。所以,实例化 ConfigurationClassPostProcessor 对象并加入到容器后。这句话啥意思?想想再补充一下。

  10. 创建了 EventListenerMethodProcessor 实例,和创建 ConfigurationClassPostProcessor 时类似,依次执行

    1. InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation — 目前有 ImportAwareBeanPostProcessor

    2. 构造函数

    3. MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition — 目前有 ApplicationListenerDetector

    4. InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation

    5. InstantiationAwareBeanPostProcessor#postProcessProperties — 目前有 ImportAwareBeanPostProcessor

    6. InstantiationAwareBeanPostProcessor#postProcessPropertyValues — 从 5.1 开始废弃,使用上面方法代替。

    7. BeanPostProcessor#postProcessBeforeInitialization — 目前有

      1. 用户手动添加的 BeanPostProcessor

      2. ApplicationContextAwareProcessor

      3. ApplicationListenerDetector

      4. ImportAwareBeanPostProcessor

    8. init

    9. BeanPostProcessor#postProcessAfterInitialization 方法。 — 与 postProcessBeforeInitialization 相同,不再赘述。

      有一点需要注意,上面增加了 ImportAwareBeanPostProcessor 实例,这里也会执行。以下都是如此,不再赘述。

  11. 实例化用户通过 BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry(DefaultListableBeanFactory) 或者 @Configuration 添加的 BeanFactoryPostProcessor,以及 Spring 自己添加的 BeanFactoryPostProcessor。依次执行如下方法:

    1. InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation — 目前有 ImportAwareBeanPostProcessor

    2. Bean 的构造函数

    3. MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition — 目前有 ApplicationListenerDetector

    4. InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation

    5. InstantiationAwareBeanPostProcessor#postProcessProperties — 目前有 ImportAwareBeanPostProcessor

    6. InstantiationAwareBeanPostProcessor#postProcessPropertyValues — 从 5.1 开始废弃,使用上面方法代替。

    7. BeanPostProcessor#postProcessBeforeInitialization — 目前有

      1. 用户手动添加的 BeanPostProcessor

      2. ApplicationContextAwareProcessor

      3. ApplicationListenerDetector

      4. ImportAwareBeanPostProcessor

    8. init

    9. BeanPostProcessor#postProcessAfterInitialization 方法

  12. 调用上一步创建的 BeanFactoryPostProcessor 对象的 postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) 方法。这里目前包含 EventListenerMethodProcessor 对象。EventListenerMethodProcessorAnnotationConfigApplicationContext() 初始化时,创建 new AnnotatedBeanDefinitionReader(this) 对象时,通过调用 AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry) 方法注册到容器中的。

    1. 这里调用 EventListenerMethodProcessor#postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory),创建 EventListenerFactory 对象,依次执行

      这个 EventListenerFactory 对象不重要。或者说,目前没有发现它特别重要的地方。

      1. InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation

      2. Bean 的构造函数

      3. MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition

      4. InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation

      5. InstantiationAwareBeanPostProcessor#postProcessProperties

      6. InstantiationAwareBeanPostProcessor#postProcessPropertyValues — 从 5.1 开始废弃,使用上面方法代替。

      7. BeanPostProcessor#postProcessBeforeInitialization

      8. init

      9. BeanPostProcessor#postProcessAfterInitialization 方法

  13. 到此为止,invokeBeanFactoryPostProcessors(beanFactory) 方法调用完毕。

  14. 下面开始调用 registerBeanPostProcessors(beanFactory) 方法。

  15. 添加 PostProcessorRegistrationDelegate.BeanPostProcessorChecker 实例,以下执行 BeanPostProcessor 方法时,都会带上。

  16. 创建 AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor 对象,依次执行如下方法:

    1. InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation — 目前有 ImportAwareBeanPostProcessor

    2. 构造函数

    3. MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition — 目前有 ApplicationListenerDetector

    4. InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation

    5. InstantiationAwareBeanPostProcessor#postProcessProperties

    6. InstantiationAwareBeanPostProcessor#postProcessPropertyValues — 从 5.1 开始废弃,使用上面方法代替。

    7. AutowiredAnnotationBeanPostProcessor#setBeanFactory(DefaultListableBeanFactory) — 完成 BeanNameAwareBeanClassLoaderAwareBeanFactoryAware 三个 Aware 的注入。通过 AbstractAutowireCapableBeanFactory#invokeAwareMethods 方法来完成。

    8. BeanPostProcessor#postProcessBeforeInitialization — 目前有

      1. 用户手动添加的 BeanPostProcessor

      2. ApplicationContextAwareProcessor — 完成如下六个 Aware 的注入:

        1. EnvironmentAware

        2. EmbeddedValueResolverAware

        3. ResourceLoaderAware

        4. ApplicationEventPublisherAware

        5. MessageSourceAware

        6. ApplicationContextAware

      3. ApplicationListenerDetector

      4. ImportAwareBeanPostProcessor

      5. BeanPostProcessorChecker

    9. init

    10. BeanPostProcessor#postProcessAfterInitialization 方法

  17. AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor 对象注册到容器中。以下会随着 BeanPostProcessor 的调用,也会被执行。

  18. 创建 AnnotationAwareAspectJAutoProxyCreator 对象,依次执行如下方法:

    1. InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation — 目前有如下三个:

      1. ImportAwareBeanPostProcessor

      2. CommonAnnotationBeanPostProcessor

      3. AutowiredAnnotationBeanPostProcessor

    2. 构造函数

    3. MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition — 目前有如下三个:

      1. ApplicationListenerDetector

      2. CommonAnnotationBeanPostProcessor — 收集依赖信息。

      3. AutowiredAnnotationBeanPostProcessor — 收集依赖信息。

    4. InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation

    5. InstantiationAwareBeanPostProcessor#postProcessProperties 目前有如下三个:

      1. ImportAwareBeanPostProcessor

      2. CommonAnnotationBeanPostProcessor — 完成依赖注入。

      3. AutowiredAnnotationBeanPostProcessor — 完成依赖注入。

    6. InstantiationAwareBeanPostProcessor#postProcessPropertyValues — 从 5.1 开始废弃,使用上面方法代替。

    7. BeanPostProcessor#postProcessBeforeInitialization — 目前有

      1. 用户手动添加的 BeanPostProcessor

      2. ApplicationContextAwareProcessor — 完成如下六个 Aware 的注入:

        1. EnvironmentAware

        2. EmbeddedValueResolverAware

        3. ResourceLoaderAware

        4. ApplicationEventPublisherAware

        5. MessageSourceAware

        6. ApplicationContextAware

      3. ApplicationListenerDetector

      4. ImportAwareBeanPostProcessor

      5. BeanPostProcessorChecker

      6. CommonAnnotationBeanPostProcessor

      7. AutowiredAnnotationBeanPostProcessor

    8. init

    9. BeanPostProcessor#postProcessAfterInitialization 方法

  19. AnnotationAwareAspectJAutoProxyCreator 对象注册到容器中。以下会随着 BeanPostProcessor 的调用,也会被执行。

  20. 重新添加 ApplicationListenerDetector,其实就是换了个位置,将其调整到了最后。

  21. 到此为止,registerBeanPostProcessors(beanFactory) 方法调用完毕。

  22. 调用 initMessageSource() 方法,注册 MessageSource Bean。

  23. 调用 initApplicationEventMulticaster() 方法,注册 SimpleApplicationEventMulticaster 对象,

  24. 调用 onRefresh() 方法,这是空方法,方便做扩展。

  25. 调用 registerListeners() 方法,但是似乎什么也没做。

  26. 调用 finishBeanFactoryInitialization(beanFactory) 方法,这个方法中,最重要的一个操作就是实例化非懒加载的所有 Bean,在 DefaultListableBeanFactory#preInstantiateSingletons 中完成这些操作。目前,除了用户自己实现的,还有七个如下的 BeanPostProcessor

    1. ApplicationContextAwareProcessor

    2. ConfigurationClassPostProcessor

    3. BeanPostProcessorChecker

    4. AnnotationAwareAspectJAutoProxyCreator

    5. CommonAnnotationBeanPostProcessor

    6. AutowiredAnnotationBeanPostProcessor

    7. ApplicationListenerDetector

      这部分内容放在下一篇文章 Spring Bean 生命周期概述 再展开来讲。

  27. 调用 finishRefresh() — 启动生命周期函数,广播刷新完成通知。具体如下:

    1. 清理 Resource 缓存(也就是被扫描到的各种类,自定义类,以及相关父类和所实现的接口)。(像是在 ImportSelector 中声明的类。但是没有找到添加到缓存的地方?)

    2. 注册 LifecycleProcessor,并通过它启动所有的 LifecycleProcessor 和它自身。没有看出来干什么用的?

    3. 广播 ContextRefreshedEvent 事件。

    4. ConfigurableApplicationContext 注册到 LiveBeansView 上,如果它存在的话。

    5. 清理各种缓存

      1. 启动过程中的反射相关缓存,比如 init-methodAware 相关的方法,注入需要的字段等等;

      2. AnnotationFilter 相关缓存;

      3. 注解元素缓存和生命周期函数(AwareInitializingBean、`BeanFactoryPostProcessor`等)缓存清空

      4. 解析类型缓存清空

      5. 反省结果清空

在下一篇文章 Spring Bean 生命周期概述 中,D瓜哥将针对 Spring Bean 的整个生命周期展开详细说明。

3.6.3. 附录:启动日志

下面是启动日志。有删减,为了方便阅读,增加了序号和层次。

  1. 调用 prepareRefresh() 方法,初始化属性源(property source)配置。

  2. 调用 obtainFreshBeanFactory() 获得 ConfigurableListableBeanFactory 对象。

  3. 准备 BeanFactory,添加必要的 Bean,在 prepareBeanFactory 中完成。

  4. 下面通过 invokeBeanFactoryPostProcessors 方法,开始执行 BeanFactoryPostProcessor 相关的方法

  5. LogBeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry(DefaultListableBeanFactory) — 用户自己手动添加的 BeanDefinitionRegistryPostProcessor 实例

  6. 创建 ConfigurationClassPostProcessor Bean

    1. 构造函数

    2. ApplicationListenerDetector#postProcessMergedBeanDefinition(RootBeanDefinition, ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor) — ApplicationListenerDetector 实例是在 prepareBeanFactory 方法中,加入到容器中的。

    3. LogBeanPostProcessor#postProcessBeforeInitialization(ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor) — 用户自己手动添加

    4. LogDestructionAwareBeanPostProcessor#postProcessBeforeInitialization(ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor) — 用户自己手动添加,继承默认实现。

    5. ApplicationContextAwareProcessor#postProcessBeforeInitialization(ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor) — ApplicationContextAwareProcessor 实例是在 prepareBeanFactory 方法中,加入到容器中的。处理六种 Aware 注入。

    6. ApplicationListenerDetector#postProcessBeforeInitialization(ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor)

    7. LogBeanPostProcessor#postProcessAfterInitialization(ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor)

    8. LogDestructionAwareBeanPostProcessor#postProcessAfterInitialization(ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor) — 用户自己手动添加,继承默认实现,没有任何操作。

    9. ApplicationContextAwareProcessor#postProcessAfterInitialization(ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor) — 继承默认实现,没有任何操作。

    10. ApplicationListenerDetector#postProcessAfterInitialization(ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor)

  7. ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry(DefaultListableBeanFactory) — 在这里,处理 @Configuration@Import@ImportResource@Bean 和 。

  8. LogBeanDefinitionRegistryPostProcessor#postProcessBeanFactory(DefaultListableBeanFactory)

  9. ConfigurationClassPostProcessor#postProcessBeanFactory(DefaultListableBeanFactory) — 在这里给 @Configuration 标注的类,生成 cglib 增强后的代理类。注意:在这里,还增加了一个 ImportAwareBeanPostProcessor 后置处理器。

    因为 ConfigurationClassPostProcessor 是一个 InstantiationAwareBeanPostProcessor 实例。所以,实例化 ConfigurationClassPostProcessor 对象并加入到容器后。这句话啥意思?想想再补充一下。

  10. 创建 EventListenerMethodProcessor Bean, Name: org.springframework.context.event.internalEventListenerProcessor

    1. ImportAwareBeanPostProcessor#postProcessBeforeInstantiation(EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)

    2. 构造函数

    3. ApplicationListenerDetector#postProcessMergedBeanDefinition(RootBeanDefinition, EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)

    4. ImportAwareBeanPostProcessor#postProcessAfterInstantiation(EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)

    5. ImportAwareBeanPostProcessor#postProcessProperties(MutablePropertyValues, EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)

    6. LogBeanPostProcessor#postProcessBeforeInitialization(EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)

    7. LogDestructionAwareBeanPostProcessor#postProcessBeforeInitialization(EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)

    8. ApplicationContextAwareProcessor#postProcessBeforeInitialization(EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)

    9. ApplicationListenerDetector#postProcessBeforeInitialization(EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)

    10. ImportAwareBeanPostProcessor#postProcessBeforeInitialization(EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)

    11. LogBeanPostProcessor#postProcessAfterInitialization(EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)

    12. LogDestructionAwareBeanPostProcessor#postProcessAfterInitialization(EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)

    13. ApplicationContextAwareProcessor#postProcessAfterInitialization(EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)

    14. ApplicationListenerDetector#postProcessAfterInitialization(EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)

    15. ImportAwareBeanPostProcessor#postProcessAfterInitialization(EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)

  11. 创建自定义 LogBeanFactoryPostProcessor,通过上面 LogBeanDefinitionRegistryPostProcessorpostProcessBeanDefinitionRegistry 方法添加。在这一步创建用户通过 BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry(DefaultListableBeanFactory) 或者 @Configuration 添加的 BeanFactoryPostProcessor,以及 Spring 自己添加的 BeanFactoryPostProcessor 等类的相关 Bean。

    1. ImportAwareBeanPostProcessor#postProcessBeforeInstantiation(LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)

    2. ApplicationListenerDetector#postProcessMergedBeanDefinition(RootBeanDefinition, LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)

    3. ImportAwareBeanPostProcessor#postProcessAfterInstantiation(LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)

    4. ImportAwareBeanPostProcessor#postProcessProperties(MutablePropertyValues, LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)

    5. LogBeanPostProcessor#postProcessBeforeInitialization(LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)

    6. LogDestructionAwareBeanPostProcessor#postProcessBeforeInitialization(LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)

    7. ApplicationContextAwareProcessor#postProcessBeforeInitialization(LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)

    8. ApplicationListenerDetector#postProcessBeforeInitialization(LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)

    9. ImportAwareBeanPostProcessor#postProcessBeforeInitialization(LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)

    10. LogBeanPostProcessor#postProcessAfterInitialization(LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)

    11. LogDestructionAwareBeanPostProcessor#postProcessAfterInitialization(LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)

    12. ApplicationContextAwareProcessor#postProcessAfterInitialization(LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)

    13. ApplicationListenerDetector#postProcessAfterInitialization(LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)

    14. ImportAwareBeanPostProcessor#postProcessAfterInitialization(LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)

  12. 这里会调用上一步创建的 BeanFactoryPostProcessor 对象的 postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) 方法。这里目前包含 EventListenerMethodProcessor 对象。EventListenerMethodProcessorAnnotationConfigApplicationContext() 初始化时,创建 new AnnotatedBeanDefinitionReader(this) 对象时,通过调用 AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry) 方法注册到容器中的。

  13. LogBeanFactoryPostProcessor#postProcessBeanFactory(DefaultListableBeanFactory)

  14. 到此为止,invokeBeanFactoryPostProcessors(beanFactory) 方法调用完毕。

  15. 下面开始调用 registerBeanPostProcessors(beanFactory) 方法。

  16. 添加 PostProcessorRegistrationDelegate.BeanPostProcessorChecker 实例,以下执行 BeanPostProcessor 方法时,都会带上。

  17. 创建 AutowiredAnnotationBeanPostProcessor Bean,Name: org.springframework.context.annotation.internalAutowiredAnnotationProcessor

    1. ImportAwareBeanPostProcessor#postProcessBeforeInstantiation(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)

    2. ApplicationListenerDetector#postProcessMergedBeanDefinition(RootBeanDefinition, AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)

    3. ImportAwareBeanPostProcessor#postProcessAfterInstantiation(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)

    4. ImportAwareBeanPostProcessor#postProcessProperties(MutablePropertyValues, AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)

    5. AutowiredAnnotationBeanPostProcessor#setBeanFactory(DefaultListableBeanFactory)

    6. LogBeanPostProcessor#postProcessBeforeInitialization(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)

    7. LogDestructionAwareBeanPostProcessor#postProcessBeforeInitialization(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)

    8. ApplicationContextAwareProcessor#postProcessBeforeInitialization(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)

    9. ApplicationListenerDetector#postProcessBeforeInitialization(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)

    10. ImportAwareBeanPostProcessor#postProcessBeforeInitialization(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)

    11. BeanPostProcessorChecker#postProcessBeforeInitialization(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)

    12. LogBeanPostProcessor#postProcessAfterInitialization(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)

    13. LogDestructionAwareBeanPostProcessor#postProcessAfterInitialization(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)

    14. ApplicationContextAwareProcessor#postProcessAfterInitialization(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)

    15. ApplicationListenerDetector#postProcessAfterInitialization(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)

    16. ImportAwareBeanPostProcessor#postProcessAfterInitialization(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)

    17. BeanPostProcessorChecker#postProcessAfterInitialization(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)

  18. 创建 CommonAnnotationBeanPostProcessor Bean,Name: org.springframework.context.annotation.internalCommonAnnotationProcessor

    1. ImportAwareBeanPostProcessor#postProcessBeforeInstantiation(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)

    2. ApplicationListenerDetector#postProcessMergedBeanDefinition(RootBeanDefinition, CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)

    3. ImportAwareBeanPostProcessor#postProcessAfterInstantiation(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)

    4. ImportAwareBeanPostProcessor#postProcessProperties(MutablePropertyValues, CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)

    5. LogBeanPostProcessor#postProcessBeforeInitialization(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)

    6. LogDestructionAwareBeanPostProcessor#postProcessBeforeInitialization(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)

    7. ApplicationContextAwareProcessor#postProcessBeforeInitialization(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)

    8. ApplicationListenerDetector#postProcessBeforeInitialization(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)

    9. ImportAwareBeanPostProcessor#postProcessBeforeInitialization(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)

    10. BeanPostProcessorChecker#postProcessBeforeInitialization(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)

    11. LogBeanPostProcessor#postProcessAfterInitialization(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)

    12. LogDestructionAwareBeanPostProcessor#postProcessAfterInitialization(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)

    13. ApplicationContextAwareProcessor#postProcessAfterInitialization(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)

    14. ApplicationListenerDetector#postProcessAfterInitialization(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)

    15. ImportAwareBeanPostProcessor#postProcessAfterInitialization(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)

    16. BeanPostProcessorChecker#postProcessAfterInitialization(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)

  19. 创建 AnnotationAwareAspectJAutoProxyCreator,Name: org.springframework.aop.config.internalAutoProxyCreator。也许是因为配置了 @EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)这个再探究竟?

    1. ImportAwareBeanPostProcessor#postProcessBeforeInstantiation(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)

    2. CommonAnnotationBeanPostProcessor#postProcessBeforeInstantiation(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)

    3. AutowiredAnnotationBeanPostProcessor#postProcessBeforeInstantiation(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)

    4. ApplicationListenerDetector#postProcessMergedBeanDefinition(RootBeanDefinition, AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)

    5. CommonAnnotationBeanPostProcessor#postProcessMergedBeanDefinition(RootBeanDefinition, AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)

    6. AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition(RootBeanDefinition, AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)

    7. ImportAwareBeanPostProcessor#postProcessAfterInstantiation(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)

    8. CommonAnnotationBeanPostProcessor#postProcessAfterInstantiation(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)

    9. AutowiredAnnotationBeanPostProcessor#postProcessAfterInstantiation(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)

    10. ImportAwareBeanPostProcessor#postProcessProperties(MutablePropertyValues, AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)

    11. CommonAnnotationBeanPostProcessor#postProcessProperties(MutablePropertyValues, AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)

    12. AutowiredAnnotationBeanPostProcessor#postProcessProperties(MutablePropertyValues, AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)

    13. LogBeanPostProcessor#postProcessBeforeInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)

    14. LogDestructionAwareBeanPostProcessor#postProcessBeforeInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)

    15. ApplicationContextAwareProcessor#postProcessBeforeInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)

    16. ApplicationListenerDetector#postProcessBeforeInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)

    17. ImportAwareBeanPostProcessor#postProcessBeforeInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)

    18. BeanPostProcessorChecker#postProcessBeforeInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)

    19. CommonAnnotationBeanPostProcessor#postProcessBeforeInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)

    20. AutowiredAnnotationBeanPostProcessor#postProcessBeforeInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)

    21. LogBeanPostProcessor#postProcessAfterInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)

    22. LogDestructionAwareBeanPostProcessor#postProcessAfterInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)

    23. ApplicationContextAwareProcessor#postProcessAfterInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)

    24. ApplicationListenerDetector#postProcessAfterInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)

    25. ImportAwareBeanPostProcessor#postProcessAfterInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)

    26. BeanPostProcessorChecker#postProcessAfterInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)

    27. CommonAnnotationBeanPostProcessor#postProcessAfterInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)

    28. AutowiredAnnotationBeanPostProcessor#postProcessAfterInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)

  20. 预加载 ConfigUserService 等 Bean。下面以 UserService 为例:

    1. ImportAwareBeanPostProcessor#postProcessBeforeInstantiation(UserService, UserService)

    2. AnnotationAwareAspectJAutoProxyCreator#postProcessBeforeInstantiation(UserService, UserService)

    3. CommonAnnotationBeanPostProcessor#postProcessBeforeInstantiation(UserService, UserService)

    4. AutowiredAnnotationBeanPostProcessor#postProcessBeforeInstantiation(UserService, UserService)

    5. 构造函数

    6. CommonAnnotationBeanPostProcessor#postProcessMergedBeanDefinition(RootBeanDefinition, UserService, UserService)

    7. AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition(RootBeanDefinition, UserService, UserService)

    8. ApplicationListenerDetector#postProcessMergedBeanDefinition(RootBeanDefinition, UserService, UserService)

    9. ImportAwareBeanPostProcessor#postProcessAfterInstantiation(UserService, UserService)

    10. AnnotationAwareAspectJAutoProxyCreator#postProcessAfterInstantiation(UserService, UserService)

    11. CommonAnnotationBeanPostProcessor#postProcessAfterInstantiation(UserService, UserService)

    12. AutowiredAnnotationBeanPostProcessor#postProcessAfterInstantiation(UserService, UserService)

    13. ImportAwareBeanPostProcessor#postProcessProperties(MutablePropertyValues, UserService, UserService)

    14. AnnotationAwareAspectJAutoProxyCreator#postProcessProperties(MutablePropertyValues, UserService, UserService)

    15. AnnotationAwareAspectJAutoProxyCreator#postProcessPropertyValues(MutablePropertyValues, PropertyDescriptor[], UserService, UserService)

    16. CommonAnnotationBeanPostProcessor#postProcessProperties(MutablePropertyValues, UserService, UserService)

    17. AutowiredAnnotationBeanPostProcessor#postProcessProperties(MutablePropertyValues, UserService, UserService)

    18. UserService#setBeanFactory(DefaultListableBeanFactory)

    19. LogBeanPostProcessor#postProcessBeforeInitialization(UserService, UserService)

    20. LogDestructionAwareBeanPostProcessor#postProcessBeforeInitialization(UserService, UserService)

    21. ApplicationContextAwareProcessor#postProcessBeforeInitialization(UserService, UserService)

    22. UserService#setApplicationContext(AnnotationConfigApplicationContext)

    23. ImportAwareBeanPostProcessor#postProcessBeforeInitialization(UserService, UserService)

    24. BeanPostProcessorChecker#postProcessBeforeInitialization(UserService, UserService)

    25. AnnotationAwareAspectJAutoProxyCreator#postProcessBeforeInitialization(UserService, UserService)

    26. CommonAnnotationBeanPostProcessor#postProcessBeforeInitialization(UserService, UserService)

    27. AutowiredAnnotationBeanPostProcessor#postProcessBeforeInitialization(UserService, UserService)

    28. ApplicationListenerDetector#postProcessBeforeInitialization(UserService, UserService)

    29. UserService#afterPropertiesSet()

    30. UserService#init()

    31. LogBeanPostProcessor#postProcessAfterInitialization(UserService, UserService)

    32. LogDestructionAwareBeanPostProcessor#postProcessAfterInitialization(UserService, UserService)

    33. ApplicationContextAwareProcessor#postProcessAfterInitialization(UserService, UserService)

    34. ImportAwareBeanPostProcessor#postProcessAfterInitialization(UserService, UserService)

    35. BeanPostProcessorChecker#postProcessAfterInitialization(UserService, UserService)

    36. AnnotationAwareAspectJAutoProxyCreator#postProcessAfterInitialization(UserService, UserService)

    37. CommonAnnotationBeanPostProcessor#postProcessAfterInitialization(UserService, UserService)

    38. AutowiredAnnotationBeanPostProcessor#postProcessAfterInitialization(UserService, UserService)

    39. ApplicationListenerDetector#postProcessAfterInitialization(UserService, UserService)

  21. 销毁 Bean,beanFactory.destroyBean(bean)

    1. LogDestructionAwareBeanPostProcessor#postProcessBeforeDestruction(UserService, UserService)

    2. UserService#destroy()

不知道有没有人关注这个附录日志,这里再重复一遍:在下一篇文章 Spring Bean 生命周期概述 中,D瓜哥将针对 Spring Bean 的整个生命周期展开详细说明。

3.7. Bean 生命周期概述

Spring 启动流程概述 中,分析了 Spring 的启动流程。本文就来说明一下 Spring Bean 整个生命周期。如果有不清楚的地方,可以参考上文的“附录:启动日志”。

直接上图:Spring Bean 生命周期流程图。内容较多,图片文字偏小,请放大看(矢量图,可以任意放大):

Spring Bean 生命周期流程图
Figure 6. Spring Bean 生命周期流程图

下面是文字说明。

3.7.1. Bean 生命周期简述

  1. 调用 InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation,主要是判断 AnnotationAwareAspectJAutoProxyCreator 是否可以生成代理。

  2. 调用构造函数

  3. 调用 MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition,主要是通过 CommonAnnotationBeanPostProcessorAutowiredAnnotationBeanPostProcessor 收集依赖信息。

  4. InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation,这步什么也没做。

  5. 调用 InstantiationAwareBeanPostProcessor#postProcessProperties,主要是完成依赖注入。

  6. 调用 AutowiredAnnotationBeanPostProcessor#setBeanFactory,注入 BeanFactory 等相关信息。

  7. 调用 BeanPostProcessor#postProcessBeforeInitialization,主要是注入 ApplicationContext 等相关信息。

  8. 调用 InitializingBean#afterPropertiesSetinit-method 方法

  9. 调用 BeanPostProcessor#postProcessAfterInitialization,主要是生成 AOP 代理类。

3.7.2. Bean 生命周期详解

getBean() 方法获取 Bean 时,如果缓存中没有对应的 Bean,则会创建 Bean,整个流程如下:

  1. InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation — 目前有如下四个:

    1. ImportAwareBeanPostProcessor — 继承父类实现,无所事事。

    2. AnnotationAwareAspectJAutoProxyCreator — 继承父类实现,判断是否属于基础切面类,如果有指定的 Target 则生成代理。

    3. CommonAnnotationBeanPostProcessor — 无所事事。

    4. AutowiredAnnotationBeanPostProcessor — 继承父类实现,无所事事。

  2. 构造函数

  3. MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition — 目前有如下三个:

    1. CommonAnnotationBeanPostProcessor — 收集 @Resource 依赖信息,initMethodsdestroyMethods 等信息。(就是 @PostConstruct@PreDestroy 标注的方法。)这些信息被缓存到了 this.injectionMetadataCache 变量中,注入时从这个变量中取值。

    2. AutowiredAnnotationBeanPostProcessor — 收集 @Autowired 的依赖信息。这些信息被缓存到了 this.injectionMetadataCache 变量中,注入时从这个变量中取值。

    3. ApplicationListenerDetector — 判断 Bean 是否是一个 ApplicationListener,是则保留,在后面的 postProcessAfterInitialization 方法中,加入到容器的 applicationListeners 中。

  4. InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation — 与上面的 postProcessBeforeInstantiation 方法对应,目前有如下四个:

    1. ImportAwareBeanPostProcessor — 继承父类实现,无所事事。

    2. AnnotationAwareAspectJAutoProxyCreator — 继承父类实现,无所事事。

    3. CommonAnnotationBeanPostProcessor — 无所事事。

    4. AutowiredAnnotationBeanPostProcessor — 无所事事。

  5. InstantiationAwareBeanPostProcessor#postProcessProperties — 目前有如下三个:

    1. ImportAwareBeanPostProcessor — 如果 Bean 是 EnhancedConfiguration(它继承了 BeanFactoryAware) 的实现类,则注入 BeanFactory

    2. AnnotationAwareAspectJAutoProxyCreator — 无所事事。

    3. CommonAnnotationBeanPostProcessor — 完成 @Resource 依赖注入。

      在这里会递归创建所依赖 Bean。调试代码,弄清楚。

    4. AutowiredAnnotationBeanPostProcessor — 完成 @Autowired@Value 注入

  6. InstantiationAwareBeanPostProcessor#postProcessPropertyValues — 从 5.1 开始废弃,使用上面方法代替。

    这里要注意,并不是执行完四个类的 postProcessProperties 方法,再去执行四个类的 postProcessPropertyValues 方法。而是以类为顺序的,执行完一个类的 postProcessProperties 方法,然后去执行 postProcessPropertyValues 方法。执行完一个类,再去执行下一个类。这个现象在下面的日志中有反应。
  7. AutowiredAnnotationBeanPostProcessor#setBeanFactory(DefaultListableBeanFactory) — 通过 AbstractAutowireCapableBeanFactory#invokeAwareMethods 方法如下 Aware 注入:

    1. BeanNameAware

    2. BeanClassLoaderAware

    3. BeanFactoryAware

  8. BeanPostProcessor#postProcessBeforeInitialization — 目前有

    1. 用户手动添加的 BeanPostProcessor

    2. ApplicationContextAwareProcessor — 完成如下六个 Aware 的注入:

      1. EnvironmentAware

      2. EmbeddedValueResolverAware

      3. ResourceLoaderAware

      4. ApplicationEventPublisherAware

      5. MessageSourceAware

      6. ApplicationContextAware

    3. ImportAwareBeanPostProcessor — 如果实现了 ImportAware 接口,则注入 importMetadata 信息。

    4. BeanPostProcessorChecker — 无所事事。

    5. AnnotationAwareAspectJAutoProxyCreator — 无所事事。

    6. CommonAnnotationBeanPostProcessor — 要调用 LifecycleMetadata#invokeInitMethods 方法,但是,里面去没有任何实现,似乎调用了全局设置的初始化操作。需要找文档确认一下。

    7. AutowiredAnnotationBeanPostProcessor — 继承父类实现,无所事事。

    8. ApplicationListenerDetector — 无所事事。

  9. InitializingBean#afterPropertiesSet()

  10. init-method

  11. BeanPostProcessor#postProcessAfterInitialization 方法 — 目前有

    1. 用户手动添加的 BeanPostProcessor

    2. ApplicationContextAwareProcessor — 继承默认实现,无所事事。

    3. ImportAwareBeanPostProcessor — 继承默认实现,无所事事。

    4. BeanPostProcessorChecker — 如果 Bean 是 BeanPostProcessor 子类,则检查 BeanPostProcessor 数量。

    5. AnnotationAwareAspectJAutoProxyCreator — 检查 Bean 和提前暴露的引用是否相同,不同则重新生成代理对象。注意:绝大部分的 AOP 代理生成都是在这个方法中完成的。Spring AOP 源码分析:入门 中有更详细的说明。

    6. CommonAnnotationBeanPostProcessor — 继承父类实现,无所事事。

    7. AutowiredAnnotationBeanPostProcessor — 继承父类实现,无所事事。

    8. ApplicationListenerDetector — 将 ApplicationListener 类型的 Bean,加入到容器的 applicationListeners 中,方便容器开始监听。

初始化之前,似乎可以设置全局的初始化操作。忘了具体在哪个类中了?

3.7.3. Bean 生命周期补充说明

下面对创建 Bean 的流程做进一步说明:

3.7.3.1. InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation

通过 AbstractAutowireCapableBeanFactory#resolveBeforeInstantiation 方法,调用 InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation 方法。遍历 InstantiationAwareBeanPostProcessor 列表(getBeanPostProcessorCache().instantiationAware 变量)时,如果返回值不为空,则立即返回,不再继续调用。不为空,则表示创建了 Bean 对象,然后马上调用 BeanPostProcessor#postProcessAfterInitialization 方法。如果这里创建对象,则直接返回该对象,不再进行下面的调用。有四个 InstantiationAwareBeanPostProcessor 对象:

  1. ConfigurationClassPostProcessor

  2. AnnotationAwareAspectJAutoProxyCreator

  3. CommonAnnotationBeanPostProcessor

  4. AutowiredAnnotationBeanPostProcessor

3.7.3.2. Bean 的构造函数
3.7.3.3. MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition

通过 AbstractAutowireCapableBeanFactory#applyMergedBeanDefinitionPostProcessors 调用 MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition 方法。变量: getBeanPostProcessorCache().mergedDefinition这个方法主要干什么?通过 CommonAnnotationBeanPostProcessor#applyMergedBeanDefinitionPostProcessors 调用 CommonAnnotationBeanPostProcessor#findResourceMetadata 可以看出,这个地方可以获取依赖信息。带验证。系统中有如下四个类:

  1. CommonAnnotationBeanPostProcessor

  2. AutowiredAnnotationBeanPostProcessor

  3. ApplicationListenerDetector

  4. InitDestroyAnnotationBeanPostProcessor — 这个有吗?没有加入到变量中。

3.7.3.4. InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation

有一点重要的信息,日志中没有体现出来。设置 Bean 的属性是在执行 BeanPostProcessor 调用之前完成的。在 AbstractAutowireCapableBeanFactory#doCreateBean 方法中,调用了 AbstractAutowireCapableBeanFactory#populateBean 方法来设置属性,然后去调用的 BeanPostProcessorinit 方法。populateBean 方法是通过调用 InstantiationAwareBeanPostProcessor#postProcessProperties 方法来完成注入,其中 CommonAnnotationBeanPostProcessorAutowiredAnnotationBeanPostProcessor 分别处理不同的注解。下面是 populateBean 方法更详细的说明。

在注入 Bean 属性之前,调用 InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation。(从变量 getBeanPostProcessorCache().instantiationAware 中获取列表。)容器完成初始化后,有 ImportAwareBeanPostProcessorAnnotationAwareAspectJAutoProxyCreatorCommonAnnotationBeanPostProcessorAutowiredAnnotationBeanPostProcessor 四个 InstantiationAwareBeanPostProcessor 对象。但是,这四个类,没有做任何操作。如果返回值为 false 则中断,不再继续遍历 InstantiationAwareBeanPostProcessor 列表。

  1. ConfigurationClassPostProcessor

  2. AnnotationAwareAspectJAutoProxyCreator

  3. CommonAnnotationBeanPostProcessor

  4. AutowiredAnnotationBeanPostProcessor

3.7.3.5. InstantiationAwareBeanPostProcessor#postProcessProperties

接着调用 InstantiationAwareBeanPostProcessor#postProcessProperties 方法来完成属性注入。

3.7.3.6. InstantiationAwareBeanPostProcessor#postProcessPropertyValues

然后再执行 InstantiationAwareBeanPostProcessor#postProcessPropertyValues。这个方法马上从 5.1 开始要废弃掉,使用上述 postProcessProperties 代替。

到这里 populateBean 方法结束。

3.7.3.7. AutowiredAnnotationBeanPostProcessor#setBeanFactory(DefaultListableBeanFactory)
3.7.3.8. BeanPostProcessor#postProcessBeforeInitialization

调用 BeanPostProcessor#postProcessBeforeInitialization 方法。

3.7.3.9. InitializingBean#afterPropertiesSet()
3.7.3.10. init-method

init 方法。

3.7.3.11. BeanPostProcessor#postProcessAfterInitialization

调用 BeanPostProcessor#postProcessAfterInitialization 方法。

3.7.4. Bean 销毁流程

  1. 调用 beanFactory.destroyBean(bean) 方法,开始销毁 Bean。

  2. 调用 DestructionAwareBeanPostProcessor#postProcessBeforeDestruction(Object bean, String beanName) — ApplicationListenerDetector 就是一个 DestructionAwareBeanPostProcessor。但是,Bean 销毁时,不知道为什么没有被调用。

  3. 调用 DisposableBean#destroy() 方法

  4. 如果还有 destroy-method,接着通过反射调用 destroy-method 方法。

3.8. 扩展点概览及实践

学习 Spring 代码,最重要的是掌握 Spring 有哪些扩展点,可以利用这些扩展点对 Spring 做什么扩展操作。说得更具体一点,如果自己开发一个框架,如何与 Spring 进行整合,如果对 Spring 的扩展点有一个比较清晰的认识,势必会事半功倍。

3.8.1. @Import

先来看一下 @Import 注解的定义:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/*
 * Copyright 2002-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.context.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Indicates one or more <em>component classes</em> to import &mdash; typically
 * {@link Configuration @Configuration} classes.
 *
 * <p>Provides functionality equivalent to the {@code <import/>} element in Spring XML.
 * Allows for importing {@code @Configuration} classes, {@link ImportSelector} and
 * {@link ImportBeanDefinitionRegistrar} implementations, as well as regular component
 * classes (as of 4.2; analogous to {@link AnnotationConfigApplicationContext#register}).
 *
 * <p>{@code @Bean} definitions declared in imported {@code @Configuration} classes should be
 * accessed by using {@link org.springframework.beans.factory.annotation.Autowired @Autowired}
 * injection. Either the bean itself can be autowired, or the configuration class instance
 * declaring the bean can be autowired. The latter approach allows for explicit, IDE-friendly
 * navigation between {@code @Configuration} class methods.
 *
 * <p>May be declared at the class level or as a meta-annotation.
 *
 * <p>If XML or other non-{@code @Configuration} bean definition resources need to be
 * imported, use the {@link ImportResource @ImportResource} annotation instead.
 *
 * @author Chris Beams
 * @author Juergen Hoeller
 * @since 3.0
 * @see Configuration
 * @see ImportSelector
 * @see ImportBeanDefinitionRegistrar
 * @see ImportResource
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

	/**
	 * {@link Configuration @Configuration}, {@link ImportSelector},
	 * {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
	 */
	Class<?>[] value();

}

从声明可以看出,使用时,只需要指定 Class 实例即可;从方法的文档中可以看出,Class 实例可以分为三种:ImportSelectorImportBeanDefinitionRegistrar 和常规组件类。示例如下:

1
2
3
4
@Configuration
@Import(LogImportSelector.class)
public static class Config {
}

org.springframework.context.annotation.ConfigurationClassParser#processImports 方法中,集中了对 @Import 注解的处理。从代码可以非常清晰地看出,分了三种情况进行处理:

  1. ImportSelector

  2. ImportBeanDefinitionRegistrar

  3. 常规组件 Class

下面分别对其进行介绍。

3.8.1.1. ImportSelector

先来看一下 ImportSelector 接口的定义:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/*
 * Copyright 2002-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.context.annotation;

import java.util.function.Predicate;

import org.springframework.core.type.AnnotationMetadata;
import org.springframework.lang.Nullable;

/**
 * Interface to be implemented by types that determine which @{@link Configuration}
 * class(es) should be imported based on a given selection criteria, usually one or
 * more annotation attributes.
 *
 * <p>An {@link ImportSelector} may implement any of the following
 * {@link org.springframework.beans.factory.Aware Aware} interfaces,
 * and their respective methods will be called prior to {@link #selectImports}:
 * <ul>
 * <li>{@link org.springframework.context.EnvironmentAware EnvironmentAware}</li>
 * <li>{@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware}</li>
 * <li>{@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware}</li>
 * <li>{@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}</li>
 * </ul>
 *
 * <p>Alternatively, the class may provide a single constructor with one or more of
 * the following supported parameter types:
 * <ul>
 * <li>{@link org.springframework.core.env.Environment Environment}</li>
 * <li>{@link org.springframework.beans.factory.BeanFactory BeanFactory}</li>
 * <li>{@link java.lang.ClassLoader ClassLoader}</li>
 * <li>{@link org.springframework.core.io.ResourceLoader ResourceLoader}</li>
 * </ul>
 *
 * <p>{@code ImportSelector} implementations are usually processed in the same way
 * as regular {@code @Import} annotations, however, it is also possible to defer
 * selection of imports until all {@code @Configuration} classes have been processed
 * (see {@link DeferredImportSelector} for details).
 *
 * @author Chris Beams
 * @author Juergen Hoeller
 * @since 3.1
 * @see DeferredImportSelector
 * @see Import
 * @see ImportBeanDefinitionRegistrar
 * @see Configuration
 */
public interface ImportSelector {

	/**
	 * Select and return the names of which class(es) should be imported based on
	 * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
	 * @return the class names, or an empty array if none
	 */
	String[] selectImports(AnnotationMetadata importingClassMetadata);

	/**
	 * Return a predicate for excluding classes from the import candidates, to be
	 * transitively applied to all classes found through this selector's imports.
	 * <p>If this predicate returns {@code true} for a given fully-qualified
	 * class name, said class will not be considered as an imported configuration
	 * class, bypassing class file loading as well as metadata introspection.
	 * @return the filter predicate for fully-qualified candidate class names
	 * of transitively imported configuration classes, or {@code null} if none
	 * @since 5.2.4
	 */
	@Nullable
	default Predicate<String> getExclusionFilter() {
		return null;
	}

}

从接口文档中就可以看出,使用 String[] selectImports(AnnotationMetadata importingClassMetadata) 方法,返回所需要引入的类全限定名即可。实例如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class LogImportSelector implements ImportSelector {
  @Override
  public String[] selectImports(AnnotationMetadata importingClassMetadata) {
    return new String[]{
        UserDao.class.getName(),
        UserService.class.getName(),
        ProtoService.class.getName()
    };
  }
}
3.8.1.2. ImportBeanDefinitionRegistrar

先来看一下 ImportBeanDefinitionRegistrar 接口的定义:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
/*
 * Copyright 2002-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.context.annotation;

import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.core.type.AnnotationMetadata;

/**
 * Interface to be implemented by types that register additional bean definitions when
 * processing @{@link Configuration} classes. Useful when operating at the bean definition
 * level (as opposed to {@code @Bean} method/instance level) is desired or necessary.
 *
 * <p>Along with {@code @Configuration} and {@link ImportSelector}, classes of this type
 * may be provided to the @{@link Import} annotation (or may also be returned from an
 * {@code ImportSelector}).
 *
 * <p>An {@link ImportBeanDefinitionRegistrar} may implement any of the following
 * {@link org.springframework.beans.factory.Aware Aware} interfaces, and their respective
 * methods will be called prior to {@link #registerBeanDefinitions}:
 * <ul>
 * <li>{@link org.springframework.context.EnvironmentAware EnvironmentAware}</li>
 * <li>{@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware}
 * <li>{@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware}
 * <li>{@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}
 * </ul>
 *
 * <p>Alternatively, the class may provide a single constructor with one or more of
 * the following supported parameter types:
 * <ul>
 * <li>{@link org.springframework.core.env.Environment Environment}</li>
 * <li>{@link org.springframework.beans.factory.BeanFactory BeanFactory}</li>
 * <li>{@link java.lang.ClassLoader ClassLoader}</li>
 * <li>{@link org.springframework.core.io.ResourceLoader ResourceLoader}</li>
 * </ul>
 *
 * <p>See implementations and associated unit tests for usage examples.
 *
 * @author Chris Beams
 * @author Juergen Hoeller
 * @since 3.1
 * @see Import
 * @see ImportSelector
 * @see Configuration
 */
public interface ImportBeanDefinitionRegistrar {

	/**
	 * Register bean definitions as necessary based on the given annotation metadata of
	 * the importing {@code @Configuration} class.
	 * <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
	 * registered here, due to lifecycle constraints related to {@code @Configuration}
	 * class processing.
	 * <p>The default implementation delegates to
	 * {@link #registerBeanDefinitions(AnnotationMetadata, BeanDefinitionRegistry)}.
	 * @param importingClassMetadata annotation metadata of the importing class
	 * @param registry current bean definition registry
	 * @param importBeanNameGenerator the bean name generator strategy for imported beans:
	 * {@link ConfigurationClassPostProcessor#IMPORT_BEAN_NAME_GENERATOR} by default, or a
	 * user-provided one if {@link ConfigurationClassPostProcessor#setBeanNameGenerator}
	 * has been set. In the latter case, the passed-in strategy will be the same used for
	 * component scanning in the containing application context (otherwise, the default
	 * component-scan naming strategy is {@link AnnotationBeanNameGenerator#INSTANCE}).
	 * @since 5.2
	 * @see ConfigurationClassPostProcessor#IMPORT_BEAN_NAME_GENERATOR
	 * @see ConfigurationClassPostProcessor#setBeanNameGenerator
	 */
	default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
			BeanNameGenerator importBeanNameGenerator) {

		registerBeanDefinitions(importingClassMetadata, registry);
	}

	/**
	 * Register bean definitions as necessary based on the given annotation metadata of
	 * the importing {@code @Configuration} class.
	 * <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
	 * registered here, due to lifecycle constraints related to {@code @Configuration}
	 * class processing.
	 * <p>The default implementation is empty.
	 * @param importingClassMetadata annotation metadata of the importing class
	 * @param registry current bean definition registry
	 */
	default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
	}

}

这里使用到了 BeanDefinitionRegistry 接口,来看一下这个接口的定义:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
/*
 * Copyright 2002-2023 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.beans.factory.support;

import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.core.AliasRegistry;

/**
 * Interface for registries that hold bean definitions, for example RootBeanDefinition
 * and ChildBeanDefinition instances. Typically implemented by BeanFactories that
 * internally work with the AbstractBeanDefinition hierarchy.
 *
 * <p>This is the only interface in Spring's bean factory packages that encapsulates
 * <i>registration</i> of bean definitions. The standard BeanFactory interfaces
 * only cover access to a <i>fully configured factory instance</i>.
 *
 * <p>Spring's bean definition readers expect to work on an implementation of this
 * interface. Known implementors within the Spring core are DefaultListableBeanFactory
 * and GenericApplicationContext.
 *
 * @author Juergen Hoeller
 * @since 26.11.2003
 * @see org.springframework.beans.factory.config.BeanDefinition
 * @see AbstractBeanDefinition
 * @see RootBeanDefinition
 * @see ChildBeanDefinition
 * @see DefaultListableBeanFactory
 * @see org.springframework.context.support.GenericApplicationContext
 * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
 * @see PropertiesBeanDefinitionReader
 */
public interface BeanDefinitionRegistry extends AliasRegistry {

	/**
	 * Register a new bean definition with this registry.
	 * Must support RootBeanDefinition and ChildBeanDefinition.
	 * @param beanName the name of the bean instance to register
	 * @param beanDefinition definition of the bean instance to register
	 * @throws BeanDefinitionStoreException if the BeanDefinition is invalid
	 * @throws BeanDefinitionOverrideException if there is already a BeanDefinition
	 * for the specified bean name and we are not allowed to override it
	 * @see GenericBeanDefinition
	 * @see RootBeanDefinition
	 * @see ChildBeanDefinition
	 */
	void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
			throws BeanDefinitionStoreException;

	/**
	 * Remove the BeanDefinition for the given name.
	 * @param beanName the name of the bean instance to register
	 * @throws NoSuchBeanDefinitionException if there is no such bean definition
	 */
	void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;

	/**
	 * Return the BeanDefinition for the given bean name.
	 * @param beanName name of the bean to find a definition for
	 * @return the BeanDefinition for the given name (never {@code null})
	 * @throws NoSuchBeanDefinitionException if there is no such bean definition
	 */
	BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;

	/**
	 * Check if this registry contains a bean definition with the given name.
	 * @param beanName the name of the bean to look for
	 * @return if this registry contains a bean definition with the given name
	 */
	boolean containsBeanDefinition(String beanName);

	/**
	 * Return the names of all beans defined in this registry.
	 * @return the names of all beans defined in this registry,
	 * or an empty array if none defined
	 */
	String[] getBeanDefinitionNames();

	/**
	 * Return the number of beans defined in the registry.
	 * @return the number of beans defined in the registry
	 */
	int getBeanDefinitionCount();

	/**
	 * Determine whether the bean definition for the given name is overridable,
	 * i.e. whether {@link #registerBeanDefinition} would successfully return
	 * against an existing definition of the same name.
	 * <p>The default implementation returns {@code true}.
	 * @param beanName the name to check
	 * @return whether the definition for the given bean name is overridable
	 * @since 6.1
	 */
	default boolean isBeanDefinitionOverridable(String beanName) {
		return true;
	}

	/**
	 * Determine whether the given bean name is already in use within this registry,
	 * i.e. whether there is a local bean or alias registered under this name.
	 * @param beanName the name to check
	 * @return whether the given bean name is already in use
	 */
	boolean isBeanNameInUse(String beanName);

}

很明显,可以通过 registerBeanDefinition(String beanName, BeanDefinition beanDefinition) 方法,向容器在中注入所需要的 BeanDefinition,而 BeanDefinition 是常见的 Bean 实例的基石。示例如下:

1
2
3
4
5
6
7
8
public class LogImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                    BeanDefinitionRegistry registry) {
    RootBeanDefinition definition = new RootBeanDefinition(UserService.class);
    registry.registerBeanDefinition(UserService.class.getName(), definition);
  }
}
3.8.1.3. 常规组件 Class

这是最简单的情况,直接举例:

1
2
3
4
@Configuration
@Import(UserService.class)
public static class Config {
}

3.8.2. BeanDefinitionRegistryPostProcessor

先来看一下 BeanDefinitionRegistryPostProcessor 的定义:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/*
 * Copyright 2002-2023 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.beans.factory.support;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;

/**
 * Extension to the standard {@link BeanFactoryPostProcessor} SPI, allowing for
 * the registration of further bean definitions <i>before</i> regular
 * BeanFactoryPostProcessor detection kicks in. In particular,
 * BeanDefinitionRegistryPostProcessor may register further bean definitions
 * which in turn define BeanFactoryPostProcessor instances.
 *
 * @author Juergen Hoeller
 * @since 3.0.1
 * @see org.springframework.context.annotation.ConfigurationClassPostProcessor
 */
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {

	/**
	 * Modify the application context's internal bean definition registry after its
	 * standard initialization. All regular bean definitions will have been loaded,
	 * but no beans will have been instantiated yet. This allows for adding further
	 * bean definitions before the next post-processing phase kicks in.
	 * @param registry the bean definition registry used by the application context
	 * @throws org.springframework.beans.BeansException in case of errors
	 */
	void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;

	/**
	 * Empty implementation of {@link BeanFactoryPostProcessor#postProcessBeanFactory}
	 * since custom {@code BeanDefinitionRegistryPostProcessor} implementations will
	 * typically only provide a {@link #postProcessBeanDefinitionRegistry} method.
	 * @since 6.1
	 */
	@Override
	default void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
	}

}

这个接口扩展了标准的 BeanFactoryPostProcessor 接口,允许在普通的 BeanFactoryPostProcessor 接口实现类执行之前注册更多的 BeanDefinition。特别地是,BeanDefinitionRegistryPostProcessor 可以注册 BeanFactoryPostProcessorBeanDefinition

postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) 方法可以修改在 BeanDefinitionRegistry 接口实现类中注册的任意 BeanDefinition,也可以增加和删除 BeanDefinition。原因是这个方法执行前,所有常规的 BeanDefinition 已经被加载到 BeanDefinitionRegistry 接口实现类中,但还没有bean被实例化。

实例如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public class LogBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    System.out.println(getAndIncrement()
        + "LogBeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry\n");
    RootBeanDefinition beanDefinition = new RootBeanDefinition(LogBeanFactoryPostProcessor.class);
    registry.registerBeanDefinition(beanDefinition.getBeanClassName(), beanDefinition);
  }

  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    System.out.println(getAndIncrement()
        + "LogBeanDefinitionRegistryPostProcessor.postProcessBeanFactory\n");
  }
}

BeanDefinitionRegistryPostProcessor 在 Spring 内部的使用,最重要的示例就是 ConfigurationClassPostProcessor,这个类负责解析 @Import@Configuration 等注解。感兴趣可以认真研究一下这个类的代码。

3.8.3. BeanFactoryPostProcessor

BeanFactory 生成后,如果想对 BeanFactory 进行一些处理,该怎么办呢?BeanFactoryPostProcessor 接口就是用来处理 BeanFactory 的。

先来看一下接口定义:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
/*
 * Copyright 2002-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.beans.factory.config;

import org.springframework.beans.BeansException;

/**
 * spring的扩展点之一:用于修改 BeanDefinition 定义信息。<p/>
 *
 * 实现该接口,可以在 Spring 的 bean 创建之前修改 bean 的定义属性。<p/>
 * Spring 允许 BeanFactoryPostProcessor 在容器实例化任何其它 bean 之前读取配置元数据,
 * 并可以根据需要进行修改,例如可以把 bean 的 scope 从 singleton 改为 prototype,也可以把 property 的值给修改掉。<p/>
 *
 * 可以同时配置多个 BeanFactoryPostProcessor,并通过设置 'order' 属性来控制各个 BeanFactoryPostProcessor 的执行次序。
 * BeanFactoryPostProcessor 是在 Spring 容器加载了 bean 的定义文件之后,在 bean 实例化之前执行的。
 *
 * Factory hook that allows for custom modification of an application context's
 * bean definitions, adapting the bean property values of the context's underlying
 * bean factory.
 *
 * <p>Useful for custom config files targeted at system administrators that
 * override bean properties configured in the application context. See
 * {@link PropertyResourceConfigurer} and its concrete implementations for
 * out-of-the-box solutions that address such configuration needs.
 *
 * <p>A {@code BeanFactoryPostProcessor} may interact with and modify bean
 * definitions, but never bean instances. Doing so may cause premature bean
 * instantiation, violating the container and causing unintended side effects.
 * If bean instance interaction is required, consider implementing
 * {@link BeanPostProcessor} instead.
 *
 * <h3>Registration</h3>
 * <p>An {@code ApplicationContext} auto-detects {@code BeanFactoryPostProcessor}
 * beans in its bean definitions and applies them before any other beans get created.
 * A {@code BeanFactoryPostProcessor} may also be registered programmatically
 * with a {@code ConfigurableApplicationContext}.
 *
 * <h3>Ordering</h3>
 * <p>{@code BeanFactoryPostProcessor} beans that are autodetected in an
 * {@code ApplicationContext} will be ordered according to
 * {@link org.springframework.core.PriorityOrdered} and
 * {@link org.springframework.core.Ordered} semantics. In contrast,
 * {@code BeanFactoryPostProcessor} beans that are registered programmatically
 * with a {@code ConfigurableApplicationContext} will be applied in the order of
 * registration; any ordering semantics expressed through implementing the
 * {@code PriorityOrdered} or {@code Ordered} interface will be ignored for
 * programmatically registered post-processors. Furthermore, the
 * {@link org.springframework.core.annotation.Order @Order} annotation is not
 * taken into account for {@code BeanFactoryPostProcessor} beans.
 *
 * @author Juergen Hoeller
 * @author Sam Brannen
 * @since 06.07.2003
 * @see BeanPostProcessor
 * @see PropertyResourceConfigurer
 */
@FunctionalInterface
public interface BeanFactoryPostProcessor {

	/**
	 * Modify the application context's internal bean factory after its standard
	 * initialization. All bean definitions will have been loaded, but no beans
	 * will have been instantiated yet. This allows for overriding or adding
	 * properties even to eager-initializing beans.
	 * @param beanFactory the bean factory used by the application context
	 * @throws org.springframework.beans.BeansException in case of errors
	 */
	void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;

}

若 IoC 容器内添加了实现了 BeanFactoryPostProcessor 接口的实现类 Bean,那么在该容器中实例化任何其他 Bean 之前可以回调该 Bean 中的 postPrcessorBeanFactory() 方法来对 Bean 的配置元数据进行更改,比如设置 init-method,或者将 ScopeSINGLETON 改为 PROTOTYPE。示例如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class LogBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    System.out.println(getAndIncrement()
        + "LogBeanFactoryPostProcessor.postProcessBeanFactory\n");
    System.out.println(Arrays.toString(beanFactory.getBeanDefinitionNames()).replaceAll(",", ",\n"));
    BeanDefinition definition = beanFactory.getBeanDefinition(UserService.class.getName());
    // 设置 init 方法
    definition.setInitMethodName("init");
  }
}

在代码 org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors 中,集中了对 BeanFactoryPostProcessor 的调用。该方法把处理过程,委托给了 org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory, java.util.List<BeanFactoryPostProcessor>) 方法来处理。根据代码可以整理出处理流程如下:

  1. 如果 beanFactory 是一个 BeanDefinitionRegistry 实例,则:

    1. 首先处理参数传过来的 List<BeanFactoryPostProcessor> beanFactoryPostProcessors 对象

      1. 如果 postProcessorBeanDefinitionRegistryPostProcessor 实现类,则直接调用 postProcessBeanDefinitionRegistry,然后加入到 List<BeanDefinitionRegistryPostProcessor> registryProcessors 列表中;

      2. 如果不是,则加入到 List<BeanFactoryPostProcessor> regularPostProcessors 列表中;

    2. BeanFactory 中通过 beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false) 方法获取 BeanDefinitionRegistryPostProcessor 名称列表。筛选出实现了 PriorityOrdered 接口的实例,然后排序再逐一调用 postProcessBeanDefinitionRegistry 方法。最后,加入到 List<BeanDefinitionRegistryPostProcessor> registryProcessors 列表中。

    3. BeanFactory 中通过 beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false) 方法获取 BeanDefinitionRegistryPostProcessor 名称列表。筛选出实现了 Ordered 接口的实例,然后排序再逐一调用 postProcessBeanDefinitionRegistry 方法。最后,加入到 List<BeanDefinitionRegistryPostProcessor> registryProcessors 列表中。(注意:上一步已经调用过的则不再重复调用。)

    4. BeanFactory 中通过 beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false) 方法获取 BeanDefinitionRegistryPostProcessor 名称列表。剔除掉前两步调用过的类,排序再逐一调用 postProcessBeanDefinitionRegistry 方法。最后,加入到 List<BeanDefinitionRegistryPostProcessor> registryProcessors 列表中。要强调的一点是:这里是通过一个循环来反复执行这一步,D瓜哥认为是在调用 postProcessBeanDefinitionRegistry 方法中,有会参数新注册的 BeanDefinitionRegistryPostProcessor,所以需要反复调用。大家如果有不同见解,也欢迎留言讨论。

    5. 调用 BeanDefinitionRegistryPostProcessor 对象的 postProcessBeanFactory 方法;

    6. 调用 BeanFactoryPostProcessor 对象的 postProcessBeanFactory 方法;

  2. 如果 beanFactory 不是 BeanDefinitionRegistry 实例,则直接调用 BeanFactoryPostProcessor 对象的 postProcessBeanFactory 方法;

  3. BeanFactory 中通过 beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false) 方法获取 BeanFactoryPostProcessor 名称列表。将其分为:

    1. 实现 PriorityOrdered 接口的实例

    2. 实现 Ordered 接口的实例

    3. 未排序的实例

      按照这个顺序,排除已经处理过的实例,再分类,然后排序再跟着这个顺序依次逐一调用 BeanFactoryPostProcessor 对象的 postProcessBeanFactory 方法;

  4. 最后,向 BeanFactory 注册 ApplicationListenerDetector 实例。

3.8.4. InstantiationAwareBeanPostProcessor

注意区分 InstantiationInitialization

  • Instantiation — 实例化,在实例化之前还没有生成对象。

  • Initialization — 初始化,对象已经生成,需要对其做进一步的处理,比如赋值等。

3.8.5. FactoryBean

在对象生成上,有时也许需要做些特殊处理。比如,创建对象过程比较繁琐,希望可以通过实现 FactoryBean 来封装初始化过程。

在 Spring 官方文档 Core Technologies: Customizing Instantiation Logic with a FactoryBean 也有进一步的说明。

目前,Spring 源码中,FactoryBean 的实现类就有五十多个,随便举几个栗子🌰:

  • org.springframework.http.converter.json.GsonFactoryBean

  • org.springframework.cache.jcache.JCacheManagerFactoryBean

  • org.springframework.aop.framework.ProxyFactoryBean

示例如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package com.diguage.truman.context;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.annotation.*;

import java.util.Arrays;

/**
 * FactoryBean 测试
 *
 * @author D瓜哥 · https://www.diguage.com
 * @since 2020-05-26 16:34
 */
public class FactoryBeanTest {
  @Test
  public void test() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.register(Config.class);
    context.refresh();

    UserService userService = context.getBean(UserService.class);
    System.out.println(userService.getById(119L));

    System.out.println("-↓----");
    System.out.println("&userServiceFactoryBean = " (1)
        + context.getBean("&userServiceFactoryBean"));
    System.out.println(" userServiceFactoryBean = " (2)
        + context.getBean("userServiceFactoryBean"));
    System.out.println("-↑----");

    UserServiceFactoryBean factoryBean = context.getBean(UserServiceFactoryBean.class);
    System.out.println(factoryBean);
    System.out.println(Arrays.toString(context.getBeanDefinitionNames())
        .replaceAll(",", ",\n"));
  }

  @Configuration
  public static class Config {
    @Bean
    public UserServiceFactoryBean userServiceFactoryBean() {
      return new UserServiceFactoryBean();
    }
  }


  public static class UserService {
    public String getById(Long id) {
      return "Name-" + id;
    }
  }

  public static class UserServiceFactoryBean implements FactoryBean<UserService> {
    @Override
    public UserService getObject() throws Exception {
      return new UserService();
    }

    @Override
    public Class<?> getObjectType() {
      return UserService.class;
    }

    @Override
    public boolean isSingleton() {
      return false;
    }
  }
}
1 通过 Bean 名称 &userServiceFactoryBean 获得的 Bean 是 UserServiceFactoryBean 对象;
2 通过 Bean 名称 userServiceFactoryBean 获得的 Bean 是 UserService 对象;

有一点需要强调一下:& 符号的使用需要注意。上面的代码和相应注释给出了说明。

3.8.6. ObjectFactory

D瓜哥个人认为 FactoryBeanObjectFactory 功能有些重叠,都是为了创建对象而设计的。

通过 ObjectFactory 的文档,Spring 给出了官方解释:

这个接口通常用于封装一个通用的工厂,它在每次调用时返回某个目标对象的新实例(原型)。

这个接口类似于 FactoryBean,但后者的实现通常是作为 BeanFactory 中的 SPI 实例来定义,而这个类的实现通常是作为 API 馈送给其他 Bean(通过注入)。因此,getObject()方法有不同的异常处理行为。

Spring 在解决循环依赖时和在创建 Bean 时,都使用到接口。它似乎可以脱离 Spring 单独使用。

3.8.7. ObjectProvider

ObjectProvider 继承了 ObjectFactory 接口,它是后者的一个变体,提供了更加丰富的操作 T getIfAvailable(),T getIfUnique() 等。在 Spring 5.1 以后,有继承了 Iterable<T> 接口,方法用于循环或者 forEach 方法。在 org.springframework.beans.factory.support.DefaultListableBeanFactory 中有使用示例。

3.8.8. BeanPostProcessor

BeanPostProcessor 是 Spring 中最最重要的扩展点。Spring 内部大量的功能 IoC 和 AOP 也都是通过 BeanPostProcessor 来实现的。先来看一下接口定义:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/*
 * Copyright 2002-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.beans.factory.config;

import org.springframework.beans.BeansException;
import org.springframework.lang.Nullable;

/**
 * BeanPostProcessor是Spring框架的提供的一个扩展类点(不止一个)
 * 通过实现BeanPostProcessor接口,程序员就可插手bean实例化的过程,从而减轻了beanFactory的负担
 * 值得说明的是这个接口可以设置多个,会形成一个列表,然后依次执行
 * (但是spring默认的怎么办?set)
 * 比如AOP就是在bean实例后期间将切面逻辑织入bean实例中的
 * AOP也正是通过BeanPostProcessor和IOC容器建立起了联系
 * (由spring提供的默认的PostPorcessor,spring提供了很多默认的PostProcessor,下面我会一一介绍这些实现类的功能)
 * 可以来演示一下 BeanPostProcessor 的使用方式(把动态代理和IOC、aop结合起来使用)
 * 在演示之前先来熟悉一下这个接口,其实这个接口本身特别简单,简单到你发指
 * 但是他的实现类特别复杂,同样复杂到发指!
 * 可以看看spring提供哪些默认的实现(前方高能)
 * 查看类的关系图可以知道spring提供了以下的默认实现,因为高能,故而我们只是解释几个常用的
 * 1、ApplicationContextAwareProcessor (acap)
 *    acap后置处理器的作用是,当应用程序定义的Bean实现ApplicationContextAware接口时注入ApplicationContext对象
 *    当然这是他的第一个作业,他还有其他作用,这里不一一列举了,可以参考源码
 *    我们可以针对ApplicationContextAwareProcessor写一个栗子
 *  2、InitDestroyAnnotationBeanPostProcessor
 *     用来处理自定义的初始化方法和销毁方法
 *    上次说过Spring中提供了3种自定义初始化和销毁方法分别是
 *    一、通过@Bean指定init-method和destroy-method属性
 *    二、Bean实现InitializingBean接口和实现DisposableBean
 *    三、@PostConstruct:@PreDestroy
 *    为什么spring通这三种方法都能完成对bean生命周期的回调呢?
 *    可以通过InitDestroyAnnotationBeanPostProcessor的源码来解释
 *  3、InstantiationAwareBeanPostProcessor
 *  4、CommonAnnotationBeanPostProcessor
 *  5、AutowiredAnnotationBeanPostProcessor
 *  6 、RequiredAnnotationBeanPostProcessor
 *  7、BeanValidationPostProcessor
 *  8、AbstractAutoProxyCreator
 *  ......
 *  后面会一一解释
 *
 * Factory hook that allows for custom modification of new bean instances &mdash;
 * for example, checking for marker interfaces or wrapping beans with proxies.
 *
 * <p>Typically, post-processors that populate beans via marker interfaces
 * or the like will implement {@link #postProcessBeforeInitialization},
 * while post-processors that wrap beans with proxies will normally
 * implement {@link #postProcessAfterInitialization}.
 *
 * <h3>Registration</h3>
 * <p>An {@code ApplicationContext} can autodetect {@code BeanPostProcessor} beans
 * in its bean definitions and apply those post-processors to any beans subsequently
 * created. A plain {@code BeanFactory} allows for programmatic registration of
 * post-processors, applying them to all beans created through the bean factory.
 *
 * <h3>Ordering</h3>
 * <p>{@code BeanPostProcessor} beans that are autodetected in an
 * {@code ApplicationContext} will be ordered according to
 * {@link org.springframework.core.PriorityOrdered} and
 * {@link org.springframework.core.Ordered} semantics. In contrast,
 * {@code BeanPostProcessor} beans that are registered programmatically with a
 * {@code BeanFactory} will be applied in the order of registration; any ordering
 * semantics expressed through implementing the
 * {@code PriorityOrdered} or {@code Ordered} interface will be ignored for
 * programmatically registered post-processors. Furthermore, the
 * {@link org.springframework.core.annotation.Order @Order} annotation is not
 * taken into account for {@code BeanPostProcessor} beans.
 *
 * @author Juergen Hoeller
 * @author Sam Brannen
 * @since 10.10.2003
 * @see InstantiationAwareBeanPostProcessor
 * @see DestructionAwareBeanPostProcessor
 * @see ConfigurableBeanFactory#addBeanPostProcessor
 * @see BeanFactoryPostProcessor
 */
public interface BeanPostProcessor {

	/**
	 * 在bean初始化之前执行
	 *
	 * Bean实例已经创建好了,只是没有执行各种初始化回调。
	 *
	 * 使用 beanFactory.addBeanPostProcessor(new LogBeanPostProcessor()); 配置,则能应用于所有的 Bean 创建,包括内置 Bean。
	 *
	 * 但是,如果使用 @Import(LogBeanPostProcessor.class) 配置,则只能应用于用户自定义的 Bean 的创建,不能应用于内置 Bean 的创建。
	 *
	 * 如果使用 @Bean 配置,则配置类自身也不会被应用。
	 *
	 * Apply this {@code BeanPostProcessor} to the given new bean instance <i>before</i> any bean
	 * initialization callbacks (like InitializingBean's {@code afterPropertiesSet}
	 * or a custom init-method). The bean will already be populated with property values.
	 * The returned bean instance may be a wrapper around the original.
	 * <p>The default implementation returns the given {@code bean} as-is.
	 * @param bean the new bean instance
	 * @param beanName the name of the bean
	 * @return the bean instance to use, either the original or a wrapped one;
	 * if {@code null}, no subsequent BeanPostProcessors will be invoked
	 * @throws org.springframework.beans.BeansException in case of errors
	 * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet
	 */
	@Nullable
	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		System.out.printf(".. %s#%s(%s, %s)%n%n",
				getClass().getSimpleName(),
				"postProcessBeforeInitialization",
				bean.getClass().getSimpleName(),
				beanName);

		return bean;
	}

	/**
	 * 初始化之后
	 *
	 * Apply this {@code BeanPostProcessor} to the given new bean instance <i>after</i> any bean
	 * initialization callbacks (like InitializingBean's {@code afterPropertiesSet}
	 * or a custom init-method). The bean will already be populated with property values.
	 * The returned bean instance may be a wrapper around the original.
	 * <p>In case of a FactoryBean, this callback will be invoked for both the FactoryBean
	 * instance and the objects created by the FactoryBean (as of Spring 2.0). The
	 * post-processor can decide whether to apply to either the FactoryBean or created
	 * objects or both through corresponding {@code bean instanceof FactoryBean} checks.
	 * <p>This callback will also be invoked after a short-circuiting triggered by a
	 * {@link InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation} method,
	 * in contrast to all other {@code BeanPostProcessor} callbacks.
	 * <p>The default implementation returns the given {@code bean} as-is.
	 * @param bean the new bean instance
	 * @param beanName the name of the bean
	 * @return the bean instance to use, either the original or a wrapped one;
	 * if {@code null}, no subsequent BeanPostProcessors will be invoked
	 * @throws org.springframework.beans.BeansException in case of errors
	 * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet
	 * @see org.springframework.beans.factory.FactoryBean
	 */
	@Nullable
	default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		System.out.printf(".. %s#%s(%s, %s)%n%n",
				getClass().getSimpleName(),
				"postProcessAfterInitialization",
				bean.getClass().getSimpleName(),
				beanName);

		return bean;
	}

}

具体到实际应用上,Spring 内置了大量的应用:

  1. ApplicationContextAwareProcessor — Aware 接口的处理。

  2. InitDestroyAnnotationBeanPostProcessor — init-methoddestroy-method 方法的调用。

  3. InstantiationAwareBeanPostProcessor

  4. CommonAnnotationBeanPostProcessor — 常用注解 @Resource@PostConstruct@PreDestroy 的解析。

  5. AutowiredAnnotationBeanPostProcessor — 常用注解 @Autowired@Value@Inject 的解析。

  6. BeanValidationPostProcessor — 字段校验。

  7. AbstractAutoProxyCreator — 生成代理。

少废话,直接上代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class LogBeanPostProcessor implements BeanPostProcessor {
  @Override
  public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    if (bean instanceof UserService) {
      System.out.println(getAndIncrement()
          + "LogBeanPostProcessor.postProcessBeforeInitialization");
      System.out.println(bean);
      System.out.println();
    }
    return bean;
  }

  @Override
  public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    if (bean instanceof UserService) {
      System.out.println(getAndIncrement()
          + "LogBeanPostProcessor.postProcessAfterInitialization");
      System.out.println(bean);
      System.out.println();
    }
    return bean;
  }
}

// 将其注册到 BeanFactory 上
beanFactory.addBeanPostProcessor(new LogBeanPostProcessor());

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean(String, Object, RootBeanDefinition) 方法中,通过 applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName)applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName) 来分别调用 postProcessBeforeInitializationpostProcessAfterInitialization 方法。

3.8.9. 各种 Aware

有时,自己开发的代码可能需要 ApplicationContext 或者 BeanFactory 等实例。则可以通过实现相应的 Aware 接口来获得对应的实例。目前有如下这些 Aware 接口:

  1. ApplicationContextAware

  2. ApplicationEventPublisherAware

  3. BeanClassLoaderAware

  4. BeanFactoryAware

  5. BeanNameAware

  6. BootstrapContextAware

  7. EmbeddedValueResolverAware

  8. EnvironmentAware

  9. ImportAware

  10. LoadTimeWeaverAware

  11. MessageSourceAware

  12. NotificationPublisherAware

  13. ResourceLoaderAware

  14. SchedulerContextAware

  15. ServletConfigAware

  16. ServletContextAware

在代码 org.springframework.context.support.ApplicationContextAwareProcessor#invokeAwareInterfaces 中,集中处理了 EnvironmentAwareEmbeddedValueResolverAwareResourceLoaderAwareApplicationEventPublisherAwareMessageSourceAwareApplicationContextAware 等六种 Aware 注入。值得一提的是,通过类的定义可以得知,ApplicationContextAwareProcessor 是一个 BeanPostProcessor 实现类,那么 BeanPostProcessor 的处理机制也通过适用于该类。

3.8.9.1. ApplicationContextAware

如果某个 Bean 实现了 ApplicationContextAware 接口,那么 Spring 将会将该 Bean 所在的上下文环境 ApplicationContext 传递给 setApplicationContext() 方法,在 Bean 类中新增一个 ApplicationContext 字段用来保存 ApplicationContext 的值,并实现 setApplicationContext() 方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Service
public static class UserService implements InitializingBean, ApplicationContextAware {
  @Resource
  UserDao userDao;

  ApplicationContext applicationContext;

  public UserService() {
    System.out.println(getAndIncrement()
        + "UserService()\n");
  }

  @Override
  public void afterPropertiesSet() throws Exception {
    System.out.println(getAndIncrement()
        + "UserService.afterPropertiesSet\n");
  }

  public void init() {
    System.out.println(getAndIncrement()
        + "UserService.init\n");
  }

  String getById(Long id) {
    return userDao.getById(id);
  }

  @Override
  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    System.out.println(getAndIncrement()
        + "UserService.setApplicationContext\n");
    this.applicationContext = applicationContext;
  }
}
3.8.9.2. BeanClassLoaderAware

如果某个 Bean 实现了 BeanClassLoaderAware 接口,那么 Spring 将会将创建 Bean 的 ClassLoader 传递给 setBeanClassLoader() 方法,在 Bean 类中新增了一个 classLoader 字段用来保存 ClassLoader 的值,并实现 setBeanClassLoader() 方法。

3.8.9.3. BeanFactoryAware

如果某个 Bean 实现了 BeanFactoryAware 接口,那么 Spring 将会将创建 Bean 的 BeanFactory 传递给 setBeanFactory() 方法,在 Bean 类中新增了一个 beanFactory 字段用来保存 BeanFactory 的值,并实现 setBeanFactory() 方法。

3.8.9.4. BeanNameAware

如果某个 Bean 实现了 BeanNameAware 接口,那么 Spring 将会将 Bean 实例的ID传递给 setBeanName() 方法,在 Bean 类中新增一个 beanName 字段,并实现 setBeanName() 方法。

3.8.9.5. ServletContextAware

这个接口只能在 Web 项目中使用。

如果某个 Bean 实现了 ServletContextAware 接口,那么 Spring 将会将 ServletContext 传递给 setServletContext() 方法,在 Bean 类中新增一个字段,并实现 setServletContext() 方法。

3.8.10. InitializingBeaninit-method

设置 init-method 方法和实现 InitializingBean 方法达到的效果是一样的。在代码 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#invokeInitMethods 中可以看到很详细的处理流程:

  1. 判断 Bean 是否是 InitializingBean 实例,如果是,则做类型转换,然后再调用其 afterPropertiesSet() 方法;

  2. 获取 AbstractBeanDefinition#initMethodName 属性,然后判断是否合法(①长度大于零,②和第一步条件不重复,③不是外部管理的初始化方法),如果合法,则调用该方法。

init-method 是通过反射执行的,而 afterPropertiesSet() 是直接执行的。所以 afterPropertiesSet() 的执行效率比 init-method 要高;不过 init-method 消除了 Bean 对 Spring 依赖。

其实,按照一种方式设置即可。如果两者同时存在,则按照上述顺序执行。示例见上面的 ApplicationContextAware 示例。

3.8.11. DestructionAwareBeanPostProcessor

能否在 Bean 销毁之前,对其做些操作呢?答案是可以的。

DestructionAwareBeanPostProcessor 就可以实现这个功能。先来看一下接口定义:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/*
 * Copyright 2002-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.beans.factory.config;

import org.springframework.beans.BeansException;

/**
 * Subinterface of {@link BeanPostProcessor} that adds a before-destruction callback.
 *
 * <p>The typical usage will be to invoke custom destruction callbacks on
 * specific bean types, matching corresponding initialization callbacks.
 *
 * @author Juergen Hoeller
 * @since 1.0.1
 */
public interface DestructionAwareBeanPostProcessor extends BeanPostProcessor {

	/**
	 * Apply this BeanPostProcessor to the given bean instance before its
	 * destruction, e.g. invoking custom destruction callbacks.
	 * <p>Like DisposableBean's {@code destroy} and a custom destroy method, this
	 * callback will only apply to beans which the container fully manages the
	 * lifecycle for. This is usually the case for singletons and scoped beans.
	 * @param bean the bean instance to be destroyed
	 * @param beanName the name of the bean
	 * @throws org.springframework.beans.BeansException in case of errors
	 * @see org.springframework.beans.factory.DisposableBean#destroy()
	 * @see org.springframework.beans.factory.support.AbstractBeanDefinition#setDestroyMethodName(String)
	 */
	void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException;

	/**
	 * Determine whether the given bean instance requires destruction by this
	 * post-processor.
	 * <p>The default implementation returns {@code true}. If a pre-5 implementation
	 * of {@code DestructionAwareBeanPostProcessor} does not provide a concrete
	 * implementation of this method, Spring silently assumes {@code true} as well.
	 * @param bean the bean instance to check
	 * @return {@code true} if {@link #postProcessBeforeDestruction} is supposed to
	 * be called for this bean instance eventually, or {@code false} if not needed
	 * @since 4.3
	 */
	default boolean requiresDestruction(Object bean) {
		return true;
	}

}

由于 DestructionAwareBeanPostProcessorBeanPostProcessor 子类,由此可见,可以像操作 BeanPostProcessor 一样来操作 DestructionAwareBeanPostProcessor 实现类。示例如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class LogDestructionAwareBeanPostProcessor implements DestructionAwareBeanPostProcessor {
  @Override
  public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
    System.out.println(getAndIncrement()
        + "LogDestructionAwareBeanPostProcessor.postProcessBeforeDestruction");
    System.out.println(bean.getClass().getName());
  }
}

// 将其注册到 BeanFactory 上
beanFactory.addBeanPostProcessor(new LogDestructionAwareBeanPostProcessor());

调用是在 org.springframework.beans.factory.support.DisposableBeanAdapter#destroy 方法中实现的。

当调用 beanFactory.destroyBean(bean) 来手动销毁 Bean 时,就会创建 DisposableBeanAdapter 实例,然后调用 destroy() 来触发这个回调。也是在这个方法中,当调用完回调后,就会触发下面的 DisposableBean 回调。

3.8.12. DisposableBeandestroy-method

想要触发生命周期函数的 destroy() 方法,必须要要手动调用 beanFactory.destroyBean(bean) 方法才行:

1
2
3
DggDisposableBean dggDisposableBean = applicationContext.getBean(DggDisposableBean.class);
ConfigurableListableBeanFactory beanFactory = ApplicationContext.getBeanFactory();
beanFactory.destroyBean(dggDisposableBean);

调用是在 org.springframework.beans.factory.support.DisposableBeanAdapter#destroy 方法中实现的。

InitializingBeaninit-method 类似,destroy-method 也是在 DisposableBean#destroy() 之后执行的。如果同时存在,只要两者不重复,则两个同时都会执行。

3.8.13. ApplicationListener

org.springframework.context.support.AbstractApplicationContext#finishRefresh 中,发布了 ContextRefreshedEvent 事件。

3.8.14. 整合实践

上面介绍那么多,现在找一些实际项目对整合过程做个分析。先来个简单的。

3.8.14.1. Hibernate 与 Spring 整合

在 Spring 官网中,给出了非常详细的介绍: Data Access: Hibernate

Hibernate 与 Spring 整合主要涉及下面几个类:

  1. LocalSessionFactoryBean — 声明 Hibernate 配置信息;或者注入数据库连接池对象。

  2. HibernateTransactionManager — 负责处理 Hibernate 的事务。

实例代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<beans>
  <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
    <property name="url" value="jdbc:hsqldb:hsql://localhost:9001"/>
    <property name="username" value="sa"/>
    <property name="password" value=""/>
  </bean>

  <bean id="mySessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
    <property name="dataSource" ref="myDataSource"/>
    <property name="mappingResources">
      <list>
        <value>product.hbm.xml</value>
      </list>
    </property>
    <property name="hibernateProperties">
      <value>
        hibernate.dialect=org.hibernate.dialect.HSQLDialect
      </value>
    </property>
  </bean>

  <bean id="transactionManager"
      class="org.springframework.orm.hibernate5.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"/>
  </bean>

  <tx:annotation-driven/>

  <bean id="myProductDao" class="product.ProductDaoImpl">
    <property name="sessionFactory" ref="mySessionFactory"/>
  </bean>

  <bean id="myProductService" class="product.SimpleProductService">
    <property name="productDao" ref="myProductDao"/>
  </bean>
</beans>

Spring 与 Hibernate 的整合过程还是比较简单的,就是把 Hibernate 的相关对象当做普通的 Bean 注册到 Spring 容器中即可。

另外,还有一种 HibernateTemplate 方式,和上面的方式类似,就不再赘述。

原计划还准备添加 Spring 与 MyBATIS 和 Apache Dubbo 整合分析。考虑到本篇内容已经非常长,仔细分析它们的整合过程又需要大篇幅内容,所以,另外单独开文章进行说明。

3.9. Spring 循环依赖

循环依赖在编程中是一个常见问题(当然,这并不是最佳实践)。并且,Spring 如何解决循环依赖这个问题在面试中也经常见。下面,D瓜哥就从源码的层面深入剖析一下这个问题。

3.9.1. 示例程序

先展示一下示例程序:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package com.diguage.truman.context;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.stereotype.Component;

/**
 * @author D瓜哥, https://www.diguage.com/
 * @since 2020-05-24 13:02
 */
public class CircularDependenceSingletonTest {
  public static final Log log = LogFactory.getLog(CircularDependenceSingletonTest.class);

  @Test
  public void test() {
    AnnotationConfigApplicationContext applicationContext
        = new AnnotationConfigApplicationContext();
    applicationContext.register(Config.class);
    applicationContext.refresh();

    log.info(applicationContext.getBean(A.class));
    log.info(applicationContext.getBean(B.class));
    log.info(applicationContext.getBean(C.class));

    log.info("-A--------");
    A a = applicationContext.getBean(A.class);
    log.info(a);
    log.info(a.b);
    log.info("-B--------");
    B b = applicationContext.getBean(B.class);
    log.info(b);
    log.info(b.c);
    log.info("-C--------");
    C c = applicationContext.getBean(C.class);
    log.info(c);
    log.info(c.a);
  }

  @Configuration
  @Import(AbcImportSelector.class)
  public static class Config {
  }

  public static class AbcImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
      return new String[]{
          A.class.getName(),
          B.class.getName(),
          C.class.getName()};
    }
  }


  @Component
  public static class A {
    @Autowired
    B b;
  }

  @Component
  public static class B {
    @Autowired
    C c;
  }

  @Component
  public static class C {
    @Autowired
    A a;
  }
}

上述示例代码中的循环依赖情况如下:

循环依赖
Figure 7. 循环依赖

3.9.2. 源码剖析

3.9.2.1. 三级缓存

D瓜哥在 深入剖析 Spring 核心数据结构:BeanFactory 中,概要性地对 BeanFactory 的属性做了一一说明。 而其中的“三级缓存”属性,则是解决循环依赖问题的关键所在:

  1. Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256):Bean 名称到单例 Bean 的映射,用于存放完全初始化好的 Bean。可以理解成,这就是所谓的容器。这是一级缓存。

  2. Map<String, Object> earlySingletonObjects = new HashMap<>(16):Bean 到“未成熟”单例 Bean 的映射。该 Bean 对象只是被创建出来,但是还没有注入依赖。在容器解决循环依赖时,用于存储中间状态。这是二级缓存。

  3. Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16):Bean 名称到 Bean 的 ObjectFactory 对象的映射,存放 Bean 工厂对象。在容器解决循环依赖时,用于存储中间状态。这是三级缓存。

Bean 的获取过程就类似计算机缓存的作用过程:先从一级获取,失败再从二级、三级里面获取。在 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean) 方法中,可以明确看到整个过程:

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(beanName, allowEarlyReference)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**
 * 解决循环依赖:引入三级缓存。<p/>
 *
 * Return the (raw) singleton object registered under the given name.
 * <p>Checks already instantiated singletons and also allows for an early
 * reference to a currently created singleton (resolving a circular reference).
 * @param beanName the name of the bean to look for
 * @param allowEarlyReference whether early references should be created or not
 * @return the registered singleton object, or {@code null} if none found
 */
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	// Quick check for existing instance without full singleton lock
	// 从单例对象缓存中(一级缓存)获取 beanName 对应的实例对象
	Object singletonObject = this.singletonObjects.get(beanName);
	// 如果单例缓存中(一级缓存)没有,并且该 beanName 对应的单例对象正在创建中
	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
		// 从早期单例对象缓存中(二级缓存)获取单例对象
		// 【之所以称之为早期单例对象,是因为 earlySingletonObjects 里的对象都是通过提前
		// 曝光的 ObjectFactory 创建出来的,还未进行属性填充等初始化操作】
		singletonObject = this.earlySingletonObjects.get(beanName);
		// 如果早期单例对象缓存中(二级缓存)也没有,并且允许创建早期单例对象引用
		if (singletonObject == null && allowEarlyReference) {
			// 如果为空,则对全局变量进行加锁后进行处理
			synchronized (this.singletonObjects) {
				// Consistent creation of early reference within full singleton lock
				singletonObject = this.singletonObjects.get(beanName);
				if (singletonObject == null) {
					singletonObject = this.earlySingletonObjects.get(beanName);
					if (singletonObject == null) {
						// 当某些方法需要提前初始化的时候则会调用 addSingletonFactory 方法将
						// 对应的 ObjectFactory 初始化策略存储到 singletonFactories 中(三级缓存)
						// TODO dgg 何时放入三级缓存 singletonFactories 中的?
						ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
						if (singletonFactory != null) {
							// 如果存在单例对象工厂,则通过工厂创建一个单例对象
							singletonObject = singletonFactory.getObject();
							// 记录在缓存中(二级缓存),二级缓存和三级缓存不能同时存在
							this.earlySingletonObjects.put(beanName, singletonObject);
							// 从三级缓存中移除
							this.singletonFactories.remove(beanName);
						}
					}
				}
			}
		}
	}
	return singletonObject;
}
3.9.2.2. Bean 创建过程

D瓜哥在 Spring Bean 生命周期概述 中专门讨论过 Bean 的生命周期函数。Bean 的实例创建和依赖注入是分开来处理的。具体到 Spring 的内部函数调用,有可以描述成如下:

Bean创建
Figure 8. Bean创建

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean 方法中,能明确看到三个方法的调用过程。在这个方法上打上断点,开始调试。

3.9.2.3. 实例创建
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
/**
 * 真正创建 Bean 的方法。<p/>
 *
 * Actually create the specified bean. Pre-creation processing has already happened
 * at this point, e.g. checking {@code postProcessBeforeInstantiation} callbacks.
 * <p>Differentiates between default bean instantiation, use of a
 * factory method, and autowiring a constructor.
 * @param beanName the name of the bean
 * @param mbd the merged bean definition for the bean
 * @param args explicit arguments to use for constructor or factory method invocation
 * @return a new instance of the bean
 * @throws BeanCreationException if the bean could not be created
 * @see #instantiateBean
 * @see #instantiateUsingFactoryMethod
 * @see #autowireConstructor
 */
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
		throws BeanCreationException {

	// Instantiate the bean.
	// BeanWrapper 是用来持有创建出来的 Bean 对象的
	BeanWrapper instanceWrapper = null;
	// 获取FactoryBean实例缓存
	if (mbd.isSingleton()) {
		// 如果是单例对象,从 FactoryBean 实例缓存中移除当前 Bean 定义信息
		instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
	}
	// 没有BeanWrapper 就创建实例
	if (instanceWrapper == null) {
		// doCreateBean -1
		// 这个方法里面完成了对象创建,仅仅是对象
		// 执行完整个方法,可以看看控制台的变化
		/**
		 * 创建 bean 实例,并将实例包裹在 BeanWrapper 实现类对象中返回。
		 * createBeanInstance中包含三种创建 bean 实例的方式:
		 *   1. 通过工厂方法创建 bean 实例
		 *   2. 通过构造方法自动注入(autowire by constructor)的方式创建 bean 实例
		 *   3. 通过无参构造方法方法创建 bean 实例
		 *
		 * 若 bean 的配置信息中配置了 lookup-method 和 replace-method,则会使用 CGLIB
		 * 增强 bean 实例。关于lookup-method和replace-method后面再说。
		 */
		instanceWrapper = createBeanInstance(beanName, mbd, args);
	}
	// 从 BeanWrapper 中获取原始实例 bean
	Object bean = instanceWrapper.getWrappedInstance();
	// 从 BeanWrapper 中获取原始实例 bean 对象的 Class 属性
	Class<?> beanType = instanceWrapper.getWrappedClass();
	// 如果不为 NullBean 类型,那么就修改目标类型
	if (beanType != NullBean.class) {
		mbd.resolvedTargetType = beanType;
	}

	// Allow post-processors to modify the merged bean definition.
	// 调用 PostProcessor 后置处理器
	// 允许 BeanPostProcessor 去修改合并后的 BeanDefinition
	synchronized (mbd.postProcessingLock) {
		if (!mbd.postProcessed) {
			try {
				// MergedBeanDefinitionPostProcessors 后置处理器修改合并 bean 的定义
				applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
			}
			catch (Throwable ex) {
				throw new BeanCreationException(mbd.getResourceDescription(), beanName,
						"Post-processing of merged bean definition failed", ex);
			}
			mbd.markAsPostProcessed();
		}
	}

	// Eagerly cache singletons to be able to resolve circular references
	// even when triggered by lifecycle interfaces like BeanFactoryAware.
	// 向容器中缓存单例模式的 Bean 对象,以防循环引用
	// 判断当前 bean 是否需要提前曝光:单例 && 允许循环依赖 && 当前 bean 正在创建中,检查循环依赖
	boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
			isSingletonCurrentlyInCreation(beanName));
	if (earlySingletonExposure) {
		if (logger.isTraceEnabled()) {
			logger.trace("Eagerly caching bean '" + beanName +
					"' to allow for resolving potential circular references");
		}
		// bean 变量就是上面创建好的实例,通过这个方法将实例包裹在
		// ObjectFactory<?> singletonFactory 匿名类的实例中,
		// 然后将其放入到 singletonFactories 变量中
		// 这里是一个匿名内部类,为了防止循环引用,尽早持有对象的引用
		// 为避免后期循环依赖,可以在 bean 初始化前完成将创建实例的 ObjectFactory 放入工厂
		addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
	}

	// Initialize the bean instance.
	// Bean 对象的初始化,依赖注入再次触发
	// 这个 exposedObject 在初始化完成知火候返回作为依赖注入完成后的 Bean
	Object exposedObject = bean;
	try {
		// 设置属性,对 Bean 属性进行依赖注入,非常重要 FIXME dgg 属性注入,需要深入研究
		// 将 Bean 实例化对象封装,并且 Bean 定义中配置的属性值赋值给实例对象
		// 填充属性(对 bean 的属性进行填充,将属性值注入其中,当存在循环依赖时,则会递归初始化依赖的 Bean)
		populateBean(beanName, mbd, instanceWrapper);
		// 在对 Bean 实例对象生成或依赖注入完成以后,开始对 Bean 实例对象进行初始化,
		// 为 Bean 实例对象应用 BeanPOSTProcessor 后置处理器
		// 初始化 Bean 对象,执行后置处理器,aop 就是在这里完成的处理
		exposedObject = initializeBean(beanName, exposedObject, mbd);
	}
	catch (Throwable ex) {
		if (ex instanceof BeanCreationException bce && beanName.equals(bce.getBeanName())) {
			throw bce;
		}
		else {
			throw new BeanCreationException(mbd.getResourceDescription(), beanName, ex.getMessage(), ex);
		}
	}

	if (earlySingletonExposure) {
		// 从缓存中获取具体的实例化对象
		// 获取指定名称的已注册的单例模式 Bean 对象
		Object earlySingletonReference = getSingleton(beanName, false);
		// earlySingletonReference 只有检测到有循环依赖的情况下才会不为空
		if (earlySingletonReference != null) {
			// 根据名称获取的已注册的 Bean 和正在实例化的 Bean 是同一个
			if (exposedObject == bean) {
				// 当前实例化的 Bean 初始化完成
				exposedObject = earlySingletonReference;
			}
			// 当前 Bean 依赖其他 Bean,并且当发送循环依赖时不允许新创建实例对象
			else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
				String[] dependentBeans = getDependentBeans(beanName);
				Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
				// 获取当前 Bean 所依赖的其他 Bean
				for (String dependentBean : dependentBeans) {
					if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
						actualDependentBeans.add(dependentBean);
					}
				}
				if (!actualDependentBeans.isEmpty()) {
					throw new BeanCurrentlyInCreationException(beanName,
							"Bean with name '" + beanName + "' has been injected into other beans [" +
							StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
							"] in its raw version as part of a circular reference, but has eventually been " +
							"wrapped. This means that said other beans do not use the final version of the " +
							"bean. This is often the result of over-eager type matching - consider using " +
							"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
				}
			}
		}
	}

	// Register bean as disposable.
	// 注册完成依赖注入的 Bean
	try {
		// 注册 Bean 对象,方便后续再容器销毁的时候销毁对象
		registerDisposableBeanIfNecessary(beanName, bean, mbd);
	}
	catch (BeanDefinitionValidationException ex) {
		throw new BeanCreationException(
				mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
	}
	// 返回所需的实例对象
	return exposedObject;
}

关于 createBeanInstance 方法,已经在上面的注释中增加了响应说明,这里就不再贴代码了。

3.9.2.4. 依赖注入

接着上面的代码,往下看,看如何完成注入的:

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
/**
 * 真正创建 Bean 的方法。<p/>
 *
 * Actually create the specified bean. Pre-creation processing has already happened
 * at this point, e.g. checking {@code postProcessBeforeInstantiation} callbacks.
 * <p>Differentiates between default bean instantiation, use of a
 * factory method, and autowiring a constructor.
 * @param beanName the name of the bean
 * @param mbd the merged bean definition for the bean
 * @param args explicit arguments to use for constructor or factory method invocation
 * @return a new instance of the bean
 * @throws BeanCreationException if the bean could not be created
 * @see #instantiateBean
 * @see #instantiateUsingFactoryMethod
 * @see #autowireConstructor
 */
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
		throws BeanCreationException {

	// Instantiate the bean.
	// BeanWrapper 是用来持有创建出来的 Bean 对象的
	BeanWrapper instanceWrapper = null;
	// 获取FactoryBean实例缓存
	if (mbd.isSingleton()) {
		// 如果是单例对象,从 FactoryBean 实例缓存中移除当前 Bean 定义信息
		instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
	}
	// 没有BeanWrapper 就创建实例
	if (instanceWrapper == null) {
		// doCreateBean -1
		// 这个方法里面完成了对象创建,仅仅是对象
		// 执行完整个方法,可以看看控制台的变化
		/**
		 * 创建 bean 实例,并将实例包裹在 BeanWrapper 实现类对象中返回。
		 * createBeanInstance中包含三种创建 bean 实例的方式:
		 *   1. 通过工厂方法创建 bean 实例
		 *   2. 通过构造方法自动注入(autowire by constructor)的方式创建 bean 实例
		 *   3. 通过无参构造方法方法创建 bean 实例
		 *
		 * 若 bean 的配置信息中配置了 lookup-method 和 replace-method,则会使用 CGLIB
		 * 增强 bean 实例。关于lookup-method和replace-method后面再说。
		 */
		instanceWrapper = createBeanInstance(beanName, mbd, args);
	}
	// 从 BeanWrapper 中获取原始实例 bean
	Object bean = instanceWrapper.getWrappedInstance();
	// 从 BeanWrapper 中获取原始实例 bean 对象的 Class 属性
	Class<?> beanType = instanceWrapper.getWrappedClass();
	// 如果不为 NullBean 类型,那么就修改目标类型
	if (beanType != NullBean.class) {
		mbd.resolvedTargetType = beanType;
	}

	// Allow post-processors to modify the merged bean definition.
	// 调用 PostProcessor 后置处理器
	// 允许 BeanPostProcessor 去修改合并后的 BeanDefinition
	synchronized (mbd.postProcessingLock) {
		if (!mbd.postProcessed) {
			try {
				// MergedBeanDefinitionPostProcessors 后置处理器修改合并 bean 的定义
				applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
			}
			catch (Throwable ex) {
				throw new BeanCreationException(mbd.getResourceDescription(), beanName,
						"Post-processing of merged bean definition failed", ex);
			}
			mbd.markAsPostProcessed();
		}
	}

	// Eagerly cache singletons to be able to resolve circular references
	// even when triggered by lifecycle interfaces like BeanFactoryAware.
	// 向容器中缓存单例模式的 Bean 对象,以防循环引用
	// 判断当前 bean 是否需要提前曝光:单例 && 允许循环依赖 && 当前 bean 正在创建中,检查循环依赖
	boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
			isSingletonCurrentlyInCreation(beanName));
	if (earlySingletonExposure) {
		if (logger.isTraceEnabled()) {
			logger.trace("Eagerly caching bean '" + beanName +
					"' to allow for resolving potential circular references");
		}
		// bean 变量就是上面创建好的实例,通过这个方法将实例包裹在
		// ObjectFactory<?> singletonFactory 匿名类的实例中,
		// 然后将其放入到 singletonFactories 变量中
		// 这里是一个匿名内部类,为了防止循环引用,尽早持有对象的引用
		// 为避免后期循环依赖,可以在 bean 初始化前完成将创建实例的 ObjectFactory 放入工厂
		addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
	}

	// Initialize the bean instance.
	// Bean 对象的初始化,依赖注入再次触发
	// 这个 exposedObject 在初始化完成知火候返回作为依赖注入完成后的 Bean
	Object exposedObject = bean;
	try {
		// 设置属性,对 Bean 属性进行依赖注入,非常重要 FIXME dgg 属性注入,需要深入研究
		// 将 Bean 实例化对象封装,并且 Bean 定义中配置的属性值赋值给实例对象
		// 填充属性(对 bean 的属性进行填充,将属性值注入其中,当存在循环依赖时,则会递归初始化依赖的 Bean)
		populateBean(beanName, mbd, instanceWrapper);
		// 在对 Bean 实例对象生成或依赖注入完成以后,开始对 Bean 实例对象进行初始化,
		// 为 Bean 实例对象应用 BeanPOSTProcessor 后置处理器
		// 初始化 Bean 对象,执行后置处理器,aop 就是在这里完成的处理
		exposedObject = initializeBean(beanName, exposedObject, mbd);
	}
	catch (Throwable ex) {
		if (ex instanceof BeanCreationException bce && beanName.equals(bce.getBeanName())) {
			throw bce;
		}
		else {
			throw new BeanCreationException(mbd.getResourceDescription(), beanName, ex.getMessage(), ex);
		}
	}

	if (earlySingletonExposure) {
		// 从缓存中获取具体的实例化对象
		// 获取指定名称的已注册的单例模式 Bean 对象
		Object earlySingletonReference = getSingleton(beanName, false);
		// earlySingletonReference 只有检测到有循环依赖的情况下才会不为空
		if (earlySingletonReference != null) {
			// 根据名称获取的已注册的 Bean 和正在实例化的 Bean 是同一个
			if (exposedObject == bean) {
				// 当前实例化的 Bean 初始化完成
				exposedObject = earlySingletonReference;
			}
			// 当前 Bean 依赖其他 Bean,并且当发送循环依赖时不允许新创建实例对象
			else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
				String[] dependentBeans = getDependentBeans(beanName);
				Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
				// 获取当前 Bean 所依赖的其他 Bean
				for (String dependentBean : dependentBeans) {
					if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
						actualDependentBeans.add(dependentBean);
					}
				}
				if (!actualDependentBeans.isEmpty()) {
					throw new BeanCurrentlyInCreationException(beanName,
							"Bean with name '" + beanName + "' has been injected into other beans [" +
							StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
							"] in its raw version as part of a circular reference, but has eventually been " +
							"wrapped. This means that said other beans do not use the final version of the " +
							"bean. This is often the result of over-eager type matching - consider using " +
							"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
				}
			}
		}
	}

	// Register bean as disposable.
	// 注册完成依赖注入的 Bean
	try {
		// 注册 Bean 对象,方便后续再容器销毁的时候销毁对象
		registerDisposableBeanIfNecessary(beanName, bean, mbd);
	}
	catch (BeanDefinitionValidationException ex) {
		throw new BeanCreationException(
				mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
	}
	// 返回所需的实例对象
	return exposedObject;
}
addSingletonFactory

先来看看 addSingletonFactory 方法:

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingletonFactory
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
 * 如果需要,添加给定的单例对象工厂来构建指定的单例对象。<p/>
 *
 * Add the given singleton factory for building the specified singleton
 * if necessary.
 * <p>To be called for eager registration of singletons, e.g. to be able to
 * resolve circular references.
 * @param beanName the name of the bean
 * @param singletonFactory the factory for the singleton object
 */
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
	Assert.notNull(singletonFactory, "Singleton factory must not be null");
	// 对 singletonObjects(一级缓存)加锁,保证线程安全
	synchronized (this.singletonObjects) {
		// 如果单例对象的高速缓存 Map 中【beanName=bean实例】没有 beanName 的对象
		if (!this.singletonObjects.containsKey(beanName)) {
			// 将 beanName=singletonFactory 放入到单例工厂的缓存中去【beanName=ObjectFactory】(三级缓存)
			this.singletonFactories.put(beanName, singletonFactory);
			// 从早期的单例对象的高速缓存【beanName=bean实例】移除 beanName 相关的缓存对象(二级缓存)
			this.earlySingletonObjects.remove(beanName);
			// 将 beanName 添加到已注册的单例缓存中
			this.registeredSingletons.add(beanName);
		}
	}
}

从这里可以明显看出,代码符合我们上面注释中的描述: singletonFactory 变量被放入到 singletonFactories 变量中了。

populateBean

再来看看 populateBean

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
 * 实现 Bean 属性依赖注入的功能。<p/>
 *
 * 对属性的注入过程分以下两种情况:
 * 1)、属性值类型不需要强制转换时,不需要解析属性值,直接准备进行依赖注入。
 * 2)、属性值需要进行类型强制转换时,如对其他对象的引用等,首先需要解析属性值,然后对解析后的属性值进行依赖注入。
 *
 * Populate the bean instance in the given BeanWrapper with the property values
 * from the bean definition.
 * @param beanName the name of the bean
 * @param mbd the bean definition for the bean
 * @param bw the BeanWrapper with bean instance
 */
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
	// 如果 BeanWrapper 为空
	if (bw == null) {
		// 如果 RootBeanDefinition 有需要设置的属性
		if (mbd.hasPropertyValues()) {
			throw new BeanCreationException(
					mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance");
		}
		else {
			// Skip property population phase for null instance.
			// 没有可填充的属性,直接跳过
			return;
		}
	}

Spring Bean 生命周期概述 中对 Bean 的生命周期做了概要的介绍。这里就体现出来 CommonAnnotationBeanPostProcessorAutowiredAnnotationBeanPostProcessor 的作用。上面我们用的是 @Autowired 注解。所以,这里使用 AutowiredAnnotationBeanPostProcessor 来处理。

依赖注入的调用链

查找依赖的调用链很繁琐,中间有牵涉到 Bean 创建的过程,这里只列出调用过程中的主要方法列表,需要请根据自己需要来单步调试。

完成依赖注入的调用链如下:

  1. org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean

  2. org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessProperties

  3. org.springframework.beans.factory.annotation.InjectionMetadata#inject

  4. org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject

  5. org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveDependency

  6. org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency

  7. org.springframework.beans.factory.config.DependencyDescriptor#resolveCandidate

  8. org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String)

  9. org.springframework.beans.factory.annotation.InjectionMetadata#inject — 最后,还是在这里完成注入。

3.9.2.5. 加入容器

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(String, ObjectFactory<?>) 方法中,可以看到 Spring 在获得 Bean 实例后的处理过程:

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(String, ObjectFactory<?>)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
/**
 * Return the (raw) singleton object registered under the given name,
 * creating and registering a new one if none registered yet.
 * @param beanName the name of the bean
 * @param singletonFactory the ObjectFactory to lazily create the singleton
 * with, if necessary
 * @return the registered singleton object
 */
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
	Assert.notNull(beanName, "Bean name must not be null");
	// 使用单例对象的高速缓存 Map(一级缓存)作为锁,保证线程同步
	synchronized (this.singletonObjects) {
		// 从单例对象的高速缓存 Map 中(一级缓存)获取 beanName 对应的单例对象
		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null) {
			if (this.singletonsCurrentlyInDestruction) {
				throw new BeanCreationNotAllowedException(beanName,
						"Singleton bean creation not allowed while singletons of this factory are in destruction " +
						"(Do not request a bean from a BeanFactory in a destroy method implementation!)");
			}
			if (logger.isDebugEnabled()) {
				logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
			}
			// 创建单例之前的回调,默认实现将单例注册为当前正在创建中
			beforeSingletonCreation(beanName);
			// 表示生成了新的单例对象的标识,默认为false,表示没有生成新的单例对象
			boolean newSingleton = false;
			// 异常日志记录标识,没有时为true,否则为false
			boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
			// 如果没有异常记录
			if (recordSuppressedExceptions) {
				// 对异常记录表进行初始化
				this.suppressedExceptions = new LinkedHashSet<>();
			}
			try {
				// 从单列工厂获取对象
				singletonObject = singletonFactory.getObject();
				// 生成新的单例对象标识设为true,表示生成了新的单例对象
				newSingleton = true;
			}
			catch (IllegalStateException ex) {
				// Has the singleton object implicitly appeared in the meantime ->
				// if yes, proceed with it since the exception indicates that state.
				// 同时,单例对象是否隐式出现 -> 如果是,请继续操作,因为异常标明该状态
				// 尝试从单例对象的告诉缓存 Map 中获取 beanName 的单例对象
				singletonObject = this.singletonObjects.get(beanName);
				if (singletonObject == null) {
					throw ex;
				}
			}
			catch (BeanCreationException ex) {
				if (recordSuppressedExceptions) {
					// 循环将异常对象添加到 Bean 创建异常中,这样做相当于'因xxx异常导致了 Bean 创建异常'的说法
					for (Exception suppressedException : this.suppressedExceptions) {
						ex.addRelatedCause(suppressedException);
					}
				}
				throw ex;
			}
			finally {
				if (recordSuppressedExceptions) {
					// 将异常日志置为 null,因为 suppressedExceptions 是对应单个 Bean 的异常记录
					// 防止异常信息混乱
					this.suppressedExceptions = null;
				}
				// 创建单例后的回调,默认实现将单例标记为不在创建中
				afterSingletonCreation(beanName);
			}
			// newSingleton=true代表了创建了新的单例对象
			if (newSingleton) {
				// 创建完 Bean 后,将其加入到容器中
				// 将 beanName 和 SingletonObject 的映射关系添加到该工厂的单例缓存中
				addSingleton(beanName, singletonObject);
			}
		}
		return singletonObject;
	}
}

加入容器的操作也很简单:

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingleton
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
/**
 * 将 beanName 和 singletonObject 的映射关系添加到该工厂的单例缓存中<p/>
 *
 * Add the given singleton object to the singleton cache of this factory.
 * <p>To be called for eager registration of singletons.
 * @param beanName the name of the bean
 * @param singletonObject the singleton object
 */
protected void addSingleton(String beanName, Object singletonObject) {
	synchronized (this.singletonObjects) {
		// 将映射对象添加到单例对象的高速缓存中去(一级缓存)
		this.singletonObjects.put(beanName, singletonObject);
		// 移除beanName在单例工厂缓存中的数据(三级缓存)
		this.singletonFactories.remove(beanName);
		// 移除beanName在早期单例对象的高速缓存的数据(二级缓存)
		this.earlySingletonObjects.remove(beanName);
		// 将beanName注册到已注册的单例集合中
		this.registeredSingletons.add(beanName);
	}
}

3.9.3. 小结

这里,假设 A → BB → A 两层循环依赖来说明问题

  1. 通过 applicationContext.getBean(A.class) 方法,委托给 AbstractBeanFactory#doGetBean 方法来尝试获取 Bean;获取不到则开始创建;

    1. Bean 是调用 instanceWrapper = createBeanInstance(beanName, mbd, args); 方法创建出来了实例,然后又通过 addSingletonFactory(beanName, () → getEarlyBeanReference(beanName, mbd, bean)); 将已经创建的实例封装到一个 ObjectFactory<?> singletonFactory 匿名类中,放入到三级缓存中。

    2. populateBean(beanName, mbd, instanceWrapper); 方法,通过 CommonAnnotationBeanPostProcessorAutowiredAnnotationBeanPostProcessorpostProcessProperties 查找依赖,完成注入。

      1. 查找依赖时,就会通过调用 getBean(beanName) 获取 Bean B。此时,还没有 Bean B,则会从这里的第二步开始执行,创建实例,封装后加入到三级缓存 singletonFactories 中,调用 populateBean(beanName, mbd, instanceWrapper); 方法,通过 CommonAnnotationBeanPostProcessorAutowiredAnnotationBeanPostProcessorpostProcessProperties 查找依赖,完成注入。依赖注入的过程,请看 依赖注入的调用链 小节。

      2. 到这里,就要查找 Bean A 了,一二三级缓存依次来查找(DefaultSingletonBeanRegistry#getSingleton(beanName, allowEarlyReference)),在三级缓存中,找到了对应的 ObjectFactory<?> singletonFactory 实例,然后调用 getObject() 方法,获得 A 的实例,将其加入到二级缓存中,将三级中的相关内容清理掉。从这里也可以看出,通过 AbstractBeanFactory#doGetBean 方法获得的 Bean 不一定是完全初始化好的 Bean,有可能是一个未完成初始化的实例对象。

      3. 获得 A 的实例后,就可以完成 Bean B 的初始化,调用 DefaultSingletonBeanRegistry#addSingleton 方法,将其加入一级缓存 singletonObjects 中,也就是容器中。(由于 Bean B 可以直接完成依赖注入,则它不会从三级缓存跳到二级缓存。最后的三级缓存在调用 addSingleton 方法时,直接被清理掉了。)

    3. 到这里就可以获取 Bean B 了,然后完成 A 的依赖注入。

  2. 最后,通过调用 DefaultSingletonBeanRegistry#addSingleton 方法,将 Bean A 加入到一级缓存 singletonObjects 中,也就是容器中。所有的初始化工作就完成了。

需要注意的是,有两种情况,Spring 是没办法完成循环注入的:

  1. 构造函数注入 — 这种要求在实例之前创建好依赖的实例,但是明显无法完成,所以不能解决循环依赖。

  2. PROTOTYPE 类型的 Bean 相互依赖 — 刚刚看到,上面的三级缓存变量都是为 SINGLETON 类型的 Bean 准备的。PROTOTYPE 类型的 Bean 在检查到循环依赖时,就直接抛异常了。

3.10. I18n

properties 文件内容是以 ISO-8859-1 编码的。所以,不支持中文,需要把中文进行转码。

Diagram

3.11. 事件发布

Diagram

容器启动伊始,就会检查容器内是否存在名称为 applicationEventMulticasterApplicationEventMulticaster 对象实例。有的话就使用提供的实现,没有则默认初始化一个 SimpleApplicationEventMulticaster 作为将会使用的 ApplicationEventMulticaster

refresh() 时,先调用 initMessageSource() 初始化 MessageSource 实例;然后调用 initApplicationEventMulticaster() 初始化 ApplicationEventMulticaster

Spring 是以 Bean 为核心的。Bean 的配置、配置的读取和解析、以合适的数据结构对 Bean 元数据进行各种操作等。

Spring 是如何解决构造函数依赖的呢?以及如何注入呢?

3.12. 内置 PostProcessor 的注册

AnnotationConfigUtils.registerAnnotationConfigProcessors(BeanDefinitionRegistry) 方法中,注册了 Spring 内置的一些核心 PostProcessor

  1. ConfigurationClassPostProcessor

  2. AutowiredAnnotationBeanPostProcessor

  3. CommonAnnotationBeanPostProcessor

  4. PersistenceAnnotationBeanPostProcessor? — 这个得看是否需要

  5. EventListenerMethodProcessor

  6. DefaultEventListenerFactory

通过对 AnnotationConfigUtils.registerAnnotationConfigProcessors 调用追踪来看,在如下地方进行了调用:

  1. 通过 AnnotationConfigBeanDefinitionParser.parse 在处理 <context:annotation-config/> 时;

  2. 通过 ComponentScanBeanDefinitionParser.parse 在处理 <context:component-scan/> 时;

  3. 通过 AnnotatedBeanDefinitionReader 构造函数在初始化时;

  4. 通过 ClassPathBeanDefinitionScanner.scan 在扫描类路径时;

覆盖了 XML 配置文件和注解配置两种最核心的场景。

Diagram
Diagram
Diagram

3.13. 常用接口简介

在单步调试 Spring 源代码过程中,D瓜哥发现,总有那么几个接口(含一些实现类)到处都想露个脸。第一个遇到时,也许会一头雾水,不知期所以然,跟着一步步跳下去,也许到最后就不知所踪了。D瓜哥觉得,有必要先介绍一些常用的接口,让大家有个影响,调试代码时,最起码不至于因为突然出现的类而茫然失措。

这节,D瓜哥就先介绍一些常用的接口。

注意:这里只是简单介绍,后续代码分析中用到某些类,D瓜哥还会着重分析介绍的。
Advised

切面

AopProxyFactory

基类

Advisor
AopProxy
AliasRegistry

管理别名的通用接口。同时,该类也是 BeanDefinitionRegistry 接口的父接口。

ApplicationContextAware

ApplicationContext 运行时,实现该接口的类可以容器通知到。直白点说,就是可以把 ApplicationContext 对象设置到实现这个接口的类的对象中。

ApplicationContext

这个大家最熟悉,常用它及其子类。该接口是为应用配置提供的集中接口。

ApplicationEventMulticaster

应用事件监听器管理接口,并且可以将相关事件广播给所管理的监听器。

ApplicationEventPublisher

包装时间发布功能的接口。

ApplicationEvent

用于被所有应用事件扩展的抽象类。

ApplicationListener

应用事件监听器接口。实现该接口,用于监听应用的某种事件。该接口继承了 EventListener

AttributeAccessor

用于从任意对象添加或者获取属性元数据的接口。

AutowireCandidateResolver

自动注入候选解析器。

Aware

标识接口,没有任何方法。实现其相关子接口的,可以被容器的相关框架对象以回调方式的方法来通知。以后遇到以该接口名结尾的接口,都可以认为它里面含有一个 Setter 方法。

BeanDefinitionDecorator

BeanDefinition 对象装饰器接口。

BeanDefinitionDocumentReader

XML 文档读取器,负责读取 XML 文档,具体解析则是交给 BeanDefinitionParserDelegate 来完成的。

BeanDefinitionParserDelegate

解析 XML 的原元素,将其转化为 BeanDefinition

BeanDefinitionParser

自定义或者顶级 XML 元素(例如 <beans/>)处理器,负责将 XML 标签转化为 BeanDefinition 对象。

BeanDefinitionReader

Bean 定义读取器,负责从 Resource 或字符串中读取 Bean 定义。

BeanDefinitionRegistry

BeanDefinition 注册表,存放从 XML 解析出来的 BeanDefinition

BeanDefinition

Spring 中表示 Bean 定义的基本数据结构,存放属性值、构造函数参数值等等。

  1. ROLE_INFRASTRUCTURE 这些常量是什么鬼?

BeanFactoryAware

提供一个 setBeanFactory(BeanFactory beanFactory) 方法,可以让实现该接口的类的对象能被设置自身所属的 BeanFactory

BeanFactoryPostProcessor

BeanFactory 后置处理器。可以修改应用上下文中的 Bean。

BeanFactory

Spring Bean 容器的最基础接口,提供容器最基本的操作。

BeanPostProcessor
BeanWrapper
ConversionService
ConverterRegistry
DisposableBean
Environment
EventListener

事件监听器标识接口。没有任何方法,只是用于表示其子类是事件监听器。

FactoryBean
InstantiationStrategy

负责创建与 RootBeanDefinition 定义相对应实例的接口。提取出来,方便改变不同的创建方式。

LifecycleProcessor
Lifecycle
MessageSourceAware
MessageSource
MutablePropertyValues
NamespaceHandler
ParserContext
PropertyAccessor
PropertyEditorRegistrar
PropertyEditorRegistry
PropertyEditor
PropertyResolver
PropertySources
PropertyValue
PropertyValues
ResolvableType
ResourceLoaderAware
ResourceLoader
Resource
TypeConverter

:leveloffset: 1

3.13.1. 资源加载 与 Resource

ResourceLoader 也是一种策略模式,加载资源的策略。

Diagram

classpath*:classpath: 的唯一区别就在于,如果能够在 classpath 中找到多个指定的资源,则返回多个。

ApplicationContext 继承了 ResourcePatternResolver,当 然就间接实现了 ResourceLoader 接口。所以,任何的 ApplicationContext 实现都可以看作是一个 ResourceLoader 甚至 ResourcePatternResolver。而这就是 ApplicationContext 支持 Spring 内统一资源加载策略的真相。

Diagram

AbstractApplicationContext 继承了 DefaultResourceLoader,那么,它的 getResource(String) 当然就直接用 DefaultResourceLoader 的了。

AbstractApplicationContext 类的内 部声明有一个 resourcePatternResolver,类型是 ResourcePatternResolver,对应的实例类型为 PathMatchingResourcePatternResolver

ApplicationContext 的实现类在作为 ResourceLoader 或者 ResourcePatternResolver 时候的行为,完全就是委派给了 PathMatchingResourcePatternResolverDefaultResourceLoader 来做。

Resource 类图

Diagram

3.13.2. 标签解析

<bean> 的子标签 <property> 会根据属性不同,被解析成不同的对象,例如 TypedStringValueRuntimeBeanReference,然后再被封装成 PropertyValue 对象,最后被存放在 GenericBeanDefinition 对象的 MutablePropertyValues propertyValues 属性(在 AbstractBeanDefinition 中声明)中。在首次获取对象时,根据这里的信息再逐步转化成不同对象。

这里还有一点需求说明:

  • GenericBeanDefinition 对象对应 XML 等原始配置文件中对 Bean 的定义和配置。

  • RootBeanDefinition 则代表生成实例时, Bean 对应的定义和配置。是根据 GenericBeanDefinition 的配置来生成的。如果 GenericBeanDefinition 定义的是子 Bean 的话,则会同时合并父类的相关属性。这个合并怎么实现的?

  • BeanWrapperImpl 对象是对创建后的实例对象的包装,被存储在

  • 对象之间的依赖关系存放在 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#dependentBeanMapMap<String, Set<String>>,bean name -→ Set of dependent bean names) 中。 而 AbstractBeanFactory 通过继承 FactoryBeanRegistrySupport, 而 FactoryBeanRegistrySupport 继承了 DefaultSingletonBeanRegistry,进而获得了对这个关系的访问。

  • org.springframework.beans.factory.support.DefaultSingletonBeanRegistrydependentBeanMapdependenciesForBeanMap 是什么关系?

    • 例如 A ref B, A ref C,就是 类 A 中有属性 B 和 C。

    • dependenciesForBeanMap 存到就是 A → (B, C);

    • dependentBeanMap 中存的是 B → A 和 C → A。这个还要进一步确认。

疑问:

  • org.springframework.beans.factory.support.ConstructorResolver#autowireConstructorthis.beanFactory.initBeanWrapper(bw),为什么把对 BeanWrapper 对初始化封装在 BeanFactory 而不是 BeanWrapper 内部?

3.13.3. 注解解析

说明一下常用的注解的处理过程:

  1. @Component

  2. @Service

  3. @Repository

  4. @Controller

  5. @Autowired

  6. @Resource

  7. @Value

  8. @Qualifier

  9. @Required

考虑把 @Controller 放在 Spring MVC 再做说明。

可以使用 p-namespace 来简化属性的赋值操作:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xmlns:p="http://www.springframework.org/schema/p"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans
                https://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean name="classic" class="com.example.ExampleBean">
		<property name="email" value="someone@somewhere.com"/>
	</bean>

	<bean name="p-namespace" class="com.example.ExampleBean"
		  p:email="someone@somewhere.com"/>
</beans>

可以使用 c-namespace 来简化构造函数参数的声明:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xmlns:c="http://www.springframework.org/schema/c"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans
                https://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="beanTwo" class="x.y.ThingTwo"/>
	<bean id="beanThree" class="x.y.ThingThree"/>

	<!-- traditional declaration with optional argument names -->
	<bean id="beanOne" class="x.y.ThingOne">
		<constructor-arg name="thingTwo" ref="beanTwo"/>
		<constructor-arg name="thingThree" ref="beanThree"/>
		<constructor-arg name="email" value="something@somewhere.com"/>
	</bean>

	<!-- c-namespace declaration with argument names -->
	<bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
		  c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>

</beans>

除了常规的 @Autowired@Resource 注入外,还可以使用 @Lookup 注解来注入依赖。示例如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package com.diguage.truman.context;

import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Lookup;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;

/**
 * @author D瓜哥, https://www.diguage.com/
 * @since 2021-08-08 22:26:20
 */
@Slf4j
public class AnnoLookupTest {

	@Test
	public void test() {
		AnnotationConfigApplicationContext context
				= new AnnotationConfigApplicationContext();
		context.register(Config.class);
		context.refresh();
		UserService service = context.getBean(UserService.class);
		service.get();
	}

	@Configuration
	@Import(LookupImportSelector.class)
	public static class Config {
	}

	public static class LookupImportSelector implements ImportSelector {
		@Override
		public String[] selectImports(AnnotationMetadata metadata) {
			return new String[]{
					UserDao.class.getName(),
					UserService.class.getName()
			};
		}
	}

	@Repository
	public static class UserDao {
		public String getUser() {
			log.info("execute UserDao.getUser");
			return "D瓜哥";
		}
	}

	@Component
	public static abstract class UserService {
		public void get() {
			UserDao userDao = getUserDao();
			log.info("invoke userDao...");
			log.info("result: {}", userDao.getUser());
		}

		/**
		 * TODO 这里使用抽象方法,让人费解。
		 */
		@Lookup
		public abstract UserDao getUserDao();
	}
}

这里有个疑问:为什么要把 @Lookup 注解标注在抽象方法上?

3.13.4. BeanNameGenerator 自定义 Bean 名称

如果想自定义 Bean 名称,可以实现 BeanNameGenerator 接口,然后将其配置到 Spring 容器上。更详细文档,请看: Core Technologies:Naming Autodetected Components

3.13.5. 占位符解析

PropertyPlaceholderHelper.parseStringValue
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
/**
 * 主要逻辑在此:
 * 1、首先 占位符 是支持嵌套的,譬如 ${a${b}},因此截取出 占位符 中间的目标字符串后,是需要递归解析的
 * 2、其次,此处是不支持解析 循环占位符 的,比如当属性源为 a -> ${a} 时,显然是无法解析的,
 *    此处通过一个 Set<String> visitedPlaceholders 保证该逻辑
 * 3、占位符解析完成后,就是从 属性源 中解析对应属性了,对于解析为 null 的属性是支持指定默认值的(前提是你指定了默认值分割符)
 * 4、如果仍然解析失败,根据属性 ignoreUnresolvablePlaceholders 决定是否抛出异常
 * 5、如果解析成功了,对于解析的结果,如果仍然包含 占位符,则再次进行解析
 * 6、最后更新 startIndex,便于解析后面的 占位符,如果都解析完了,就是 -1,然后把解析完的 占位符 移出 visitedPlaceholders
 * 7、最后,分析下用于获取 占位符 后缀下标的方法 findPlaceholderEndIndex
 */
protected String parseStringValue(
		String value, PlaceholderResolver placeholderResolver, @Nullable Set<String> visitedPlaceholders) {
	// 如果不包含指定前缀,那就原样返回
	int startIndex = value.indexOf(this.placeholderPrefix);
	if (startIndex == -1) {
		return value;
	}

	StringBuilder result = new StringBuilder(value);
	while (startIndex != -1) {
		// 先找到对应后缀的下标
		int endIndex = findPlaceholderEndIndex(result, startIndex);
		if (endIndex != -1) {
			// 截取前后缀中间的目标字符串
			String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
			String originalPlaceholder = placeholder;
			if (visitedPlaceholders == null) {
				visitedPlaceholders = new HashSet<>(4);
			}
			// 先把解析目标字符串保存起来,避免循环解析
			if (!visitedPlaceholders.add(originalPlaceholder)) {
				throw new IllegalArgumentException(
						"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
			}
			// 开始递归解析目标字符串,因为目标字符串可能也包含占位符,比如 ${a${b}}
			// Recursive invocation, parsing placeholders contained in the placeholder key.
			placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
			// Now obtain the value for the fully resolved key...
			// 解析占位符在这里完成
			String propVal = placeholderResolver.resolvePlaceholder(placeholder);
			// 如果解析结果是 null,那就看是有指定默认值分割符,
			// 如果有且原始值包含该分割符,则先获取分割符前的 key,获取无果返回指定默认值
			if (propVal == null && this.valueSeparator != null) {
				int separatorIndex = placeholder.indexOf(this.valueSeparator);
				if (separatorIndex != -1) {
					String actualPlaceholder = placeholder.substring(0, separatorIndex);
					String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
					propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
					if (propVal == null) {
						propVal = defaultValue;
					}
				}
			}
			// 如果获取成功,则再解析一次
			// 这意味着如果最终解析出来的属性中仍然包含占位符,是可以继续解析的
			if (propVal != null) {
				// Recursive invocation, parsing placeholders contained in the
				// previously resolved placeholder value.
				propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
				// 解析完后整体替换
				result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
				if (logger.isTraceEnabled()) {
					logger.trace("Resolved placeholder '" + placeholder + "'");
				}
				// 然后更新 startIndex,
				// 如果后面还有占位符,就更新到下一个占位符前缀下标;
				// 如果没有,就返回 -1,打破循环
				startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
			}
			else if (this.ignoreUnresolvablePlaceholders) {
				// 到这里就是解析无果了,根据属性 ignoreUnresolvablePlaceholders
				// 决定是否抛出异常 IllegalArgumentException
				// Proceed with unprocessed value.
				startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
			}
			else {
				throw new IllegalArgumentException("Could not resolve placeholder '" +
						placeholder + "'" + " in value \"" + value + "\"");
			}
			// 解析完后从缓存中移除
			visitedPlaceholders.remove(originalPlaceholder);
		}
		else {
			startIndex = -1;
		}
	}
	return result.toString();
}
PropertyPlaceholderHelper.parseStringValue
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
 * 获取 占位符 后缀下标
 */
private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
	// 赋值 index
	int index = startIndex + this.placeholderPrefix.length();
	int withinNestedPlaceholder = 0;
	// 从 index 处开始解析
	while (index < buf.length()) {
		/**
		 * 先匹配后缀,如果匹配到,先看下是不是嵌套的后缀,
		 * 如果是嵌套后缀,嵌套层级 -1,重新计算 index;
		 * 否则就是匹配到了,直接返回
		 */
		if (StringUtils.substringMatch(buf, index, this.placeholderSuffix)) {
			if (withinNestedPlaceholder > 0) {
				withinNestedPlaceholder--;
				index = index + this.placeholderSuffix.length();
			}
			else {
				return index;
			}
		}
		/**
		 * 如果没匹配到,就看下是否匹配到 simplePrefix,
		 * 如果匹配到了,说明有嵌套 占位符;
		 * 嵌套层级 +1,重新计算 index
		 */
		else if (StringUtils.substringMatch(buf, index, this.simplePrefix)) {
			withinNestedPlaceholder++;
			index = index + this.simplePrefix.length();
		}
		// 如果都没有,index + 1 即可
		else {
			index++;
		}
	}
	return -1;
}
Diagram
Diagram
Diagram
Diagram
  1. 可以解析到其他的 PropertySourcesPlaceholderConfigurer 吗?或者可以配置多个 PropertySourcesPlaceholderConfigurer 吗?

4. AOP

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/*
 * Copyright 2002-2023 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.aop;

import org.aopalliance.aop.Advice;

/**
 * Base interface holding AOP <b>advice</b> (action to take at a joinpoint)
 * and a filter determining the applicability of the advice (such as
 * a pointcut). <i>This interface is not for use by Spring users, but to
 * allow for commonality in support for different types of advice.</i>
 *
 * <p>Spring AOP is based around <b>around advice</b> delivered via method
 * <b>interception</b>, compliant with the AOP Alliance interception API.
 * The Advisor interface allows support for different types of advice,
 * such as <b>before</b> and <b>after</b> advice, which need not be
 * implemented using interception.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 */
public interface Advisor {

	/**
	 * Common placeholder for an empty {@code Advice} to be returned from
	 * {@link #getAdvice()} if no proper advice has been configured (yet).
	 * @since 5.0
	 */
	Advice EMPTY_ADVICE = new Advice() {};


	/**
	 * Return the advice part of this aspect. An advice may be an
	 * interceptor, a before advice, a throws advice, etc.
	 * @return the advice that should apply if the pointcut matches
	 * @see org.aopalliance.intercept.MethodInterceptor
	 * @see BeforeAdvice
	 * @see ThrowsAdvice
	 * @see AfterReturningAdvice
	 */
	Advice getAdvice();

	/**
	 * Return whether this advice is associated with a particular instance
	 * (for example, creating a mixin) or shared with all instances of
	 * the advised class obtained from the same Spring bean factory.
	 * <p><b>Note that this method is not currently used by the framework.</b>
	 * Typical Advisor implementations always return {@code true}.
	 * Use singleton/prototype bean definitions or appropriate programmatic
	 * proxy creation to ensure that Advisors have the correct lifecycle model.
	 * <p>As of 6.0.10, the default implementation returns {@code true}.
	 * @return whether this advice is associated with a particular target instance
	 */
	default boolean isPerInstance() {
		return true;
	}

}

4.1. AOP 处理流程概述

AOP 是 Spring 框架的最核心的两个功能之一,在前面的 Spring 启动流程概述Spring Bean 生命周期概述 两篇文章中,分别介绍了 Spring 启动过程和 Spring Bean 的生命周期,对 IoC 有了一个细致介绍。这里来细致分析一下 Spring AOP 的实现原理和处理流程。

4.1.1. 基本概念

先来了解几个基本概念,D瓜哥私以为这些概念是 AOP 中最核心的内容,了解了基本概念,可以说基本上掌握了一半的 AOP 内容。

学习概念最权威的地方,当然就是官方文档。所以,这些概念可以在 Spring Framework Documentation: AOP Concepts 中看到最权威的介绍。

  1. Join point(连接点): 所谓的连接点是指那些被拦截到的点。在 Spring 中,连接点指的是方法,因为 Spring 只支持方法类型的连接点。在 Spring 中,使用

  2. Pointcut(切入点): 所谓的切入点,是指要对哪些 Join point(连接点) 进行拦截的定义。如果 Join point(连接点) 是全集,那么 Pointcut(切入点) 就是被选中的子集。写 AOP 代码的时候,一般是用 Pointcut(切入点) 表达式进行对 Join point(连接点) 进行选择。

  3. Advice(通知/增强): 所谓的通知就是指拦截到 Join point(连接点) 之后所要做的事情。通知根据作用位置不同,又细分为:

    1. Before advice(前置通知): 在 Join point(连接点) 之前运行的通知。这种通知,不能阻止执行流程继续到 Join point(连接点)。

    2. After returning advice(后置通知): 在 Join point(连接点) 之后运行的通知。当然,如果在 Join point(连接点) 执行过程中,抛出异常,则可能就不执行了。

    3. After throwing advice(异常通知): 方法抛出异常后,将会执行的通知。

    4. After (finally) advice(最终通知): 无论如何都会执行的通知,即使抛出异常。

    5. Around advice(环绕通知): 围绕在 Join point(连接点) 的通知,方法执行前和执行后,都可以执行自定义行为。同时,也可以决定是返回 Join point(连接点) 的返回值,还是返回自定义的返回值。

  4. Aspect(切面): 是切入点和通知的结合。

  5. Advisor(通知器/顾问): 和 Aspect(切面) 很相似。

  6. Introduction(引介): 引介是一种特殊的通知在不修改类代码的前提下,Introduction 可以在运行期为类动态地添加一些方法或属性。

  7. Target object(目标对象): 代理的目标对象。

  8. AOP proxy(代理): 一个类被AOP织入增强后,就产生一个结果代理类。

  9. Weaving(织入): 是指把增强应用到目标对象来创建新的代理对象的过程。Spring 是通过实现后置处理器 BeanPostProcessor 接口来实现织入的。也就是在 Bean 完成初始化之后,通过给目标对象生成代理对象,并交由 Spring IoC 容器来接管,这样再去容器中获取到的目标对象就是已经增强过的代理对象。

一图胜千言,通过下面的图来总结一下,希望能够帮助读者记忆。

AOP 基本概念
Figure 9. AOP 基本概念

4.1.2. 实现原理

Spring AOP 的实现原理说起来只有一句话:如果使用接口,则用 JDK 的动态代理实现;如果没有实现接口,则使用 CGLIB 通过字节码技术来实现。如图:

AOP 实现原理
Figure 10. AOP 实现原理

JDK 的动态代理和 CGLIB 字节码技术在实现上也略有不同。如图:

AOP 实现原理:JDK vs CGLIB
Figure 11. AOP 实现原理:JDK vs CGLIB

关于动态代理,D瓜哥有篇文章还在酝酿,稍后发布出来,再详细介绍。

4.1.3. AOP 织入流程

Spring Bean 生命周期概述 中介绍的 Spring Bean 声明周期流程,再结合上面提到的实现原理,如果让你设计一个 AOP 功能,你会怎么设计?

大家想一想,AOP 的三要素是什么?无非就是:① Target object(目标对象),解决增强的作用对象问题;② Pointcut(切入点),解决在哪里增强的问题;③ Advice(通知/增强),解决怎么争强的问题。到这里,思路应该比较清晰了:

  • 第一步:创建实例对象

  • 第二步:提取切面信息,包括 Pointcut(切入点)、Advice(通知/增强)。

  • 第三步:判断实例对象是否符合 Pointcut(切入点) 的选择条件;如果符合,执行下一步;否则直接跳过。

  • 第四步:创建代理,在 Target object(目标对象) 的 Pointcut(切入点) 上,Weaving(织入) Advice(通知/增强) 操作。

很多人以为这就完事了,以后方法调用直接执行增强、执行原始方法就完事了。其实,并不是这样的。如下图:

Aspect 应用流程
Figure 12. Aspect 应用流程

经过前面四步处理后,Spring 把 Target object(目标对象) 和 Advice(通知/增强) 编织在一起后,再调用代理对象的方法,代理对象就会把调用再次处理,匹配的通知和方法按顺序执行;不匹配的方法,则不会执行通知,而是只执行方法本身。

洋洋洒洒又写了好长篇幅,关于这部分的源码分析另外单独开一篇文章详细介绍吧。

4.2. 入门

在上一篇文章 Spring AOP 处理流程概述 中,对 Spring AOP 有了一个整体认识。这篇文章就带大家做一个细致的源码分析。

4.2.1. 登堂入室

使用 Spring AOP 也很简单,只需要在配置类上加上 @EnableAspectJAutoProxy 注解即可。这个注解处理过程与 Spring 扩展点实践:整合 MyBATIS 中 “@MapperScan 处理” 类似,不同的是,Spring AOP 注册了 AnnotationAwareAspectJAutoProxyCreator,它是一个 InstantiationAwareBeanPostProcessor。具体的类图如下:

Diagram

在正式开始源码分析之前,有一点必须强调一下:Spring AOP 只是借用了 AspectJ 的一些注解和个别关键 API,而整体实现是 Spring 自己完成的,并不是基于 AspectJ 实现的。这一点跟很多人的认识是不一样的,需要特别指出。

D瓜哥在 Spring Bean 生命周期概述 中指出:创建 AOP 代理对象,有两个时机:

  1. 调用 InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation 时,通过调用 AnnotationAwareAspectJAutoProxyCreator 对象的 postProcessBeforeInstantiation 方法来创建对象;

  2. 调用 BeanPostProcessor#postProcessAfterInitialization 时,通过调用 AnnotationAwareAspectJAutoProxyCreator 对象的 postProcessAfterInitialization 方法来创建对象;

下面分别对这两个方法做更详细的介绍。

4.2.2. AnnotationAwareAspectJAutoProxyCreator#postProcessBeforeInstantiation

AnnotationAwareAspectJAutoProxyCreatorpostProcessBeforeInstantiation 方法是从 AbstractAutoProxyCreator 继承过来的。代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
	System.out.printf(".. %s#%s(%s, %s)%n%n",
			getClass().getSimpleName(),
			"postProcessBeforeInstantiation",
			beanClass.getSimpleName(),
			beanName);

	//1、得到一个缓存的唯一key(根据beanClass和beanName生成唯一key)
	Object cacheKey = getCacheKey(beanClass, beanName);

	//2、如果当前targetSourcedBeans(通过自定义TargetSourceCreator创建的TargetSource)不包含cacheKey
	if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
		//2.1、advisedBeans(已经被增强的Bean,即AOP代理对象)中包含当前cacheKey,返回null,即走Spring默认流程
		if (this.advisedBeans.containsKey(cacheKey)) {
			return null;
		}
		//2.2、如果是基础设施类(如Advisor、Advice、AopInfrastructureBean的实现)不进行处理
		//2.2、shouldSkip 默认false,可以生成子类覆盖,如AspectJAwareAdvisorAutoProxyCreator覆盖(if (((AbstractAspectJAdvice) advisor.getAdvice()).getAspectName().equals(beanName)) return true;  即如果是自己就跳过)
		// 注意:这里把 @Aspect 标注的 Bean 提取出来,创建 advisor
		if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
			this.advisedBeans.put(cacheKey, Boolean.FALSE);
			return null;
		}
	}

	// Create proxy here if we have a custom TargetSource.
	// Suppresses unnecessary default instantiation of the target bean:
	// The TargetSource will handle target instances in a custom fashion.
	//3、开始创建AOP代理对象
	//3.1、配置自定义的TargetSourceCreator进行TargetSource创建
	TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
	//3.2、如果targetSource不为null 添加到targetSourcedBeans缓存,并创建AOP代理对象
	if (targetSource != null) {
		if (StringUtils.hasLength(beanName)) {
			this.targetSourcedBeans.add(beanName);
		}
		// specificInterceptors即增强(包括前置增强、后置增强等等)
		Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
		//3.3、创建代理对象
		Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
		//3.4、将代理类型放入proxyTypes从而允许后续的predictBeanType()调用获取
		this.proxyTypes.put(cacheKey, proxy.getClass());
		return proxy;
	}

	return null;
}

请注意代码中语法高亮的两行代码:

  1. getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource) 获取了所有符合条件的增强信息。

  2. createProxy(beanClass, beanName, specificInterceptors, targetSource) 创建了代理对象。

4.2.3. AnnotationAwareAspectJAutoProxyCreator#postProcessAfterInitialization

Spring Bean 生命周期概述 中已经强调过了:绝大部分的 AOP 代理生成都是在 postProcessAfterInitialization 方法中完成的。来看一下这个方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
 * 此处为真正创建AOP代理的地方。<p/>
 *
 * 首先查看该 Bean 是否存在 earlyProxyReferences 里,如果有,则说明已经被代理了,若没有,则表示还未被代理。<p/>
 *
 * Create a proxy with the configured interceptors if the bean is
 * identified as one to proxy by the subclass.
 * @see #getAdvicesAndAdvisorsForBean
 */
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
	System.out.printf(".. %s#%s(%s, %s)%n%n",
			getClass().getSimpleName(),
			"postProcessAfterInitialization",
			bean.getClass().getSimpleName(),
			beanName);

	if (bean != null) {
		// 根据给定的bean的class和name构建出个key,格式:beanClassName_beanName
		// 获取当前bean的key,如果beanName为空,则以beanName为key;
		// 如果是 FactoryBean 类型,beanName 前还会加 & 符号
		// 如果beanName为空,则以当前bean类型的class为key
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
		// 判断当前bean是否正在被代理,如果是,则不进行代理封装
		if (this.earlyBeanReferences.remove(cacheKey) != bean) {
			// 若需要被代理,则需要被封装为指定的bean,使用动态代理技术,产生代理对象
			return wrapIfNecessary(bean, beanName, cacheKey);
		}
	}
	return bean;
}

postProcessAfterInitialization 方法很简单,直接把处理代码委托给了 wrapIfNecessary(bean, beanName, cacheKey) 方法来处理。来看一下这个方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
 * 首先判断是否已经处理过,是否需要跳过,如果跳过的话则直接放入advisedBeans,表示不进行处理
 * Wrap the given bean if necessary, i.e. if it is eligible for being proxied.
 * @param bean the raw bean instance
 * @param beanName the name of the bean
 * @param cacheKey the cache key for metadata access
 * @return a proxy wrapping the bean, or the raw bean instance as-is
 */
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
	// 已经处理过的
	if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
		return bean;
	}
	// 判断是否不应该代理这个 Bean
	// advisedBeans中缓存了已经被代理过的Bean,如果存在,则直接返回
	if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
		return bean;
	}
	// 基础设施类,或者不需要代理的类,则跳过
	// Advice/Pointcut/Advisor/AopInfrastructureBean接口的beanClass不进行代理以及对beanName为aop内的切面名也不进行代理
	// 所谓基础设施类,就是 AOP 相关的注解以及这些注解标识的类
	if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
		this.advisedBeans.put(cacheKey, Boolean.FALSE);
		return bean;
	}

	// Create proxy if we have advice.
	// 如果需要代理,则创建一个代理对象
	// 查找对代理类相关的advisor对象集合,此处就与point-cut表达式有关了
	Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
	// 如果存在增强方法,则创建代理
	// 对相应的 advisor 不为空才采取代理
	if (specificInterceptors != DO_NOT_PROXY) {
		this.advisedBeans.put(cacheKey, Boolean.TRUE);
		// 根据获取到的 Advices 和 Advisors 为当前 Bean 生成代理对象
		Object proxy = createProxy(
				bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
		// 缓存代理对象 Bean 的类型,并且返回代理对象
		this.proxyTypes.put(cacheKey, proxy.getClass());
		return proxy;
	}

	this.advisedBeans.put(cacheKey, Boolean.FALSE);
	return bean;
}

通过对 wrapIfNecessary 分析,我们可以看出,核心处理也就是两个操作:

  1. getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource) 获取了所有符合条件的增强信息。

  2. createProxy(beanClass, beanName, specificInterceptors, targetSource) 创建了代理对象。

这和 postProcessBeforeInstantiation 方法中的处理就一样了。经过千山万水,终于成功在延安胜利会师。下一篇文章 Spring AOP 源码分析:获得通知,重点介绍一下如何获取通知。

4.3. 获得通知

在文章 Spring AOP 处理流程概述 中,对 Spring AOP 有了一个整体认识。在文章 Spring AOP 源码分析:入门 中,对 Spring AOP 的相关入口做了分析。这篇文章就带大家看一看,Spring AOP 是如何获取通知的?

4.3.1. 示例代码

如何阅读 Spring 源码?: 示例代码 中,已经给出了一个完整的 AOP 示例代码。为了节省篇幅,请直接参考那篇文章的示例代码,这里就不在赘述。

4.3.2. 注册 Advice(通知/增强)

请根据 Spring AOP 源码分析:入门 中提到的关键方法入口处,打上断点,开始调试。

首先,需要明确一点的是:对于切面(使用 @Aspect 注解标注过的类)在 Spring 容器中,也是被统一f封装为 BeanDefinition 实例的,也需要通过一个方式,将其注册到 Spring 容器中。比如,就像 示例代码 那样,通过 ImportSelector 方式,使用类名,将其注册到容器中。这样,就可以利用 Spring 容器对 Bean 的 API 来统一处理了。

Advice(通知/增强)几乎是在意想不到的地方完成注册的:在第一次调用 AbstractAutoProxyCreator#postProcessBeforeInstantiation 方法时,通过 AspectJAwareAdvisorAutoProxyCreator#shouldSkip 方法,完成了切面的注册。下面,我们对这个过程抽丝剥茧,逐步分析。

先来看看 findCandidateAdvisors 方法:

AnnotationAwareAspectJAutoProxyCreator#findCandidateAdvisors
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@Override
protected List<Advisor> findCandidateAdvisors() {
	// Add all the Spring advisors found according to superclass rules.
	//当使用注解方式配置AOP的时候并不是丢弃了对XML配置的支持
	//在这里调用父类方法加载配置文件中的AOP声明
	List<Advisor> advisors = super.findCandidateAdvisors();
	// Build Advisors for all AspectJ aspects in the bean factory.
	if (this.aspectJAdvisorsBuilder != null) {
		advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
	}
	return advisors;
}
4.3.2.1. 查找 XML 配置的 Advisor

正如上面注释所示,会先通过 super.findCandidateAdvisors() 先获取父类方法加载的切面声明:

AbstractAdvisorAutoProxyCreator#findCandidateAdvisors
1
2
3
4
5
6
7
8
/**
 * Find all candidate Advisors to use in auto-proxying.
 * @return the List of candidate Advisors
 */
protected List<Advisor> findCandidateAdvisors() {
	Assert.state(this.advisorRetrievalHelper != null, "No BeanFactoryAdvisorRetrievalHelper available");
	return this.advisorRetrievalHelper.findAdvisorBeans();
}

直接看 advisorRetrievalHelper.findAdvisorBeans() 方法:

BeanFactoryAdvisorRetrievalHelper#findAdvisorBeans
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
 * Find all eligible Advisor beans in the current bean factory,
 * ignoring FactoryBeans and excluding beans that are currently in creation.
 * @return the list of {@link org.springframework.aop.Advisor} beans
 * @see #isEligibleBean
 */
public List<Advisor> findAdvisorBeans() {
	// Determine list of advisor bean names, if not cached already.
	String[] advisorNames = this.cachedAdvisorBeanNames;
	if (advisorNames == null) {
		// Do not initialize FactoryBeans here: We need to leave all regular beans
		// uninitialized to let the auto-proxy creator apply to them!
		// 在 beanNamesForTypeIncludingAncestors 方法中,通过遍历所有 Bean 名称来选取合适的对对象,
		// 查找的是通过 XML 配置的 <aop:advisor/> Bean。并不会把 @Aspect 标注的类给选出来。
		advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
				this.beanFactory, Advisor.class, true, false);
		this.cachedAdvisorBeanNames = advisorNames;
	}
	if (advisorNames.length == 0) {
		return new ArrayList<>();
	}

	List<Advisor> advisors = new ArrayList<>();
	for (String name : advisorNames) {
		if (isEligibleBean(name)) {
			if (this.beanFactory.isCurrentlyInCreation(name)) {
				if (logger.isTraceEnabled()) {
					logger.trace("Skipping currently created advisor '" + name + "'");
				}
			}
			else {
				try {
					advisors.add(this.beanFactory.getBean(name, Advisor.class));
				}
				catch (BeanCreationException ex) {
					Throwable rootCause = ex.getMostSpecificCause();
					if (rootCause instanceof BeanCurrentlyInCreationException bce) {
						String bceBeanName = bce.getBeanName();
						if (bceBeanName != null && this.beanFactory.isCurrentlyInCreation(bceBeanName)) {
							if (logger.isTraceEnabled()) {
								logger.trace("Skipping advisor '" + name +
										"' with dependency on currently created bean: " + ex.getMessage());
							}
							// Ignore: indicates a reference back to the bean we're trying to advise.
							// We want to find advisors other than the currently created bean itself.
							continue;
						}
					}
					throw ex;
				}
			}
		}
	}
	return advisors;
}

这里通过 BeanFactoryUtils#beanNamesForTypeIncludingAncestors(ListableBeanFactory, Class<?>, boolean, boolean) 方法来查找 Advisor。整个过程,就是针对 BeanFactory 递归调用其父 BeanFactory,遍历所有的 Bean 名称,查找类型为 Advisor 的 Bean 名称,然后调用 beanFactory.getBean(name, Advisor.class),来获得对应的 Advisor Bean 并返回。

上面介绍了查找 XML 配置的 Advisor 过程。

4.3.2.2. 查找通过注解配置的 Advisor

我们回到 AnnotationAwareAspectJAutoProxyCreator#findCandidateAdvisors 方法中, BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors

BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
/**
 * Look for AspectJ-annotated aspect beans in the current bean factory,
 * and return to a list of Spring AOP Advisors representing them.
 * <p>Creates a Spring Advisor for each AspectJ advice method.
 * @return the list of {@link org.springframework.aop.Advisor} beans
 * @see #isEligibleBean
 */
public List<Advisor> buildAspectJAdvisors() {
	List<String> aspectNames = this.aspectBeanNames;

	if (aspectNames == null) {
		synchronized (this) {
			aspectNames = this.aspectBeanNames;
			if (aspectNames == null) {
				List<Advisor> advisors = new ArrayList<>();
				aspectNames = new ArrayList<>();
				//获取所有的beanName
				String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
						this.beanFactory, Object.class, true, false);
				//循环所有的beanName找出对应的增强方法
				for (String beanName : beanNames) {
					//不合法的bean则略过,由子类定义规则,默认返回true
					if (!isEligibleBean(beanName)) {
						continue;
					}
					// We must be careful not to instantiate beans eagerly as in this case they
					// would be cached by the Spring container but would not have been weaved.
					//获取对应的bean的类型
					Class<?> beanType = this.beanFactory.getType(beanName, false);
					if (beanType == null) {
						continue;
					}
					//如果存在Aspect注解
					if (this.advisorFactory.isAspect(beanType)) {
						aspectNames.add(beanName);
						AspectMetadata amd = new AspectMetadata(beanType, beanName);
						if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
							MetadataAwareAspectInstanceFactory factory =
									new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
							//解析标记AspectJ注解中的增强方法
							List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
							if (this.beanFactory.isSingleton(beanName)) {
								this.advisorsCache.put(beanName, classAdvisors);
							}
							else {
								this.aspectFactoryCache.put(beanName, factory);
							}
							advisors.addAll(classAdvisors);
						}
						else {
							// Per target or per this.
							if (this.beanFactory.isSingleton(beanName)) {
								throw new IllegalArgumentException("Bean with name '" + beanName +
										"' is a singleton, but aspect instantiation model is not singleton");
							}
							MetadataAwareAspectInstanceFactory factory =
									new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
							this.aspectFactoryCache.put(beanName, factory);
							advisors.addAll(this.advisorFactory.getAdvisors(factory));
						}
					}
				}
				this.aspectBeanNames = aspectNames;
				return advisors;
			}
		}
	}

	if (aspectNames.isEmpty()) {
		return Collections.emptyList();
	}
	List<Advisor> advisors = new ArrayList<>();
	for (String aspectName : aspectNames) {
		List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);
		if (cachedAdvisors != null) {
			advisors.addAll(cachedAdvisors);
		}
		else {
			MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName);
			advisors.addAll(this.advisorFactory.getAdvisors(factory));
		}
	}
	return advisors;
}

这里的逻辑比上面要简单清晰好多:查找出所有的 Bean 名称,然后选出类型标注了 @Aspect 注解的 Bean 类型,把 Bean 名称添加到 BeanFactoryAspectJAdvisorsBuilder#aspectBeanNames 实例变量中;根据类型信息,使用反射针对符合添加的方法,构建 Advisor 对象(实现类为 InstantiationModelAwarePointcutAdvisorImpl),然后将其加入到 BeanFactoryAspectJAdvisorsBuilder#advisorsCache 变量中。

值得注意的是根据通知的类型,创建不同的 Advice 对象,也是在上面的这个过程中,在 ReflectiveAspectJAdvisorFactory#getAdvice 方法中完成的。

经过上面的处理,所有对应的 Advice(通知/增强)都会被查找出来。接下来,我们看一看如何针对特定的 Bean 选择出合适的 Advice(通知/增强)的。

这里说“注册”其实意思不太正确。 Advice(通知/增强)没有什么注册一说,它只是被解析后缓存了起来。下次再使用时,就不需要解析了。

4.3.3. 选取 Advice(通知/增强)

上面解析后的 Advice(通知/增强)都被存放在了 BeanFactoryAspectJAdvisorsBuilder#Map<String, List<Advisor>> advisorsCache 变量中。所以,从这里拿到所有通知后再去做筛选。

Spring Bean 生命周期概述 中已经强调过了,AOP 代理的创建是在执行 BeanPostProcessor#postProcessAfterInitialization,也就是 AnnotationAwareAspectJAutoProxyCreator#postProcessAfterInitialization 方法中。

Spring AOP 源码分析:入门 中提到`getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource)` 获取了所有符合条件的增强信息。

结合上面两点,找到对应的方法就是 AbstractAdvisorAutoProxyCreator#getAdvicesAndAdvisorsForBean。但是,这个方法几乎没啥代码,而是把处理全部委托给了 AbstractAdvisorAutoProxyCreator#findEligibleAdvisors 方法来处理。所以,可以直接看这个方法:

AbstractAdvisorAutoProxyCreator#findEligibleAdvisors
1
2
3
4
5
6
7
8
/**
 * Find all candidate Advisors to use in auto-proxying.
 * @return the List of candidate Advisors
 */
protected List<Advisor> findCandidateAdvisors() {
	Assert.state(this.advisorRetrievalHelper != null, "No BeanFactoryAdvisorRetrievalHelper available");
	return this.advisorRetrievalHelper.findAdvisorBeans();
}

重点在 findAdvisorsThatCanApply 方法上,从方法名上来看,这似乎是要查找可用的 Advisor。来看一下具体实现:

AopUtils#findAdvisorsThatCanApply
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
 * Determine the sublist of the {@code candidateAdvisors} list
 * that is applicable to the given class.
 * @param candidateAdvisors the Advisors to evaluate
 * @param clazz the target class
 * @return sublist of Advisors that can apply to an object of the given class
 * (may be the incoming List as-is)
 */
// 找到合适的 advisors,引介增强在前,其他普通增强在后
public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
	if (candidateAdvisors.isEmpty()) {
		return candidateAdvisors;
	}
	List<Advisor> eligibleAdvisors = new ArrayList<>();
	// 首先处理引介增强
	for (Advisor candidate : candidateAdvisors) {
		if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
			eligibleAdvisors.add(candidate);
		}
	}
	boolean hasIntroductions = !eligibleAdvisors.isEmpty();
	for (Advisor candidate : candidateAdvisors) {
		// 对于引介增加已经处理过了
		if (candidate instanceof IntroductionAdvisor) {
			// already processed
			continue;
		}
		// 这里来解析切入点表达式
		// 对普通 Bean 的处理
		if (canApply(candidate, clazz, hasIntroductions)) {
			eligibleAdvisors.add(candidate);
		}
	}
	return eligibleAdvisors;
}

经过多个重载 canApply 方法的来回传递,最后由如下方法来进行处理:

AopUtils#canApply(Pointcut, Class<?>, boolean)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**
  * 表达式被解析成 AspectJExpressionPointcut 对象
  *
 * Can the given pointcut apply at all on the given class?
 * <p>This is an important test as it can be used to optimize
 * out a pointcut for a class.
 * @param pc the static or dynamic pointcut to check
 * @param targetClass the class to test
 * @param hasIntroductions whether the advisor chain
 * for this bean includes any introductions
 * @return whether the pointcut can apply on any method
 */
public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
	Assert.notNull(pc, "Pointcut must not be null");
	// 这里先判断类型是否匹配。交给 AspectJ 来完成了,不再深究。
	if (!pc.getClassFilter().matches(targetClass)) {
		return false;
	}

	MethodMatcher methodMatcher = pc.getMethodMatcher();
	if (methodMatcher == MethodMatcher.TRUE) {
		// No need to iterate the methods if we're matching any method anyway...
		return true;
	}

	IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
	if (methodMatcher instanceof IntroductionAwareMethodMatcher iamm) {
		introductionAwareMethodMatcher = iamm;
	}

	Set<Class<?>> classes = new LinkedHashSet<>();
	if (!Proxy.isProxyClass(targetClass)) {
		classes.add(ClassUtils.getUserClass(targetClass));
	}
	classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass));

	for (Class<?> clazz : classes) { // 针对每个方法,判断是否符合表达式要求
		Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
		for (Method method : methods) {
			if (introductionAwareMethodMatcher != null ? // 匹配操作都交给了 AspectJ 来完成,不再深究
					introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :
					methodMatcher.matches(method, targetClass)) {
				return true;
			}
		}
	}

	return false;
}

结果类型和方法的双重筛选后,就可以把符合条件的 Advice(通知/增强)给选择出来了。下一篇文章,来介绍一下如果创建代理类: Spring AOP 源码分析:创建代理(一)Spring AOP 源码分析:创建代理(二)

4.4. 创建代理(一):动态代理

Spring AOP 源码分析:入门 中,梳理出来了 Spring AOP 的入口。上一篇文章 Spring AOP 源码分析:获得通知 中着重介绍了如何获取通知。接着上一篇文章,这篇文章介绍一下如何创建代理。

AbstractAutoProxyCreator#createProxy
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
/**
 * Create an AOP proxy for the given bean.
 * @param beanClass the class of the bean
 * @param beanName the name of the bean
 * @param specificInterceptors the set of interceptors that is
 * specific to this bean (may be empty, but not null)
 * @param targetSource the TargetSource for the proxy,
 * already pre-configured to access the bean
 * @return the AOP proxy for the bean
 * @see #buildAdvisors
 */
protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
		@Nullable Object[] specificInterceptors, TargetSource targetSource) {

	return buildProxy(beanClass, beanName, specificInterceptors, targetSource, false);
}

private Class<?> createProxyClass(Class<?> beanClass, @Nullable String beanName,
		@Nullable Object[] specificInterceptors, TargetSource targetSource) {

	return (Class<?>) buildProxy(beanClass, beanName, specificInterceptors, targetSource, true);
}

private Object buildProxy(Class<?> beanClass, @Nullable String beanName,
		@Nullable Object[] specificInterceptors, TargetSource targetSource, boolean classOnly) {

	if (this.beanFactory instanceof ConfigurableListableBeanFactory clbf) {
		// 给 Bean 定义设置暴露属性
		AutoProxyUtils.exposeTargetClass(clbf, beanName, beanClass);
	}

	// 创建代理工厂对象
	ProxyFactory proxyFactory = new ProxyFactory();
	// 获取当前类的属性,proxyTargetClass、exposeProxy 等
	proxyFactory.copyFrom(this);

	if (proxyFactory.isProxyTargetClass()) {
		// Explicit handling of JDK proxy targets and lambdas (for introduction advice scenarios)
		if (Proxy.isProxyClass(beanClass) || ClassUtils.isLambdaClass(beanClass)) {
			// Must allow for introductions; can't just set interfaces to the proxy's interfaces only.
			for (Class<?> ifc : beanClass.getInterfaces()) {
				proxyFactory.addInterface(ifc);
			}
		}
	}
	// 如果没有使用CGLib代理
	// 决定对于给定 Bean 是否应该使用 TargetClass 而不是它的接口代理,
	// 检查 ProxyTargetClass 设置以及 preserverTargetClass 属性
	else {
		// 是否可能使用CGLib代理
		// 决定对于给定的 Bean 是否应该使用 targetClass 而不是他的接口代理
		// No proxyTargetClass flag enforced, let's apply our default checks...
		if (shouldProxyTargetClass(beanClass, beanName)) {
			proxyFactory.setProxyTargetClass(true);
		}
		else {
			// 查看beanClass对应的类是否含有InitializingBean.class/DisposableBean.class/Aware.class接口
			// 无则采用JDK动态代理,有则采用CGLib动态代理
			evaluateProxyInterfaces(beanClass, proxyFactory);
		}
	}

	// 获得所有关联的Advisor集合(该分支待补充)
	Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
	proxyFactory.addAdvisors(advisors);
	// 此处的targetSource一般为SingletonTargetSource
	// 设置需要代理的类
	proxyFactory.setTargetSource(targetSource);
	// 定制代理,扩展点,空实现
	customizeProxyFactory(proxyFactory);

	// 用来控制代理工厂被配置后,是否还允许修改。默认为 false
	proxyFactory.setFrozen(this.freezeProxy);
	// 是否设置预过滤模式,此处针对本文为true
	if (advisorsPreFiltered()) {
		proxyFactory.setPreFiltered(true);
	}

	// Use original ClassLoader if bean class not locally loaded in overriding class loader
	ClassLoader classLoader = getProxyClassLoader();
	if (classLoader instanceof SmartClassLoader smartClassLoader && classLoader != beanClass.getClassLoader()) {
		classLoader = smartClassLoader.getOriginalClassLoader();
	}
	// 获取使用JDK动态代理或者cglib动态代理产生的对象
	return (classOnly ? proxyFactory.getProxyClass(classLoader) : proxyFactory.getProxy(classLoader));
}
ProxyFactory#getProxy(ClassLoader)
1
2
3
4
5
public Object getProxy(@Nullable ClassLoader classLoader) {
  // 1、创建JDK方式的AOP代理或者CGLib方式的AOP代理
  // 2、调用具体的AopProxy来创建Proxy代理对象
  return createAopProxy().getProxy(classLoader);
}

createAopProxy() 方法中就不再列出,因为 AopProxyFactory 接口只有一个实现类 DefaultAopProxyFactory。所以,直接来看看 getProxy(classLoader) 方法:

DefaultAopProxyFactory#createAopProxy
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
	// 如果实现接口,默认采用Java动态代理
	// 如果没有接口,或者有接口却强制使用 cglib
	// optimize 是否实用激进的优化策略
	// proxyTargetClass 为 true,则代理类本身而不是接口
	// 是否存在代理接口
	if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
		Class<?> targetClass = config.getTargetClass();
		if (targetClass == null) {
			throw new AopConfigException("TargetSource cannot determine target class: " +
					"Either an interface or a target is required for proxy creation.");
		}
		if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) {
			return new JdkDynamicAopProxy(config);
		}
		return new ObjenesisCglibAopProxy(config);
	}
	else {
		return new JdkDynamicAopProxy(config);
	}
}

到这里就可以清楚看到

4.4.1. JdkDynamicAopProxy

JdkDynamicAopProxy 类如其名,就是通过 JDK 的动态代理来生成代理类的。对 JDK 动态代理比较熟悉的话,应该清楚:代理类的增强是通过实现 InvocationHandler 接口,在其 invoke 方法中增加增强逻辑。而 JdkDynamicAopProxy 正好实现了 InvocationHandler 接口,所以,在其 invoke 方法中封装了对 AOP 的 Advice(通知/增强) 调用链。

JdkDynamicAopProxy
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@Override
public Object getProxy() {
	return getProxy(ClassUtils.getDefaultClassLoader());
}

/**
 * 调用 Proxy.newProxyInstance 创建代理对象
 */
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
	if (logger.isTraceEnabled()) {
		logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
	}
       // 调用JDK动态代理方法
	return Proxy.newProxyInstance(determineClassLoader(classLoader), this.proxiedInterfaces, this);
}

由于 JdkDynamicAopProxy 实现了 InvocationHandler。所以,重点就是 invoke() 方法。来看一下:

JdkDynamicAopProxy#invoke
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
/**
 * Implementation of {@code InvocationHandler.invoke}.
 * <p>Callers will see exactly the exception thrown by the target,
 * unless a hook method throws an exception.
 */
@Override
@Nullable
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
	Object oldProxy = null;
	boolean setProxyContext = false;

	TargetSource targetSource = this.advised.targetSource;
	Object target = null;

	try {
		// equals() 方法,其目标对象未实现此方法
		if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
			// The target does not implement the equals(Object) method itself.
			return equals(args[0]);
		}
		// hashCode() 方法,其目标对象未实现此方法
		else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
			// The target does not implement the hashCode() method itself.
			return hashCode();
		}
		else if (method.getDeclaringClass() == DecoratingProxy.class) {
			// There is only getDecoratedClass() declared -> dispatch to proxy config.
			return AopProxyUtils.ultimateTargetClass(this.advised);
		}
		// Advised 接口或者其父接口中定义的方法,直接反射调用,不应用通知
		else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
				method.getDeclaringClass().isAssignableFrom(Advised.class)) {
			// Service invocations on ProxyConfig with the proxy config...
			return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
		}

		Object retVal;
		// 通过设置 exposeProxy,可以将代理暴露到代理上下文中
		if (this.advised.exposeProxy) {
			// Make invocation available if necessary.
			oldProxy = AopContext.setCurrentProxy(proxy);
			setProxyContext = true;
		}

		// Get as late as possible to minimize the time we "own" the target,
		// in case it comes from a pool.
		// 获取目标对象
		target = targetSource.getTarget();
		// 获取目标对象的类型
		Class<?> targetClass = (target != null ? target.getClass() : null);

		// Get the interception chain for this method.
		// 获取针对该目标对象的所有增强器(advisor), 这些advisor都是有顺序的,他们会按照顺序进行链式调用
		List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

		// Check whether we have any advice. If we don't, we can fall back on direct
		// reflective invocation of the target, and avoid creating a MethodInvocation.
		// 检查是否我们有一些通知。
		// 如果没有,可以直接对目标类进行反射调用,避免创建MethodInvocation类
		// 如果没有设定拦截器,那么就直接调用目标类 target 的对应方法
		if (chain.isEmpty()) {
			// We can skip creating a MethodInvocation: just invoke the target directly
			// Note that the final invoker must be an InvokerInterceptor so we know it does
			// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
			Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
			// 通过反射调用目标对象的方法
			retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
		}
		else {
			// We need to create a method invocation...
			// 创建 MethodInvocation
			//我们需要创建一个方法调用
			// proxy:生成的动态代理对象
			// target:目标方法
			// args: 目标方法参数
			// targetClass:目标类对象
			// chain: AOP拦截器执行链,是一个MethodInterceptor的集合
			MethodInvocation invocation =
					new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
			// Proceed to the joinpoint through the interceptor chain.
			// 通过拦截器链进入连接点
			// 开始执行AOP的拦截过程
			retVal = invocation.proceed();
		}

		// Massage return value if necessary.
		Class<?> returnType = method.getReturnType();
		if (retVal != null && retVal == target &&
				returnType != Object.class && returnType.isInstance(proxy) &&
				!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
			// Special case: it returned "this" and the return type of the method
			// is type-compatible. Note that we can't help if the target sets
			// a reference to itself in another returned object.
			retVal = proxy;
		}
		else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
			throw new AopInvocationException(
					"Null return value from advice does not match primitive return type for: " + method);
		}
		if (KotlinDetector.isSuspendingFunction(method)) {
			return COROUTINES_FLOW_CLASS_NAME.equals(new MethodParameter(method, -1).getParameterType().getName()) ?
					CoroutinesUtils.asFlow(retVal) : CoroutinesUtils.awaitSingleOrNull(retVal, args[args.length - 1]);
		}
		return retVal;
	}
	finally {
		if (target != null && !targetSource.isStatic()) {
			// Must have come from TargetSource.
			targetSource.releaseTarget(target);
		}
		if (setProxyContext) {
			// Restore old proxy.
			AopContext.setCurrentProxy(oldProxy);
		}
	}
}

从上面高亮代码部分可以看出,增强调用链是在 this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass) 方法中组装的。实际上,它是委托给 DefaultAdvisorChainFactory#getInterceptorsAndDynamicInterceptionAdvice 方法来完成的。来看一下这个代码:

DefaultAdvisorChainFactory#getInterceptorsAndDynamicInterceptionAdvice
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
/**
 * 从提供的配置实例 config 中获取 advisor 列表,遍历处理这些 advisor。
 * 如果是 IntroductionAdvisor,则判断此 Advisor 能否应用到目标 targetClass 上。
 * 如果是 PointcutAdvisor,则判断此 Advisor 能否应用到目标方法 Method 上,
 * 将满足条件的 Advisor 通过 AdvisorAdapter 转化成 Interceptor 列表返回。
 */
@Override
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
		Advised config, Method method, @Nullable Class<?> targetClass) {

	// This is somewhat tricky... We have to process introductions first,
	// but we need to preserve order in the ultimate list.
	// advice适配器注册中心
	// MethodBeforeAdviceAdapter:将Advisor适配成MethodBeforeAdvice
	// AfterReturningAdviceAdapter:将Advisor适配成AfterReturningAdvice
	// ThrowsAdviceAdapter: 将Advisor适配成ThrowsAdvice
	// 这里实际上注册一系列 AdvisorAdapter,用于将 Advisor 转化成 MethodInterceptor
	AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
	Advisor[] advisors = config.getAdvisors();
	// 返回值集合,里面装的都是Interceptor或者它的子类接口MethodInterceptor
	List<Object> interceptorList = new ArrayList<>(advisors.length);
	// 获取目标类的类型
	Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass());
	// 是否有引介
	Boolean hasIntroductions = null;

	// 去产生代理对象的过程中,针对该目标方法获取到的所有合适的Advisor集合
	for (Advisor advisor : advisors) {
		if (advisor instanceof PointcutAdvisor pointcutAdvisor) {
			// Add it conditionally.
			// 如果该Advisor可以对目标类进行增强,则进行后续操作
			if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
				// 获取方法适配器,该方法匹配器可以根据指定的切入点表达式进行方法匹配
				MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
				boolean match;
				if (mm instanceof IntroductionAwareMethodMatcher iamm) {
					if (hasIntroductions == null) {
						// 判断是否包含 IntroductionAdvisor
						hasIntroductions = hasMatchingIntroductions(advisors, actualClass);
					}
					match = iamm.matches(method, actualClass, hasIntroductions);
				}
				else {
					match = mm.matches(method, actualClass);
				}
				if (match) {
					// 将advisor转成MethodInterceptor
					// 从 GlobalAdvisorAdapterRegistry 获得 MethodInterceptor
					MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
					// MethodMatcher接口通过重载定义了两个matches()方法
					// 两个参数的matches() 被称为静态匹配,在匹配条件不是太严格时使用,可以满足大部分场景的使用
					// 称之为静态的主要是区分为三个参数的matches()方法需要在运行时动态的对参数的类型进行匹配
					// 两个方法的分界线就是boolean isRuntime()方法
					// 进行匹配时先用两个参数的matches()方法进行匹配,若匹配成功,则检查boolean isRuntime()的返回值若为
					// true, 则调用三个参数的matches()方法进行匹配(若两个参数的都匹配不中,三个参数的必定匹配不中)

					// 需要根据参数动态匹配(比如重载)
					if (mm.isRuntime()) {
						// Creating a new object instance in the getInterceptors() method
						// isn't a problem as we normally cache created chains.
						for (MethodInterceptor interceptor : interceptors) {
							interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));
						}
					}
					else {
						interceptorList.addAll(Arrays.asList(interceptors));
					}
				}
			}
		}
		else if (advisor instanceof IntroductionAdvisor ia) {
			if (config.isPreFiltered() || ia.getClassFilter().matches(actualClass)) {
				Interceptor[] interceptors = registry.getInterceptors(advisor);
				interceptorList.addAll(Arrays.asList(interceptors));
			}
		}
		else {
			Interceptor[] interceptors = registry.getInterceptors(advisor);
			interceptorList.addAll(Arrays.asList(interceptors));
		}
	}

	return interceptorList;
}

通知的执行则是委托给 ReflectiveMethodInvocation#proceed 来执行的。具体实现如下:

ReflectiveMethodInvocation#proceed
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@Override
@Nullable
public Object proceed() throws Throwable {
	// We start with an index of -1 and increment early.
	// 如果执行到链条的末尾, 则直接调用连接点方法 即直接调用目标方法
	if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
		return invokeJoinpoint();
	}

	// 获取集合中的MethodInterceptor
	Object interceptorOrInterceptionAdvice =
			this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
	// 如果是InterceptorAndDynamicMethodMatcher类型(动态匹配)
	if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher dm) {
		// Evaluate dynamic method matcher here: static part will already have
		// been evaluated and found to match.
		// 如果要动态匹配 joinPoint
		Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
		// 动态匹配:运行时参数是否满足匹配条件
		// 这里每一次都去匹配是否适用于这个目标方法
		if (dm.matcher().matches(this.method, targetClass, this.arguments)) {
			// 如果匹配则直接调用MethodInterceptor的invoke方法
			// 注意这里传入的参数是this,我们下面看一下ReflectiveMethodInvocation的类型
			return dm.interceptor().invoke(this);
		}
		else {
			// Dynamic matching failed.
			// Skip this interceptor and invoke the next in the chain.
			// 动态匹配失败时,略过当前 Interceptor,调用下一个 Interceptor
			// 如果不适用于此目标方法,则继续执行下一链条
			// 递归调用
			return proceed();
		}
	}
	else {
		// It's an interceptor, so we just invoke it: The pointcut will have
		// been evaluated statically before this object was constructed.
		// 说明是适用于此目标方法的,直接调用MethodInterceptor的invoke方法
		// 传入this即ReflectiveMethodInvocation实例
		// 传入this进入 这样就可以形成一个调用的链条了
		// 执行当前 Interceptor
		return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
	}
}

ReflectiveMethodInvocation 通过递归调用 proceed() 方法,来实现链式调用的。因为链本身是一个 List 对象,每次递归调用时,只需要推进其下标就可以实现链式调用的效果。

贴代码太多,篇幅有又老长了。关于利用 cglib 创建代理的过程,留到下一篇文章来重点介绍: Spring AOP 源码分析:创建代理(二)

4.5. 创建代理(二):CGLIB

Spring AOP 源码分析:入门 中,梳理出来了 Spring AOP 的入口。 Spring AOP 源码分析:获得通知 中着重介绍了如何获取通知。上一篇文章 Spring AOP 源码分析:创建代理(一) 重点介绍了一下切面链的组装和基于 JDK 动态代理的 AOP 的实现,这篇文章介绍一下基于 cglib 的代理类是生成。

4.5.1. CGLIB 简介

CGLIB

CGLIB(Code Generator Library)是一个高性能的代码生成库,被广泛应用于 AOP 框架(Spring)中以提供方法拦截功能,主要以继承目标类的方式来进行拦截实现,因此 CGLIB 可以对无接口的类进行代理。

CGLIB代理主要通过操作字节码的方式为对象引入方法调用时访问操作,底层使用了ASM来操作字节码生成新的类,ASM是一个短小精悍的字节码操作框架。CGLIB的应用栈如下:

CGLIB

最新版的 Hibernate 已经把字节码库从 cglib 切换为 Byte Buddy。

JDK 动态代理是通过实现 InvocationHandler 接口,在其 invoke 方法中添加切面逻辑。而 cglib 则是通过实现 MethodInterceptor 接口,在其 invoke 方法中添加切面逻辑。

下面看一下在 Spring 中,是如何实现利用 cglib 来实现 AOP 编程的?

4.5.2. CglibAopProxy

先看一下创建代理对象的方法:

CglibAopProxy#getProxy(ClassLoader)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
	return buildProxy(classLoader, false);
}

@Override
public Class<?> getProxyClass(@Nullable ClassLoader classLoader) {
	return (Class<?>) buildProxy(classLoader, true);
}

private Object buildProxy(@Nullable ClassLoader classLoader, boolean classOnly) {
	if (logger.isTraceEnabled()) {
		logger.trace("Creating CGLIB proxy: " + this.advised.getTargetSource());
	}

	try {
		Class<?> rootClass = this.advised.getTargetClass();
		Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");

		Class<?> proxySuperClass = rootClass;
		if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {
			proxySuperClass = rootClass.getSuperclass();
			Class<?>[] additionalInterfaces = rootClass.getInterfaces();
			for (Class<?> additionalInterface : additionalInterfaces) {
				this.advised.addInterface(additionalInterface);
			}
		}

		// Validate the class, writing log messages as necessary.
		// 验证 Class
		validateClassIfNecessary(proxySuperClass, classLoader);

		// Configure CGLIB Enhancer...
		Enhancer enhancer = createEnhancer();
		if (classLoader != null) {
			enhancer.setClassLoader(classLoader);
			if (classLoader instanceof SmartClassLoader smartClassLoader &&
					smartClassLoader.isClassReloadable(proxySuperClass)) {
				enhancer.setUseCache(false);
			}
		}
		enhancer.setSuperclass(proxySuperClass);
		enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
		enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
		enhancer.setAttemptLoad(true);
		enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));

		// 设置拦截器
		Callback[] callbacks = getCallbacks(rootClass);
		Class<?>[] types = new Class<?>[callbacks.length];
		for (int x = 0; x < types.length; x++) {
			types[x] = callbacks[x].getClass();
		}
		// fixedInterceptorMap only populated at this point, after getCallbacks call above
		ProxyCallbackFilter filter = new ProxyCallbackFilter(
				this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset);
		enhancer.setCallbackFilter(filter);
		enhancer.setCallbackTypes(types);

		// Generate the proxy class and create a proxy instance.
		// ProxyCallbackFilter has method introspection capability with Advisor access.
		try {
			// 生成代理类以及创建代理
			return (classOnly ? createProxyClass(enhancer) : createProxyClassAndInstance(enhancer, callbacks));
		}
		finally {
			// Reduce ProxyCallbackFilter to key-only state for its class cache role
			// in the CGLIB$CALLBACK_FILTER field, not leaking any Advisor state...
			filter.advised.reduceToAdvisorKey();
		}
	}
	catch (CodeGenerationException | IllegalArgumentException ex) {
		throw new AopConfigException("Could not generate CGLIB subclass of " + this.advised.getTargetClass() +
				": Common causes of this problem include using a final class or a non-visible class",
				ex);
	}
	catch (Throwable ex) {
		// TargetSource.getTarget() failed
		throw new AopConfigException("Unexpected AOP exception", ex);
	}
}

这里的关键是创建 Callback 数组,这里封装着切面逻辑。

CglibAopProxy#getCallbacks
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
private Callback[] getCallbacks(Class<?> rootClass) throws Exception {
	// Parameters used for optimization choices...
	boolean isStatic = this.advised.getTargetSource().isStatic();
	boolean isFrozen = this.advised.isFrozen();
	// 对 expose-proxy 属性的处理
	boolean exposeProxy = this.advised.isExposeProxy();

	// Choose an "aop" interceptor (used for AOP calls).
	// 将拦截器封装在 DynamicAdvisedInterceptor 中
	Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised);

	// Choose a "straight to target" interceptor. (used for calls that are
	// unadvised but can return this). May be required to expose the proxy.
	Callback targetInterceptor;
	if (exposeProxy) {
		targetInterceptor = (isStatic ?
				new StaticUnadvisedExposedInterceptor(this.advised.getTargetSource().getTarget()) :
				new DynamicUnadvisedExposedInterceptor(this.advised.getTargetSource()));
	}
	else {
		targetInterceptor = (isStatic ?
				new StaticUnadvisedInterceptor(this.advised.getTargetSource().getTarget()) :
				new DynamicUnadvisedInterceptor(this.advised.getTargetSource()));
	}

	// Choose a "direct to target" dispatcher (used for
	// unadvised calls to static targets that cannot return this).
	Callback targetDispatcher = (isStatic ?
			new StaticDispatcher(this.advised.getTargetSource().getTarget()) : new SerializableNoOp());

	Callback[] mainCallbacks = new Callback[] {
			// 将拦截器链加入 Callback 中
			aopInterceptor,  // for normal advice
			targetInterceptor,  // invoke target without considering advice, if optimized
			new SerializableNoOp(),  // no override for methods mapped to this
			targetDispatcher, this.advisedDispatcher,
			new EqualsInterceptor(this.advised),
			new HashCodeInterceptor(this.advised)
	};

	// If the target is a static one and the advice chain is frozen,
	// then we can make some optimizations by sending the AOP calls
	// direct to the target using the fixed chain for that method.
	if (isStatic && isFrozen) {
		Method[] methods = rootClass.getMethods();
		int methodsCount = methods.length;
		List<Callback> fixedCallbacks = new ArrayList<>(methodsCount);
		this.fixedInterceptorMap = CollectionUtils.newHashMap(methodsCount);

		int advicedMethodCount = methodsCount;
		for (int x = 0; x < methodsCount; x++) {
			Method method = methods[x];
			//do not create advices for non-overridden methods of java.lang.Object
			if (method.getDeclaringClass() == Object.class) {
				advicedMethodCount--;
				continue;
			}
			List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, rootClass);
			fixedCallbacks.add(new FixedChainStaticTargetInterceptor(
					chain, this.advised.getTargetSource().getTarget(), this.advised.getTargetClass()));
			this.fixedInterceptorMap.put(method, x - (methodsCount - advicedMethodCount) );
		}

		// Now copy both the callbacks from mainCallbacks
		// and fixedCallbacks into the callbacks array.
		Callback[] callbacks = new Callback[mainCallbacks.length + advicedMethodCount];
		System.arraycopy(mainCallbacks, 0, callbacks, 0, mainCallbacks.length);
		System.arraycopy(fixedCallbacks.toArray(Callback[]::new), 0, callbacks,
				mainCallbacks.length, advicedMethodCount);
		this.fixedInterceptorOffset = mainCallbacks.length;
		return callbacks;
	}
	return mainCallbacks;
}

CGLIB 是通过 MethodInterceptor 来实现方法的拦截和增强的。所以,CglibAopProxy 实现的 AOP 的增强都被封装在了 CglibAopProxy.DynamicAdvisedInterceptor 类的 intercept 中。

CglibAopProxy.DynamicAdvisedInterceptor
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
/**
 * General purpose AOP callback. Used when the target is dynamic or when the
 * proxy is not frozen.
 */
private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {

	private final AdvisedSupport advised;

	public DynamicAdvisedInterceptor(AdvisedSupport advised) {
		this.advised = advised;
	}

	@Override
	@Nullable
	public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
		Object oldProxy = null;
		boolean setProxyContext = false;
		Object target = null;
		TargetSource targetSource = this.advised.getTargetSource();
		try {
			if (this.advised.exposeProxy) {
				// Make invocation available if necessary.
				oldProxy = AopContext.setCurrentProxy(proxy);
				setProxyContext = true;
			}
			// Get as late as possible to minimize the time we "own" the target, in case it comes from a pool...
			target = targetSource.getTarget();
			Class<?> targetClass = (target != null ? target.getClass() : null);
			// 获取拦截器链
			List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
			Object retVal;
			// Check whether we only have one InvokerInterceptor: that is,
			// no real advice, but just reflective invocation of the target.
			if (chain.isEmpty()) {
				// We can skip creating a MethodInvocation: just invoke the target directly.
				// Note that the final invoker must be an InvokerInterceptor, so we know
				// it does nothing but a reflective operation on the target, and no hot
				// swapping or fancy proxying.
				Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
				// 如果拦截器链为空则直接激活原方法
				retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
			}
			else {
				// We need to create a method invocation...
				// 进入链
				retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
			}
			return processReturnType(proxy, target, method, args, retVal);
		}
		finally {
			if (target != null && !targetSource.isStatic()) {
				targetSource.releaseTarget(target);
			}
			if (setProxyContext) {
				// Restore old proxy.
				AopContext.setCurrentProxy(oldProxy);
			}
		}
	}

	@Override
	public boolean equals(@Nullable Object other) {
		return (this == other ||
				(other instanceof DynamicAdvisedInterceptor dynamicAdvisedInterceptor &&
						this.advised.equals(dynamicAdvisedInterceptor.advised)));
	}

	/**
	 * CGLIB uses this to drive proxy creation.
	 */
	@Override
	public int hashCode() {
		return this.advised.hashCode();
	}
}

还是熟悉的配方,还是熟悉的味道,又看到了 this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass) 了。

无论是 JdkDynamicAopProxy,还是 CglibAopProxy,它们也只是做了基本处理,而真正对 Advice(通知/增强) 的链式调用都是通过 AdvisedSupport#getInterceptorsAndDynamicInterceptionAdvice 最终委托给了 DefaultAdvisorChainFactory#getInterceptorsAndDynamicInterceptionAdvice 方法来生成 Advice(通知/增强)链,然后通过 ReflectiveMethodInvocation 及其子类来调用到 Advice(通知/增强)链。

JdkDynamicAopProxyinvoke 方法中,通过创建 ReflectiveMethodInvocation 对象,调用其 proceed() 方法,来完成增强的链式调用。

CglibAopProxyintercept 方法中,通过创建 CglibMethodInvocation 对象,调用其 proceed() 方法,来完成增强的链式调用。 CglibMethodInvocation 继承了 ReflectiveMethodInvocation。其实, CglibMethodInvocation 也是通过调用父类方法完成 AOP 切面调用的。这里就不再贴代码赘述了。

4.5.3. 总结

最后,使用前面文章提到的“Aspect 应用流程”再来总结一下 Spring AOP 的调用过程:

Aspect 应用流程
Figure 13. Aspect 应用流程

4.6. PointcutAdvisor

Diagram

org.springframework.aop.PointcutAdvisor 才是真正的定义一个 Pointcut 和一个 AdviceAdvisor

DefaultPointcutAdvisor 是最通用的 PointcutAdvisor 实现。

NameMatchMethodPointcutAdvisor 是细化后的 DefaultPointcutAdvisor

IntroductionAdvisorPointcutAdvisor 最本质的区别是 IntroductionAdvisor 只能应用于类级别的拦截,只能使用 Introduction 型的 Advisor

Ordered 接口用于指定 Bean 的优先级。数字越小,优先级越高。

4.7. Spring AOP 的织入

ProxyFactory 是最基本的一个织入器实现。 ProxyFactory 只需要指定如下两个最基本的东西:

  1. 对其要进行织入的目标对象。

  2. 将要应用到目标对象的 Aspect。在 Spring 里面叫做 Advisor。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package com.diguage.truman.aop;

import org.junit.jupiter.api.Test;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.interceptor.PerformanceMonitorInterceptor;
import org.springframework.aop.support.NameMatchMethodPointcutAdvisor;

/**
 * @author D瓜哥, https://www.diguage.com
 */
public class ProxyFactoryTest {
	@Test
	public void testJdkProxy() {
		MockExecutable mockTask = new MockExecutable();
		ProxyFactory factory = new ProxyFactory(mockTask);
		factory.setInterfaces(Executable.class);
		NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
		advisor.setMappedName("execute");
		advisor.setAdvice(new PerformanceMonitorInterceptor());
		factory.addAdvisor(advisor);
		Executable proxyExecutable = (Executable) factory.getProxy();
		System.out.println(proxyExecutable.getClass());
		proxyExecutable.execute();
	}

	public interface Executable {
		void execute();
	}

	public static class MockExecutable implements Executable {
		@Override
		public void execute() {
			System.out.println("MockTask.execute");
		}
	}

	@Test
	public void testCglibProxy() {
		ProxyFactory factory = new ProxyFactory(new Task());
		NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
		advisor.setMappedName("execute");
		advisor.setAdvice(new PerformanceMonitorInterceptor());
		factory.addAdvisor(advisor);
		Task proxyTask = (Task) factory.getProxy();
		System.out.println(proxyTask.getClass());
		proxyTask.execute();
	}

	public static class Task {
		public void execute() {
			System.out.println("Task.execute");
		}
	}
}

Spring AOP 框架使用 AopProxy 对使用的不同的代理实现机制进行了适度的抽象,针对不同的代理实现机制提供相应的 AopProxy 子类实现。目前提供了针对 JDK 的动态代理和 CGLIB 字节码增强两种机制的 AopProxy 实现。

Diagram

不同 AopProxy 实现的实例化过程采用工厂模式(确切地说是抽象工厂模式)进行封装,即通过 org.springframework.aop.framework.AopProxyFactory 创建 AopProxy 实例。

AdvisedSupport 就是一个生成代理对象所需要的信息的载体。

Diagram

ProxyFactoryAopProxyAdvisedSupport 于一身,可以通过 ProxyFactory 设置生成代理对象所需要的相关信息,也可以通过 ProxyFactory 取得最终生成的代理对象。前者是 AdvisedSupport 的职责,后者是 AopProxy 的职责。

ProxyFactory 只是 Spring AOP 中最基本的织入器实现,还有其他的几个 "兄弟":

Diagram

Spring AOP 的自动代理的实现建立在 IoC 容器的 BeanPostProcessor 概念之上。只要提供一个 BeanPostProcessor,当对象实例化的时候,为其生成代理对象返回,而不是实例化后的目标对象本身,从而达到代理对象自动生成的目的。伪代码如下:

1
2
3
4
5
6
7
8
9
for (bean in container) {
    if (检查当前 bean 定义是否符合拦截条件如果符合拦截条件) {
        Object proxy = createProxyFor(bean);
        return proxy;
    } 否则 {
        Object instance = createInstance(bean);
        return instance;
    }
}

检查当前 bean 是否符合拦截条件,首先需要知道拦截条件是什么?我们通过某种方式,告知自动代理实现类都有哪些拦截条件:

  1. 通过外部配置文件传入;

  2. 在具体类的元数据来指明。

BeanNameAutoProxyCreator 通过指定一组容器内的目标对象对应的 beanNames,将指定的一组拦截器应用到这些目标对象之上。

DefaultAdvisorAutoProxyCreator 注册到容器后,它会自动搜寻容器内的所有 Advisor,然后根据各个 Advisor 所提供的拦截信息,为符合条件的容器中的目标对象生成相应的代理对象。为了避免将不必要的横切逻辑织入到不需要的目标对象之上,应该尽量细化各个 Advisor 的定义。

InstantiationAwareBeanPostProcessor 有 "短路" 功能。

4.8. TargetSource

TargetSource 的作用就好像是为目标对象在外面加了一个壳,或者说,它就像是目标对象的容器。当每个针对目标对象的方法调用经历层层拦截而到达调用链的终点的时候,就该调用目标对象上定义的方法了。但这时,Spring AOP 做了点儿手脚,它不是直接调用这个目标对象上的方法,而是通过“插足于”调用链与实际目标对象之间的某个 TargetSource 来取得具体目标对象,然后再调用从 TargetSource 中取得的目标对象上的相应方法。

TargetSource 调用流程

TargetSource 最主要的特性是,每次方法调用都会触发 TargetSourcegetTarget() 方法, getTarget() 方法将从相应的 TargetSource 实现类中取得具体的目标对象,这样就可以控制每次方法调用作用到的具体实例对象:①从目标对象池获取对象;②按照某种规则,选择返回被选中的目标对象实例。

SingletonTargetSource 内部只持有一个目标对象,每次调用时,都会返回这同一个目标对象。

PrototypeTargetSource 每次调用目标对象上的方法时,都会返回一个新的目标对象实例供调用。注意: scope 要设置成 prototype;通过 targetBeanName 属性指定目标对象的 bean 定义名称,而不是引用。

使用 HotSwappableTargetSource 封装目标对象,可以在应用程序运行时,根据某种特定条件,动态地替换目标对象类的具体实现。受此启发,可以实现一个根据配置动态切换数据源的透明数据源代理。代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
package com.diguage.truman.aop;

import lombok.Data;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.target.HotSwappableTargetSource;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import jakarta.annotation.Resource;
import javax.sql.DataSource;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.SQLException;

import static org.mockito.Mockito.when;

/**
 * 根据配置透明切换数据源的插件
 * <p>
 * https://afoo.me/posts/2005-08-10-improve-datasources-swap-solution.html
 *
 * @author D瓜哥, https://www.diguage.com
 * @since 2021-07-16 23:59:56
 */
public class HotSwappableTargetSourceXmlTest {
	private static final String PRIMARY_DATASOURCE = "primaryDatasource";
	private static final String SLAVE_DATASOURCE = "slaveDatasource";
	private static boolean usePrimary = true;

	@Test
	public void test() throws SQLException {
		ApplicationContext context
				= new ClassPathXmlApplicationContext(
				"classpath:com/diguage/truman/aop/HotSwappableTargetSource.xml");

		DataSource primaryDatasource = (DataSource) context.getBean(PRIMARY_DATASOURCE);
		DataSource slaveDatasource = (DataSource) context.getBean(SLAVE_DATASOURCE);

		when(primaryDatasource.getConnection())
				.thenReturn(Mockito.mock(Connection.class, "primaryConnection"));
		when(slaveDatasource.getConnection())
				.thenReturn(Mockito.mock(Connection.class, "slaveConnection"));

		DataSource dataSource = (DataSource) context.getBean("dataSource");

		System.out.println(dataSource.getConnection());
		usePrimary = !usePrimary;
		System.out.println(dataSource.getConnection());
		System.out.println(dataSource.getConnection());
		usePrimary = !usePrimary;
		System.out.println(dataSource.getConnection());
	}

	@Configuration
	@EnableAspectJAutoProxy
	public static class Config {
		@Bean(PRIMARY_DATASOURCE)
		public DataSource primaryDatasource() {
			return Mockito.mock(DataSource.class, PRIMARY_DATASOURCE);
		}

		@Bean(SLAVE_DATASOURCE)
		public DataSource slaveDatasource() {
			return Mockito.mock(DataSource.class, SLAVE_DATASOURCE);
		}
	}

	@Data
	public static class SwapDataSourceAdvice implements MethodBeforeAdvice {
		@Resource(name = PRIMARY_DATASOURCE)
		private DataSource primaryDatasource;

		@Resource(name = SLAVE_DATASOURCE)
		private DataSource slaveDatasource;

		@Resource
		private HotSwappableTargetSource targetSource;


		@Override
		public void before(Method method, Object[] args, Object target) throws Throwable {
			DataSource used = usePrimary ? primaryDatasource : slaveDatasource;
			targetSource.swap(used);
		}
	}
}

XML 配置文件如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xmlns:context="http://www.springframework.org/schema/context"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans
	           http://www.springframework.org/schema/beans/spring-beans.xsd
	           http://www.springframework.org/schema/context
	           https://www.springframework.org/schema/context/spring-context.xsd">

	<context:annotation-config/>

	<bean id="config"
		  class="com.diguage.truman.aop.HotSwappableTargetSourceXmlTest.Config"/>

	<bean id="hotSwapTarget"
		  class="org.springframework.aop.target.HotSwappableTargetSource">
		<constructor-arg ref="primaryDatasource"/>
	</bean>

	<bean id="swapDataSourceAdvice"
		  class="com.diguage.truman.aop.HotSwappableTargetSourceXmlTest$SwapDataSourceAdvice">
		<property name="primaryDatasource" ref="primaryDatasource"/>
		<property name="slaveDatasource" ref="slaveDatasource"/>
		<property name="targetSource" ref="hotSwapTarget"/>
	</bean>

	<bean id="swapDataSourceAdvisor"
		  class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
		<property name="advice" ref="swapDataSourceAdvice"/>
		<property name="pattern" value=".*getConnection.*"/>
	</bean>

	<bean id="dataSource"
		  class="org.springframework.aop.framework.ProxyFactoryBean">
		<property name="targetSource" ref="hotSwapTarget"/>
		<property name="interceptorNames">
			<list>
				<value>swapDataSourceAdvisor</value>
			</list>
		</property>
	</bean>

</beans>

TODO 改写成 JavaConfig 格式的

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package com.diguage.truman.aop;

import lombok.Data;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.TargetSource;
import org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor;
import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.aop.target.HotSwappableTargetSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

import jakarta.annotation.Resource;
import javax.sql.DataSource;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.SQLException;

import static org.mockito.Mockito.when;

/**
 * 根据配置透明切换数据源的插件
 * <p>
 * https://afoo.me/posts/2005-08-10-improve-datasources-swap-solution.html
 *
 * @author D瓜哥, https://www.diguage.com
 * @since 2021-07-16 23:59:56
 */
public class HotSwappableTargetSourceTest {
	private static final String PRIMARY_DATASOURCE = "primaryDatasource";
	private static final String SLAVE_DATASOURCE = "slaveDatasource";
	private static final String ADVISOR_NAME = "swapDataSourceAdvisor";
	private static boolean usePrimary = true;

	@Test
	public void test() throws SQLException {
		AnnotationConfigApplicationContext context
				= new AnnotationConfigApplicationContext();context.register(Config.class);
		context.refresh();

		DataSource primaryDatasource = (DataSource) context.getBean(PRIMARY_DATASOURCE);
		DataSource slaveDatasource = (DataSource) context.getBean(SLAVE_DATASOURCE);

		when(primaryDatasource.getConnection())
				.thenReturn(Mockito.mock(Connection.class, "primaryConnection"));
		when(slaveDatasource.getConnection())
				.thenReturn(Mockito.mock(Connection.class, "slaveConnection"));

		DataSource dataSource = (DataSource) context.getBean("dataSource");

		System.out.println(dataSource.getConnection());
		usePrimary = !usePrimary;
		System.out.println(dataSource.getConnection());
		System.out.println(dataSource.getConnection());
		usePrimary = !usePrimary;
		System.out.println(dataSource.getConnection());
	}

	@Configuration
	@EnableAspectJAutoProxy
	public static class Config {
		@Bean(PRIMARY_DATASOURCE)
		public DataSource primaryDatasource() {
			return Mockito.mock(DataSource.class, PRIMARY_DATASOURCE);
		}

		@Bean(SLAVE_DATASOURCE)
		public DataSource slaveDatasource() {
			return Mockito.mock(DataSource.class, SLAVE_DATASOURCE);
		}

		@Bean
		public HotSwappableTargetSource hotSwappableTargetSource(
				@Qualifier(PRIMARY_DATASOURCE) DataSource dataSource) {
			return new HotSwappableTargetSource(dataSource);
		}

		@Bean
		public SwapDataSourceAdvice swapDataSourceAdvice() {
			return new SwapDataSourceAdvice();
		}

//		/**
//		 * 使用 Spring 自制的 Advisor
//		 */
//		@Bean(ADVISOR_NAME)
//		public RegexpMethodPointcutAdvisor methodAdvisor(SwapDataSourceAdvice advice) {
//			RegexpMethodPointcutAdvisor advisor = new RegexpMethodPointcutAdvisor();
//			advisor.setAdvice(advice);
//			advisor.setPattern(".*getConnection.*");
//			return advisor;
//		}

		/**
		 * 使用 AspectJ 表达式的 Advisor
		 */
		@Bean(ADVISOR_NAME)
		public AspectJExpressionPointcutAdvisor methodAdvisor(SwapDataSourceAdvice advice) {
			AspectJExpressionPointcutAdvisor advisor
					= new AspectJExpressionPointcutAdvisor();
			advisor.setAdvice(advice);
			advisor.setExpression("target(javax.sql.DataSource) " +
					"&& execution(java.sql.Connection getConnection(..))");
			return advisor;
		}

		@Bean("dataSource")
		public ProxyFactoryBean getProxyFactoryBean(TargetSource targetSource) {
			ProxyFactoryBean result = new ProxyFactoryBean();
			result.setTargetSource(targetSource);
			result.setInterceptorNames(ADVISOR_NAME);
			return result;
		}
	}

	@Data
	public static class SwapDataSourceAdvice implements MethodBeforeAdvice {
		@Resource(name = PRIMARY_DATASOURCE)
		private DataSource primaryDatasource;

		@Resource(name = SLAVE_DATASOURCE)
		private DataSource slaveDatasource;

		@Resource
		private HotSwappableTargetSource targetSource;

		@Override
		public void before(Method method, Object[] args, Object target) throws Throwable {
			DataSource used = usePrimary ? primaryDatasource : slaveDatasource;
			targetSource.swap(used);
		}
	}
}

@Aspect 注解中的 value 属性不能随便填写,否则会报错。该注解解析过程见 org.aspectj.internal.lang.reflect.AjTypeImpl.getPerClause。这是 “Aspect Instantiation Models”。Spring 目前只支持 perthispertarget 两种模式,其他模式不支持。

CommonsPool2TargetSource 使用 Apache Commons Pool 2 来提供对象池的支持。

ThreadLocalTargetSource 为不同线程调用提供不同的目标对象。保证各自线程上对目标对象的调用,可以被分配到当前线程对应的那个目标对象实例上。注意: scope 要设置成 prototype

4.9. @AspectJ AOP

AspectJ AOP 的解析和处理是通过 org.springframework.aop.config.AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary 方法将所需要的 BeanPostProcessorAnnotationAwareAspectJAutoProxyCreator 注册到 Spring 容器中。查看源码可知,registerAspectJAnnotationAutoProxyCreatorIfNecessary 在两个地方被调 . 在处理 XML 的 <aop:aspectj-autoproxy> 标签时,通过 org.springframework.aop.config.AspectJAutoProxyBeanDefinitionParser.parse 方法来调用上述方法完成后置处理器的注册。 . 在处理 @EnableAspectJAutoProxy 注解时,该注解上还有注解 @Import(AspectJAutoProxyRegistrar.class),其中的 AspectJAutoProxyRegistrar 是一个 ImportBeanDefinitionRegistrar 的实现,那么在执行 AspectJAutoProxyRegistrar.registerBeanDefinitions 时,就可以调用上述方法,完成后置处理器的注册。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package com.diguage.truman.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.junit.jupiter.api.Test;
import org.springframework.aop.aspectj.annotation.AspectJProxyFactory;
import org.springframework.util.StopWatch;

/**
 * @author D瓜哥, https://www.diguage.com/
 * @since 2021-07-18 14:43:36
 */
@Slf4j
public class AspectAopManualTest {
	@Test
	public void test() {
		AspectJProxyFactory weaver = new AspectJProxyFactory();
		weaver.setProxyTargetClass(true);
		weaver.setTarget(new Foo());
		weaver.addAspect(PerformanceTraceAspect.class);
		Foo proxy = weaver.getProxy();
		proxy.method1();
		proxy.method2();
	}

	@Aspect
	public static class PerformanceTraceAspect {
		@Pointcut("execution(public void *.method1()) " +
				"|| execution(public void *.method2())")
		public void pointcutName() {
		}

		@Around("pointcutName()")
		public Object trace(ProceedingJoinPoint jp) throws Throwable {
			StopWatch watch = new StopWatch();
			try {
				watch.start();
				return jp.proceed();
			} finally {
				watch.stop();
				if (log.isInfoEnabled()) {
					log.info("PT in method[{}] {}",
							jp.getSignature().getName(), watch);
				}
			}
		}
	}

	public class Foo {
		public void method1() {
			log.info("method1 execution.");
		}

		public void method2() {
			log.info("method2 execution.");
		}
	}
}

@ApectJ 形式的 Pointcut 声明包含两个部分:

  • Pointcut Expression — 其载体为 @Pointcut,该注解时方法级别的注解,所以必须依附某个方法来声明。Pointcut Expression 附着于上的方法称为该 Pointcut Expression 的 Pointcut Signature。Pointcut Expression 是真正规定 Pointcut 匹配规则的地方。 @Pointcut 所指定的 AspectJ 形式的 Pointcut 表达式由下面两部分组成:

    • Pointcut 标志符(Pointcut Designator,Spring 文档中简称为“PCD”) — 标志符表情该 Pointcut 将以什么样的行为来匹配表达式。

    • 表达式匹配模式 — 在 Pointcut 标志符之内可以指定具体的匹配模式。

  • Pointcut Signature — 在这里具体化为一个方法定义,是 Pointcut Expression 的载体。返回值必须是 void

AspectJ 的 Pointcut 表达式支持通过 &&|| 以及 ! 逻辑运算符进行表达式直接的逻辑运算。

4.9.1. AspectJ 的 Pointcut 表达式支持的标志符

4.9.1.1. execution
1
2
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
throws-pattern?)

方法的返回值类型、方法名和参数部分的匹配模式必须指定,其他部分可选。

  • * — 可用于任何部分的匹配模式中,可以匹配多个相邻的字符。

  • .. — 可以用在两个地方:①在 declaring-type-pattern 规定的位置;②在方法参数匹配模式的位置。都是表示可以匹配多个。

4.9.1.2. within

within — 只接受类型声明,将会匹配指定类型下所有的 Joinpoint。Spring AOP 只支持方法级别的 Joinpoint,所以也只会匹配所声明的所有方法执行。也可以使用通配符。

4.9.1.3. thistarget

在 AspectJ 中,this 指代调用方法一方所在的对象(caller);target 指代被调用方法所在的对象 (callee)。这样通常可以同时使用这两个标志符限定方法的调用关系。比如,如果 Object1Object2 都会调用 Object3 的某个方法。那么,Pointcut 表达式定义 this(Object2) && target(Object3) 只会当 Object2 调用 Object3 上的方法的时候才会匹配,而 Object1 调用 object3 上的方法则不会被匹配。

Spring AOP 中的 thistarget 标志符语义,有别于 AspectJ 中这两个标志符的原始语义。现在,this 指代的是目标对象的代理对象,而 target 如其名般指代的就是目标对象。如果使用 this(ObjectType) 作为 Pointcut 定义,那么当目标对象的代理对象是 ObjectType 类型的时候,该 Pointcut 定义将匹配 ObjectType 类型中所有的 Joinpoint。在 Spring AOP 中,也就是 ObjectType 中定义的所有方法。而使用 target(ObjectType) 作为 Pointcut 定义,当目标对象是 ObjectType 类型的时候该 Pointcut 定义将匹配 ObjectType 型目标对象内定义的所有 Joinpoint。在 Spring AOP 中,当然还是所有的方法执行。

实际上,从代理模式来看,代理对象通常跟目标对象的类型是相同的,因为目标对象与它的代理对象实现同一个接口。即使使用 CGLIB 的方式,目标对象的代理对象属于目标对象的子类型,通过单独使用 this 或者 target 指定类型,起到的限定作用其实是差不多的。假设我们有对象定义,如下:

1
2
3
4
5
6
7
public interface ProxyInterface {
    // ...
}

public class TargetFoo implements ProxyInterface {
    // ...
}

不论使用基于接口的代理方式,还是其于类的代理方式,如下两个 Pointcut 表达式所起的作用实际上是差不多的:

1
2
3
4
5
// `this` 指代的是目标对象的代理对象。当目标对象的代理对象是 `ProxyInterface` 类型时,则匹配。
this(ProxyInterface)

// `target` 指代的就是目标对象。当目标对象是 `ProxyInterface` 类型时,则匹配。
target(ProxyInterface)

因为 TargetFoo 作为目标对象实现了 ProxyInterface。对基于接口的代理来说,它的代理对象同样实现了这个接口。对于基于类的代理来说,因为目标时象的代理对象是继承了目标对象,自然也继承了目标对象实现的接口。所以,在这里,这两个 Pointcut 定义起得作用差不多。如果通过 thistarget 指定具体类型,会怎么样呢?如下所示:

1
2
3
4
5
// `this` 指代的是目标对象的代理对象。当目标对象的代理对象是 `TargetFoo` 类型时,则匹配。
this(TargetFoo)

// `target` 指代的就是目标对象。当目标对象是 `TargetFoo` 类型时,则匹配。
target(TargetFoo)

这时,对于基于接口的代理和基于类的代理来说,效果就不同了。对于前者来说, target(TargetFoo) 可以匹配目标对象中的所有 Joinpoint,因为目标对象确实是 TargetFoo 类型,而 this(TargetFoo) 则不可以。此时,这两个标志符出现分歧了。不过,对于后者,即基于类的代理来说,这两个 Pointcut 表达式限定的语文还是基本相同的。

通常,thistarget 标志符都是在 Pointcut 表达式中与其他标志符结合使用,以进一步加强匹配的限定规则,比如:

1
2
3
4
5
execution(void com.diguage.*.doSomething(*)) && this(TargetFoo)

或者

execution(void com.diguage.*.doSomething(*)) && target(TargetFoo)

当然,我们也可以将 thistarget 结合到一个 Pointcut 定义中。这样,在目标对象和代理对象关注的类型不同的时候,可以达到严格匹配规则的目的。为了说明这一点,让我们的 TargetFoo 定义再多实现一个接口吧!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public interface ProxyInterface {
    // ...
}

public interface ProxyInterface2 {
    // ...
}

public class TargetFoo implements ProxyInterface, ProxyInterface2 {
    // ...
}

现在,定义 Pointcut this(ProxyInterface) && target(ProxyInterface2),当为目标对象生成代理对象时,我们声明只对 ProxyInterface 接口进行代理,那么使用以上 Pointcut 表达式可以匹配 TargetFoo。如果还有其他的 ProxyInterface 实现类,但是它们没有同时实现 ProxyInterface2,那么这些其他的 ProxyInterface 实现类则不会被匹配。

写个例子代码,来印证上面的结论:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package com.diguage.truman.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.*;
import org.springframework.core.type.AnnotationMetadata;

/**
 * 验证 this 的匹配规则。
 *
 * @author D瓜哥, https://www.diguage.com/
 * @since 2021-07-19 21:27:47
 */
@Slf4j
public class AspectThisInterfaceTest {
	@Test
	public void test() {
		AnnotationConfigApplicationContext context
				= new AnnotationConfigApplicationContext();
		context.register(Config.class);
		context.refresh();

		// TODO 竟然不能写成 Rabbit.class。 为什么?
		Movable bean = context.getBean(Movable.class);
		bean.move();
	}

	@Configuration
	@Import(ThisImportSelector.class)
	@EnableAspectJAutoProxy // 注意:这行必须加
	public static class Config {
	}

	public static class ThisImportSelector implements ImportSelector {
		@Override
		public String[] selectImports(AnnotationMetadata metadata) {
			return new String[]{
					Rabbit.class.getName(),
					ThisAspect.class.getName()
			};
		}
	}

	public interface Movable {
		void move();
	}

	public static class Rabbit implements Movable {
		@Override
		public void move() {
			log.info("Rabbit.move executing...");
		}
	}

	@Aspect
	public static class ThisAspect {
		@Pointcut("this(com.diguage.truman.aop.AspectThisInterfaceTest.Movable)")
		public void doThisInterface() {
		}

		@Around("doThisInterface()")
		public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
			String typeName = joinPoint.getSignature().getDeclaringTypeName();
			String methodName = joinPoint.getSignature().getName();
			try {
				return joinPoint.proceed();
			} finally {
				log.info("aspect executing. pointcut={}.{}",
						typeName, methodName);
			}
		}
	}
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package com.diguage.truman.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.*;
import org.springframework.core.type.AnnotationMetadata;

/**
 * 验证 target 的匹配规则。
 *
 * @author D瓜哥, https://www.diguage.com/
 * @since 2021-07-19 23:28:27
 */
@Slf4j
public class AspectTargetInterfaceTest {
	@Test
	public void test() {
		AnnotationConfigApplicationContext context
				= new AnnotationConfigApplicationContext();
		context.register(Config.class);
		context.refresh();

		Movable bean = context.getBean(Movable.class);
		bean.move();
	}

	@Configuration
	@Import(TargetImportSelector.class)
	@EnableAspectJAutoProxy // 注意:这行必须加
	public static class Config {
	}

	public static class TargetImportSelector implements ImportSelector {
		@Override
		public String[] selectImports(AnnotationMetadata metadata) {
			return new String[]{
					Rabbit.class.getName(),
					TargetAspect.class.getName()
			};
		}
	}

	public interface Movable {
		void move();
	}

	public static class Rabbit implements Movable {
		@Override
		public void move() {
			log.info("Rabbit.move executing...");
		}
	}

	@Aspect
	public static class TargetAspect {
		@Pointcut("target(com.diguage.truman.aop.AspectTargetInterfaceTest.Movable)")
		public void doTargetInterface() {
		}

		@Around("doTargetInterface()")
		public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
			String typeName = joinPoint.getSignature().getDeclaringTypeName();
			String methodName = joinPoint.getSignature().getName();
			try {
				return joinPoint.proceed();
			} finally {
				log.info("aspect executing. pointcut={}.{}",
						typeName, methodName);
			}
		}
	}
}

从上面两个例子可以看出:对于 this(Interface)target(Interface) 来说,两个的效果是一样的,印证了上面的总结。

再来看看对 this(InterfaceImpl)target(InterfaceImpl) 的情况对比:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
package com.diguage.truman.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.*;
import org.springframework.core.type.AnnotationMetadata;

/**
 * 验证 this 的匹配规则。
 *
 * @author D瓜哥, https://www.diguage.com/
 * @since 2021-07-19 21:27:47
 */
@Slf4j
public class AspectThisClassTest {
	@Test
	public void test() {
		AnnotationConfigApplicationContext context
				= new AnnotationConfigApplicationContext();
		context.register(Config.class);
		context.refresh();

		// TODO 竟然不能写成 Rabbit.class。 为什么?
		Movable bean = context.getBean(Movable.class);
		bean.move();
	}

	/**
	 * 如果 (proxyTargetClass = true),则切面会执行;
	 * 如果 (proxyTargetClass = false)(默认如此),则切面会执行。
	 */
	@Configuration
	@Import(ThisImportSelector.class)
	@EnableAspectJAutoProxy //(proxyTargetClass = true)
	public static class Config {
	}

	public static class ThisImportSelector implements ImportSelector {
		@Override
		public String[] selectImports(AnnotationMetadata metadata) {
			return new String[]{
					Rabbit.class.getName(),
					ThisAspect.class.getName()
			};
		}
	}

	public interface Movable {
		void move();
	}

	public static class Rabbit implements Movable {
		@Override
		public void move() {
			log.info("Rabbit.move executing...");
		}
	}

	@Aspect
	public static class ThisAspect {
		@Pointcut("this(com.diguage.truman.aop.AspectThisClassTest.Rabbit)")
		public void doThisInterface() {
		}

		@Around("doThisInterface()")
		public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
			String typeName = joinPoint.getSignature().getDeclaringTypeName();
			String methodName = joinPoint.getSignature().getName();
			try {
				return joinPoint.proceed();
			} finally {
				log.info("aspect executing. pointcut={}.{}",
						typeName, methodName);
			}
		}
	}
}

从上面的例子可以看出:对于 this(InterfaceImpl) 来说,

  1. 如果 (proxyTargetClass = true),则切面会执行;

  2. 如果 (proxyTargetClass = false)(默认如此),则切面会执行。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package com.diguage.truman.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.*;
import org.springframework.core.type.AnnotationMetadata;

/**
 * 验证 target 的匹配规则。
 *
 * @author D瓜哥, https://www.diguage.com/
 * @since 2021-07-19 23:28:27
 */
@Slf4j
public class AspectTargetClassTest {
	@Test
	public void test() {
		AnnotationConfigApplicationContext context
				= new AnnotationConfigApplicationContext();
		context.register(Config.class);
		context.refresh();

		Movable bean = context.getBean(Movable.class);
		bean.move();
	}

	/**
	 * 无论 (proxyTargetClass = true/false),切面都会执行
	 */
	@Configuration
	@Import(TargetImportSelector.class)
	@EnableAspectJAutoProxy
	public static class Config {
	}

	public static class TargetImportSelector implements ImportSelector {
		@Override
		public String[] selectImports(AnnotationMetadata metadata) {
			return new String[]{
					Rabbit.class.getName(),
					TargetAspect.class.getName()
			};
		}
	}

	public interface Movable {
		void move();
	}

	public static class Rabbit implements Movable {
		@Override
		public void move() {
			log.info("Rabbit.move executing...");
		}
	}

	@Aspect
	public static class TargetAspect {
		@Pointcut("target(com.diguage.truman.aop.AspectTargetClassTest.Rabbit)")
		public void doTargetInterface() {
		}

		@Around("doTargetInterface()")
		public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
			String typeName = joinPoint.getSignature().getDeclaringTypeName();
			String methodName = joinPoint.getSignature().getName();
			try {
				return joinPoint.proceed();
			} finally {
				log.info("aspect executing. pointcut={}.{}",
						typeName, methodName);
			}
		}
	}
}

从上面的例子可以看出:对于 target(InterfaceImpl) 来说,无论 (proxyTargetClass = true/false),切面都会执行。

TODO 稀里糊涂,做实验验证一下!

4.9.1.4. args

args — 帮助我们捕捉拥有指定参数类型、指定参数数量的方法级 Joinpoint,而不管该方法在什么类型中声明。这个检查是动态检查,即使参数声明的是 Object,但实际是目标类型,依然可以匹配上。但是如果是 execution(* *(TargetType)),则无法捕捉到声明为父类,但实际是 TargetType 的 Joinpoint。

4.9.1.5. @within

@within — 指定注解类型,并且目标对象类型使用了该注解,则对目标对象内部所有 Joinpoint 生效。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
package com.diguage.truman.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.*;
import org.springframework.core.type.AnnotationMetadata;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author D瓜哥, https://www.diguage.com/
 * @since 2021-07-18 21:18:43
 */
@Slf4j
public class AspectWithinTest {

	@Test
	public void test() {
		AnnotationConfigApplicationContext context
				= new AnnotationConfigApplicationContext();
		context.register(Config.class);
		context.refresh();

		DiguageTask task = context.getBean(DiguageTask.class);
		task.run();
	}

	@Configuration
	@Import(WithinImportSelector.class)
	@EnableAspectJAutoProxy // 注意:这行必须加
	public static class Config {
	}

	public static class WithinImportSelector implements ImportSelector {
		@Override
		public String[] selectImports(AnnotationMetadata importingClassMetadata) {
			return new String[]{
					DiguageTask.class.getName(),
					WithinTypeAspect.class.getName(),
					WithinMethodAspect.class.getName()
			};
		}
	}

	@WithinTypeAnnotation
	public static class DiguageTask {
		@WithinMethodAnnotation
		public void run() {
			log.info("Diguage.run executing...");
		}
	}

	@Retention(RetentionPolicy.RUNTIME)
	@Target(ElementType.TYPE)
	public @interface WithinTypeAnnotation {
	}

	@Aspect
	public static class WithinTypeAspect {
		@Pointcut("@within(com.diguage.truman.aop." +
				"AspectWithinTest.WithinTypeAnnotation)")
		public void withinType() {
		}

		@Around("withinType()")
		public Object around(ProceedingJoinPoint jp) throws Throwable {
			try {
				return jp.proceed();
			} finally {
				log.info("WithinTypeAspect executing...");
			}
		}
	}

	@Retention(RetentionPolicy.RUNTIME)
	@Target(ElementType.METHOD)
	public @interface WithinMethodAnnotation {
	}

	@Aspect
	public static class WithinMethodAspect {
		@Pointcut("@within(com.diguage.truman.aop." +
				"AspectWithinTest.WithinMethodAnnotation)")
		public void withinMethod() {
		}

		/**
		 * 这里的代码没有执行到,说明对于 @Within 注解来说,只支持类注解。
		 */
		@Around("withinMethod()")
		public Object around(ProceedingJoinPoint jp) throws Throwable {
			try {
				return jp.proceed();
			} finally {
				log.info("WithinTypeAspect executing...");
			}
		}
	}
}

从上面的代码可以看出, @within 只支持类注解。不支持方法注解。

4.9.1.6. @target

@target — 指定注解类型,并且目标对象类型使用了该注解,则对目标对象内部所有 Joinpoint 生效。与 @within 没有太大区别。 @within 属于静态匹配,而 @target 则是在运行时动态匹配 Joinpoint。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
package com.diguage.truman.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.*;
import org.springframework.core.type.AnnotationMetadata;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 验证 @target 的匹配规则。
 *
 * TODO 如何验证其动态匹配的特性?
 *
 * @author D瓜哥, https://www.diguage.com/
 * @since 2021-07-18 21:18:43
 */
@Slf4j
public class AspectAnnoTargetTest {
	@Test
	public void test() {
		AnnotationConfigApplicationContext context
				= new AnnotationConfigApplicationContext();
		context.register(Config.class);
		context.refresh();

		DiguageTask task = context.getBean(DiguageTask.class);
		task.run();
	}

	@Configuration
	@Import(TargetImportSelector.class)
	@EnableAspectJAutoProxy // 注意:这行必须加
	public static class Config {
	}

	public static class TargetImportSelector implements ImportSelector {
		@Override
		public String[] selectImports(AnnotationMetadata metadata) {
			return new String[]{
					DiguageTask.class.getName(),
					TypeAspect.class.getName(),
					MethodAspect.class.getName()
			};
		}
	}

	@TypeAnnotation
	public static class DiguageTask {
		@MethodAnnotation
		public void run() {
			log.info("Diguage.run executing...");
		}
	}

	@Retention(RetentionPolicy.RUNTIME)
	@Target(ElementType.TYPE)
	public @interface TypeAnnotation {
	}

	@Aspect
	public static class TypeAspect {
		@Pointcut("@target(com.diguage.truman.aop." +
				"AspectAnnoTargetTest.TypeAnnotation)")
		public void doType() {
		}

		@Around("doType()")
		public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
			try {
				return joinPoint.proceed();
			} finally {
				log.info("TypeAspect executing...");
			}
		}
	}

	@Retention(RetentionPolicy.RUNTIME)
	@Target(ElementType.METHOD)
	public @interface MethodAnnotation {
	}

	@Aspect
	public static class MethodAspect {
		@Pointcut("@target(com.diguage.truman.aop." +
				"AspectAnnoTargetTest.MethodAnnotation)")
		public void doMethod() {
		}

		/**
		 * 这里的代码没有执行到,说明对于 @target 注解来说,只支持类注解。
		 */
		@Around("doMethod()")
		public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
			try {
				return joinPoint.proceed();
			} finally {
				log.info("MethodAspect executing...");
			}
		}
	}
}

从上面的代码可以看出, @target 只支持类注解。不支持方法注解。

TODO 如何验证其动态匹配的特性?如何做实验验证一下 @target@within 的区别?

4.9.1.7. @args

@args — 尝试检查当前方法级的 Joinpoint 的方法参数类型。如果参入的参数类型拥有 @args 所指定的注解,当前 Joinpoint 将被匹配,否则将不会被匹配。

AspectJ @args 处理逻辑
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
package com.diguage.truman.aop;

import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.*;
import org.springframework.core.type.AnnotationMetadata;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Arrays;

/**
 * 验证 @args 的匹配规则。
 *
 * @author D瓜哥, https://www.diguage.com/
 * @since 2021-07-18 21:18:43
 */
@Slf4j
public class AspectAnnoArgsTest {

	@Test
	public void test() {
		AnnotationConfigApplicationContext context
				= new AnnotationConfigApplicationContext();
		context.register(Config.class);
		context.refresh();

		DiguageTask task = context.getBean(DiguageTask.class);
		AnnoParam annoParam = new AnnoParam("AnnoParam");
		task.run(annoParam);

		NonAnnoParam nonAnnoParam = new NonAnnoParam("NonAnnoParam");
		task.run(nonAnnoParam);
	}

	@Configuration
	@Import(ArgsImportSelector.class)
	@EnableAspectJAutoProxy // 注意:这行必须加
	public static class Config {
	}

	public static class ArgsImportSelector implements ImportSelector {
		@Override
		public String[] selectImports(AnnotationMetadata metadata) {
			return new String[]{
					DiguageTask.class.getName(),
					TypeAspect.class.getName()
			};
		}
	}

	public static class DiguageTask {
		public void run(Object param) {
			log.info("Diguage.run executing.params[{}]", param);
		}
	}

	@Data
	@NoArgsConstructor
	@TypeAnnotation
	public static class AnnoParam {
		private String name;

		public AnnoParam(String name) {
			this.name = name;
		}
	}

	/**
	 * 这个参数调用时,没有执行增强,所以只会对有注解的参数进行拦截。
	 */
	@Data
	@NoArgsConstructor
	public static class NonAnnoParam {
		private String name;

		public NonAnnoParam(String name) {
			this.name = name;
		}
	}

	@Retention(RetentionPolicy.RUNTIME)
	@Target(ElementType.TYPE)
	public @interface TypeAnnotation {
	}

	@Aspect
	public static class TypeAspect {
		@Pointcut("@args(com.diguage.truman.aop." +
				"AspectAnnoArgsTest.TypeAnnotation)")
		public void doType() {
		}

		@Around("doType()")
		public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
			Object[] args = null;
			try {
				args = joinPoint.getArgs();
				return joinPoint.proceed();
			} finally {
				log.info("TypeAspect executing. params[{}]", Arrays.toString(args));
			}
		}
	}
}

@args 会尝试对系统中所有对象的每次方法执行的参数,都进行指定的注解的动态检查。只要参数类型标注有 @args 指定的注解类型,当前方法执行就将匹配,至于说参数类型是什么,则不是十分关心。

4.9.1.8. @annotation
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
package com.diguage.truman.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.*;
import org.springframework.core.type.AnnotationMetadata;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author D瓜哥, https://www.diguage.com/
 * @since 2021-07-18 23:40:42
 */
@Slf4j
public class AspectAnnotationTest {

	@Test
	public void test() {
		AnnotationConfigApplicationContext context
				= new AnnotationConfigApplicationContext();
		context.register(Config.class);
		context.refresh();

		DiguageTask task = context.getBean(DiguageTask.class);
		task.run();
	}

	@Configuration
	@Import(AnnoImportSelector.class)
	@EnableAspectJAutoProxy // 注意:这行必须加
	public static class Config {
	}

	public static class AnnoImportSelector implements ImportSelector {
		@Override
		public String[] selectImports(AnnotationMetadata metadata) {
			return new String[]{
					DiguageTask.class.getName(),
					AnnoTypeAspect.class.getName(),
					AnnoMethodAspect.class.getName()
			};
		}
	}

	@AnnoTypeAnnotation
	public static class DiguageTask {
		@AnnoMethodAnnotation
		public void run() {
			log.info("Diguage.run executing...");
		}
	}

	@Retention(RetentionPolicy.RUNTIME)
	@Target(ElementType.METHOD)
	public @interface AnnoMethodAnnotation {
	}

	@Aspect
	public static class AnnoMethodAspect {
		@Pointcut("@annotation(com.diguage.truman.aop." +
				"AspectAnnotationTest.AnnoMethodAnnotation)")
		public void annoMethod() {
		}

		@Around("annoMethod()")
		public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
			try {
				return joinPoint.proceed();
			} finally {
				log.info("AnnoMethodAspect.annoMethod executing...");
			}
		}
	}


	@Retention(RetentionPolicy.RUNTIME)
	@Target(ElementType.TYPE)
	public @interface AnnoTypeAnnotation {
	}

	@Aspect
	public static class AnnoTypeAspect {
		@Pointcut("@annotation(com.diguage.truman.aop." +
				"AspectAnnotationTest.AnnoTypeAnnotation)")
		public void annoType() {
		}

		/**
		 * 这里的代码没有执行到,说明对于 @annotation 注解来说,只支持方法注解。
		 */
		@Around("annoType()")
		public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
			try {
				return joinPoint.proceed();
			} finally {
				log.info("AnnoTypeAspect.annoType executing...");
			}
		}
	}
}

从这个例子可以看出, @annotation 注解来说,只支持方法注解。这点上,跟 @within 正好相反。

TODO @Transactional 既支持方法,又支持类,它是怎么筛选 Joinpoint 的?

4.9.1.9. bean(idOrNameOfBean)

bean(idOrNameOfBean) — Spring 特有的,不是 AspectJ 内置支持的。可以增强指定的 Bean 中的所有方法。另外,支持简单的 * 通配符,这样可以分批筛选,比如 bean(*Service) 筛选所有的 Service 类,前提是有一个规范的命名。

4.9.2. @AspectJ 形式的 Advice

@AfterThrowing 有一个独特的属性,即 throwing,通过它,可以限定 Advice 定义方法的参数名,并在方法调用的时候,将相应的异常绑定到具体方法参数上。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package com.diguage.truman.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.*;
import org.springframework.core.type.AnnotationMetadata;

import java.util.Arrays;

/**
 * 验证 @AfterThrowing 的属性 throwing
 *
 * @author D瓜哥, https://www.diguage.com/
 * @since 2021-07-19 23:28:27
 */
@Slf4j
public class AspectAfterThrowingTest {
	@Test
	public void test() {
		AnnotationConfigApplicationContext context
				= new AnnotationConfigApplicationContext();
		context.register(Config.class);
		context.refresh();

		Movable bean = context.getBean(Movable.class);
		bean.move("Henan");
	}

	@Configuration
	@Import(AspectImportSelector.class)
	@EnableAspectJAutoProxy
	public static class Config {
	}

	public static class AspectImportSelector implements ImportSelector {
		@Override
		public String[] selectImports(AnnotationMetadata metadata) {
			return new String[]{
					Rabbit.class.getName(),
					AfterThrowingAspect.class.getName()
			};
		}
	}

	public interface Movable {
		void move(String target);
	}

	public static class Rabbit implements Movable {
		@Override
		public void move(String target) {
			log.info("Rabbit.move executing...");
			throw new RuntimeException("Rabbit throws an error.");
		}
	}

	@Aspect
	public static class AfterThrowingAspect {
		@AfterThrowing(pointcut = "execution(void *.move(String, ..))",
				throwing = "e")
		public void afterThrowing(JoinPoint joinPoint, RuntimeException e) {
			Object[] args = joinPoint.getArgs();
			log.error("Target threw an error. args={}",
					Arrays.toString(args), e);
		}
	}
}

@AfterReturning 可以通过 returning 属性,将返回值绑定到 After Returning Advice 定义所在的方法。

After(Finally) Advice 通过 @After 来标注相应的方法,无论标注的方法,无论是正常返回,还是抛出异常,都会触发执行。所以,比较时候用于释放某些系统资源的场景。

Around Advice 定义的方法的第一个参数必须是 ProceedingJoinPoint,通常需要通过 proceedingJoinPoint.proceed() 方法继续调用链的执行。其他 Advice 定义方法的第一个参数是可选的 JoinPoint,即可有可无。

AOP 调试实例:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package com.diguage.truman.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.junit.jupiter.api.Test;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

import jakarta.annotation.Resource;

/**
 * @author D瓜哥, https://www.diguage.com/
 * @since 2020-06-02 11:12
 */
public class AopTest {
	@Test
	public void test() {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
		context.register(Config.class);
		context.refresh();
		UserService bean = context.getBean(UserService.class);
		bean.test();
		bean.getDesc();
		bean.setDesc("This is a test.");

		String user = bean.getById(119);
		System.out.println(user);

		BeanDefinition definition = context.getBeanDefinition(UserService.class.getName());
		System.out.println(definition);
	}

	@Configuration
	@Import(AopImportSelector.class)
	@EnableAspectJAutoProxy(exposeProxy = true)
	public static class Config {
	}

	// 使用 @Import 和 ImportSelector 搭配,就可以省去 XML 配置
	public static class AopImportSelector implements ImportSelector {
		@Override
		public String[] selectImports(AnnotationMetadata importingClassMetadata) {
			return new String[]{
					UserDao.class.getName(),
					UserService.class.getName(),
					TestAspect.class.getName()
			};
		}
	}

	@Aspect
	public static class TestAspect {
		@Pointcut("execution(* com.diguage.truman.aop.AopTest$UserService.test(..))")
		public void test() {
		}

		@Before("test()")
		public void beforeTest() {
			System.out.println("beforeTest");
		}

		@After("test()")
		public void afterTest() {
			System.out.println("afterTest");
		}

		@Around("test()")
		public Object aroundTest(ProceedingJoinPoint pjp) {
			System.out.println("aroundBefore1");
			Object restul = null;
			Signature signature = pjp.getSignature();
			System.out.println(pjp.getKind());
			Object target = pjp.getTarget();
			System.out.println(target.getClass().getName() + "#" + signature.getName());
			try {
				restul = pjp.proceed();
			} catch (Throwable throwable) {
				throwable.printStackTrace();
			}
			System.out.println("aroundAfter1");
			return restul;
		}
	}

	public static class UserDao {
		public String getById(int id) {
			return "diguage-" + id;
		}
	}

	public static class UserService {
		private String desc = "testBean";

		@Resource
		private UserDao userDao;

		public String getDesc() {
			System.out.println("getDesc");
			this.test();
			System.out.println("--this----------getDesc");
			return desc;
		}

		public void setDesc(String desc) {
			this.desc = desc;
			// 使用 @EnableAspectJAutoProxy(exposeProxy = true) 打开 exposeProxy = true
			// 则必须这样写,才能获取到当前的代理对象,然后调用的方法才是被 AOP 处理后的方法。
			// 使用 this.methodName() 调用,依然调用的是原始的、未经 AOP 处理的方法
			((UserService) AopContext.currentProxy()).test();
			System.out.println("--AopContext----setDesc");
		}

		public void test() {
			System.out.println("----------------test");
		}

		public String getById(int id) {
			return userDao.getById(id);
		}
	}
}
Diagram

ComposablePointcut 是 Spring AOP 提供可以进行 Pointcut 逻辑运算的 Pointcut 实现。

ControlFlowPointcut 是在某个类调动时拦截,其他类调用时不调用。每次运行都需要做检查,性能差,慎重选择。

感觉 AdvisorAdvice 表示的意思是一样的。不一样的是 Advisor 是 Spring 自己创建的; Advice 是 AOP Alliance 定义的。两者的包不一样。

ReflectiveMethodInvocation.currentInterceptorIndex 如果一个实例被多次调用,怎么来维护这个属性?每次调用方法,都会创建一个 CglibMethodInvocation 或者 ReflectiveMethodInvocation,这样每次 currentInterceptorIndex 属性都是从初始化开始的。

使用 JDK 动态代理生成代理类时,如果多个接口,生成代理时,如何选择?没有被选中的接口怎么办?

expose-proxy = true 使用 UserService) AopContext.currentProxy( 代替 this,就可以获取代理类。

多个 AOP 增强怎么处理?

SyntheticInstantiationAdvisor 是干什么的?

BeanDefinition 中有 RuntimeBeanReference 属性时,就会触发 org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary,从 populateBeanapplyPropertyValuesresolveValueIfNecessary

跟踪代码发现, RuntimeBeanReference 类型的依赖关系最后会像 dependsOn 属性那样,注册到 dependentBeanMapdependenciesForBeanMap 属性中。

com.alibaba.spring.beans.factory.annotation.EnableConfigurationBeanBindings 这是什么鬼?干啥的? — 将基于proprieties 文件的配置项和对应的 Bean 建立起关联关系。

这是什么?

AOP 如何实现几种不同的通知的?怎么一步一步调用下去?怎么保存现场?

Spring 中不同 Aspect 直接是如何排序的?如何实现在前置通知和后置通知在正确位置执行?

4.10. AOP 应用案例

4.10.1. 异常处理

在 “Effective Java Exceptions( Part 1Part 2Part 3)” 中,作者将 unchecked exception 对应的情况称之为 Fault,而将 checked exception 对应的情况称之为 Contingency。而 Fault Barrier 要处理的就是对应的 Fault 的情况,即 unchecked exception。

Java 异常继承体系

可以实现一个对应 Fault 处理的 Aspect,让其对系统中的所有可能的 Fault 情况进行统一处理。这个专职于处理 Fault 的 Aspect,就称之为 Fault Barrier。

4.10.2. 安全检查

安全检查属于系统的一种横切点。将系统中可能需要安全检查的点排查清楚后,可以为这些点织入安全检查的逻辑。 Spring Security 就是一个功能完备的企业级安全检查框架。

4.10.3. 缓存

org.springframework.cache.CacheManager 提供了一个缓存管理框架。还有 @Cacheable@CacheConfig@CacheEvict@CachePut@Caching 等支持缓存的相关注解。另外,可以通过 @EnableCaching 启用缓存功能。

A Guide To Caching in Spring 中有相关介绍。

Integration: 8. Cache Abstraction 中有相关文档的详细说明。

4.11. AOP 失效的问题

如果在同一个类中,直接相互调用方法,那么相关上面的切面只会在第一层存在,被调用方法的切面就会失效。可以使用 AopContext.currentProxy() 获取代理实例来调用相关方法。

这个问题是由 Spring AOP 的实现机制导致的。如果像 AspectJ 直接将横切逻辑织入目标对象,将代理对象和目标对象合二为一,调用就不会出现这个问题了。

4.12. 瞎扯模板方法

模板方法模式中,会把公共部分提取到父类,一些细微的差别放在子类来实现。但是,如果不存在继承关系的类之间怎么重用这些共用代码?这就是 AOP 所需要解决的问题。

那么,这些问题怎么解决呢?如果接口是共用的,那么就可以使用代理模式。但是,静态代理模式的弊端就是需要各种子类,如果多层代理中的顺序发生改变,则对象的创建过程也需要跟着改变。为了解决这个问题,动态代理上场了。

从这个角度来说,D瓜哥觉得是可以从模板方法模式演进出代理模式。当然,这样搞就有点“曲线救国”,只是绕的道有点远。😆

4.13. 代理模式

4.14. 动态代理

4.15. AspectJ 介绍

5. 数据访问

Diagram

5.1. JdbcTemplate

SQLExceptionTranslator 是一个接口,如果你需要在 SQLExceptionorg.springframework.dao.DataAccessException 之间作转换,那么必须实现该接口。

转换器类的实现可以采用一般通用的做法(比如使用 JDBC 的 SQLState code),如果为了使转换更准确,也可以进行定制(比如使用 Oracle 的 error code)。

SQLErrorCodeSQLExceptionTranslatorSQLExceptionTranslator 的默认实现。该实现使用指定数据库厂商的 error code,比采用 SQLState 更精确。转换过程基于一个 JavaBean ( 类 型 为 SQLErrorCodes ) 中 的 error code 。

这 个 JavaBean 由 SQLErrorCodesFactory 工厂类创建,其中的内容来自于 sql-error-codes.xml 配置文 件 。

该文件中的数据库厂商代码基于 Database MetaData 信息中的 DatabaseProductName,从而配合当前数据库的使用。

SQLErrorCodeSQLExceptionTranslator 使用以下的匹配规则:首先检查是否存在完成定制转换的子类实现 。通常 SQLErrorCodeSQLExceptionTranslator 这个类可以作为一个具体类使用,不需要进行定制,那么这个规则将不适用。 接着将 SQLException 的 error code 与错误代码集中的 error code 进行匹配。默认情况下错误代码集将从 SQLErrorCodesFactory 取得。错误代码集来自 classpath 下的 sql-error-codes.xml 文件,它们将与数据库 metadata 信息中的 database name 进行映射。

5.1.1. 基于 AbstractRoutingDataSource 的主从切换

可以考虑使用 AbstractRoutingDataSource 做一个透明主从切换代理:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package com.diguage.truman.jdbc;

import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

/**
 * RoutingDataSource 测试类
 *
 * @author D瓜哥, https://www.diguage.com/
 * @since 2022-02-04 22:57:14
 */
public class RoutingDataSourceTest {

	private static final Logger log = LoggerFactory.getLogger(RoutingDataSourceTest.class);

	private static volatile boolean isMaster = true;

	private static final String MASTER_PREFIX = "master";
	private static final String SLAVE_PREFIX = "slave";

	private static final String MASTER_DATA_SOURCE_NAME = "masterDataSource";
	private static final String SLAVE_DATA_SOURCE_NAME = "slaveDataSource";

	// TODO 在注解中使用报错。
	// private static final String DATA_SOURCE_NAME = DataSource.class.getSimpleName();
	// private static final String MASTER_DATA_SOURCE_NAME = MASTER_PREFIX + DATA_SOURCE_NAME;
	// private static final String SLAVE_DATA_SOURCE_NAME = SLAVE_PREFIX + DATA_SOURCE_NAME;

	private static final String CONNECTION_NAME = Connection.class.getSimpleName();
	private static final String MASTER_CONNECTION_NAME = MASTER_PREFIX + CONNECTION_NAME;
	private static final String SLAVE_CONNECTION_NAME = SLAVE_PREFIX + CONNECTION_NAME;

	/**
	 * TODO 这个实验还不算成功。还需要再改进。
	 */
	@Test
	public void test() throws SQLException {
		AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
		ctx.register(Config.class);
		ctx.refresh();

		DataSource dataSource = ctx.getBean(DataSource.class);
		Connection connection = dataSource.getConnection();
		assertThat(connection.toString()).isEqualTo(MASTER_CONNECTION_NAME);

		isMaster = false;
		dataSource = ctx.getBean(DataSource.class);
		connection = dataSource.getConnection();
		assertThat(connection.toString()).isEqualTo(SLAVE_CONNECTION_NAME);
	}

	@Configuration
	@EnableTransactionManagement
	public static class Config {
		@Bean(MASTER_DATA_SOURCE_NAME)
		public DataSource masterDataSource() {
			DataSource dataSource = mock(DataSource.class, MASTER_DATA_SOURCE_NAME);
			try {
				when(dataSource.getConnection())
						.thenReturn(mock(Connection.class, MASTER_CONNECTION_NAME));
			} catch (SQLException e) {
				log.info("invoke getConnection error", e);
			}
			return dataSource;
		}

		@Bean(SLAVE_DATA_SOURCE_NAME)
		public DataSource slaveDataSource() {
			DataSource dataSource = mock(DataSource.class, SLAVE_DATA_SOURCE_NAME);
			try {
				when(dataSource.getConnection())
						.thenReturn(mock(Connection.class, SLAVE_CONNECTION_NAME));
			} catch (SQLException e) {
				log.info("invoke getConnection error", e);
			}
			return dataSource;
		}

		@Bean
		@Primary
		public DataSource primaryDataSource(
				@Autowired @Qualifier(MASTER_DATA_SOURCE_NAME) DataSource masterDataSource,
				@Autowired @Qualifier(SLAVE_DATA_SOURCE_NAME) DataSource slaveDataSource) {
			HotSwappableRoutingDataSource dataSource = new HotSwappableRoutingDataSource();
			HashMap<Object, Object> dataSources = new HashMap<>();
			dataSources.put(MASTER_DATA_SOURCE_NAME, masterDataSource);
			dataSources.put(SLAVE_DATA_SOURCE_NAME, slaveDataSource);
			dataSource.setTargetDataSources(dataSources);
			dataSource.setDefaultTargetDataSource(masterDataSource);
			return dataSource;
		}
	}

	public static class HotSwappableRoutingDataSource extends AbstractRoutingDataSource {
		@Override
		protected Object determineCurrentLookupKey() {
			return isMaster ? MASTER_DATA_SOURCE_NAME : SLAVE_DATA_SOURCE_NAME;
		}
	}
}
这还是一个半成品!如果用于生成,还需要打磨,比如简化配置和侵入、接入配置中心等。

5.1.2. 异常体系

Spring 会根据错误码和 SQL 状态码将 SQLExeption 转换为对应的 Spring DAO 异常 。 在 org.springframework.jdbc.support 包中定义了 SQLExceptionTranslator 接口,该接口的两个实现类 SQLErrorCodeSQLExceptionTranslatorSQLStateSQLExceptionTranslator 分别负责处理 SQLException 中错误代码和 SQL 状态码的转换工作 。

SQLErrorCodeSQLExceptionTranslator.doTranslate 是真正实现从错误码到异常的转换工作。在 sql-error-codes.xml 文件中定义异常类型,实现可扩展性。

在两个地方完成异常转换工作:

  1. 在执行 SQL 时报错,这个时候就要进行回滚。所以,在回滚时,执行异常转换。

    TODO dgg 如果"关闭事务"(事务是否可以关闭?)或只读事务时,有事务吗?会执行回滚吗?

  2. 在提交时报错,进行异常转换。

5.2. Spring 整合 ORM 框架

5.2.1. 整合 Hibernate

5.2.2. 整合 JPA

5.2.3. 整合 MyBATIS

Spring 与 MyBATIS 的整合并不是 Spring 实现的,而且由 MyBATIS 项目组提供的。通过这个整合,也可以学习一下如何提供整合自己的类型框架。

在上一篇文章 Spring 扩展点概览及实践 中介绍了 Spring 内部存在的扩展点。学以致用,现在来分析一下 Spring 与 MyBATIS 的整合流程。

5.2.3.1. 示例程序

为了方便分析源码,先根据官方文档 mybatis-spring – MyBatis-Spring | Getting Started 搭建起一个简单实例。

数据库方面,直接使用功能了 MySQL 示例数据库: MySQL : Employees Sample Database,需要的话,自行下载。

SpringMybatisTest
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
package com.diguage.truman.mybatis;

import com.mysql.cj.jdbc.Driver;
import com.zaxxer.hikari.HikariDataSource;
import jakarta.annotation.Resource;
import org.apache.ibatis.session.Configuration;
import org.junit.jupiter.api.Test;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.Transactional;

import javax.sql.DataSource;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * @author D瓜哥 · https://www.diguage.com
 * @since 2020-05-29 17:11
 */
public class SpringMybatisTest {
  private static final Logger logger = LoggerFactory.getLogger(SpringMybatisTest.class);

  @Test
  public void testCacheQuery() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.register(Config.class);
    context.refresh();
    EmployeesMapper employeesMapper = context.getBean(EmployeesMapper.class);
    Employees employees = employeesMapper.getById(10001);
    System.out.println(employees);
    System.out.println(employeesMapper.getById(10001));
  }

  @Test
  public void testInsert() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.register(Config.class);
    context.refresh();
    EmployeesService service = context.getBean(EmployeesService.class);
    Employees employees = new Employees();
    employees.empNo = 123456789;
    employees.birthDate = new Date();
    employees.firstName = "Dummy";
    employees.lastName = "Fake";
    employees.gender = "F";
    employees.hireDate = new Date();
    int insert = service.save(employees);
  }

  @org.springframework.context.annotation.Configuration
  @EnableTransactionManagement
  @MapperScan(basePackages = "com.diguage.truman.mybatis")
  @Import(EmployeesService.class)
  public static class Config {
    @Bean
    public DataSource dataSource() {
      HikariDataSource dataSource = new HikariDataSource();
      dataSource.setUsername("root");
      dataSource.setPassword("123456");
      dataSource.setDriverClassName(Driver.class.getName());
      dataSource.setConnectionTimeout(TimeUnit.SECONDS.toMillis(5));

      dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/employees?useUnicode=true" +
          "&characterEncoding=utf-8&autoReconnectForPools=true&autoReconnect=true");
      return dataSource;
    }

    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
      return new DataSourceTransactionManager(dataSource);
    }

    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(@Autowired DataSource dataSource) {
      SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
      factoryBean.setDataSource(dataSource);
      Configuration configuration = new Configuration();
      configuration.setMapUnderscoreToCamelCase(true);
      factoryBean.setConfiguration(configuration);
      return factoryBean;
    }
  }

  @Service
  public static class EmployeesService {
    @Resource
    private EmployeesMapper employeesMapper;

    @Transactional
    public int save(Employees employees) {
      return employeesMapper.insert(employees);
    }
  }
}
EmployeesMapper
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package com.diguage.truman.mybatis;

import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

/**
 * @author D瓜哥 · https://www.diguage.com
 * @since 2020-05-29 17:23
 */
public interface EmployeesMapper {
  @Select("SELECT * FROM employees WHERE emp_no = #{id}")
  Employees getById(@Param("id") Integer id);

  @Insert("INSERT INTO employees(emp_no, birth_date, first_name, last_name, gender, hire_date) " +
      "values(#{empNo}, #{birthDate, jdbcType=TIMESTAMP}, #D瓜哥, #{lastName}, #{gender}, " +
      "       #{hireDate, jdbcType=TIMESTAMP})")
  int insert(Employees employees);
}
Employees
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.diguage.truman.mybatis;

import java.util.Date;

/**
 * @author D瓜哥 · https://www.diguage.com
 * @since 2020-05-29 17:24
 */
public class Employees {
  Integer empNo;
  Date birthDate;
  String firstName;
  String lastName;
  String gender;
  Date hireDate;

  @Override
  public String toString() {
    return "Employees{" +
        "empNo=" + empNo +
        ", birthDate=" + birthDate +
        ", firstName='" + firstName + '\'' +
        ", lastName='" + lastName + '\'' +
        ", gender='" + gender + '\'' +
        ", hireDate=" + hireDate +
        '}';
  }
}

整个实例代码中,只有 @MapperScan(basePackages = "com.diguage.truman.mybatis") 这个注解和 MyBATIS 的配置相关,我们就从这里开始吧。

5.2.3.2. @MapperScan 处理

D瓜哥在 Spring 扩展点概览及实践:BeanDefinitionRegistryPostProcessor 中已经指出 ConfigurationClassPostProcessor 负责处理 @Configuration 注解。所以,可以直接去看这个类的代码。

ConfigurationClassPostProcessor 的处理流程都是在 processConfigBeanDefinitions(BeanDefinitionRegistry registry) 方法中完成的。在这个方法中,可以看到如下代码:

ConfigurationClassPostProcessor#processConfigBeanDefinitions
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/**
 * Build and validate a configuration model based on the registry of
 * {@link Configuration} classes.
 */
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
	//定义一个list存放app 提供的bd(项目当中提供了@Component)
	List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
	//获取容器中注册的所有bd名字
	//7个 TODO 新版已经不是 7 个了。
	String[] candidateNames = registry.getBeanDefinitionNames();

	for (String beanName : candidateNames) {
		BeanDefinition beanDef = registry.getBeanDefinition(beanName);
		if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
			//如果BeanDefinition中的configurationClass属性为full或者lite,则意味着已经处理过了,直接跳过
			//这里需要结合下面的代码才能理解
			if (logger.isDebugEnabled()) {
				logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
			}
		}
		//判断是否是Configuration类,如果加了Configuration下面的这几个注解就不再判断了
		// 还有  add(Component.class.getName());
		//		candidateIndicators.add(ComponentScan.class.getName());
		//		candidateIndicators.add(Import.class.getName());
		//		candidateIndicators.add(ImportResource.class.getName());
		//beanDef == appconfig
		else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
			//BeanDefinitionHolder 也可以看成一个数据结构
			configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
		}
	}

	// Return immediately if no @Configuration classes were found
	if (configCandidates.isEmpty()) {
		return;
	}

	// 排序,根据order,不重要
	// Sort by previously determined @Order value, if applicable
	configCandidates.sort((bd1, bd2) -> {
		int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
		int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
		return Integer.compare(i1, i2);
	});

	// Detect any custom bean name generation strategy supplied through the enclosing application context
	SingletonBeanRegistry sbr = null;
	//如果BeanDefinitionRegistry是SingletonBeanRegistry子类的话,
	// 由于我们当前传入的是DefaultListableBeanFactory,是SingletonBeanRegistry 的子类
	// 因此会将registry强转为SingletonBeanRegistry
	if (registry instanceof SingletonBeanRegistry _sbr) {
		sbr = _sbr;
		if (!this.localBeanNameGeneratorSet) {
			BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
					AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
			if (generator != null) {
				this.componentScanBeanNameGenerator = generator;
				this.importBeanNameGenerator = generator;
			}
		}
	}

	if (this.environment == null) {
		this.environment = new StandardEnvironment();
	}

	// 处理 @Configuration 注解过的类
	// Parse each @Configuration class
	ConfigurationClassParser parser = new ConfigurationClassParser(
			this.metadataReaderFactory, this.problemReporter, this.environment,
			this.resourceLoader, this.componentScanBeanNameGenerator, registry);

	Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
	Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
	do {
		StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
		// TODO 处理 @PropertySource、@ComponentScan、@Import、@ImportResource、@Bean 注解,调试一下
		parser.parse(candidates);
		parser.validate();

		Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
		configClasses.removeAll(alreadyParsed);

		// Read the model and create bean definitions based on its content
		if (this.reader == null) {
			this.reader = new ConfigurationClassBeanDefinitionReader(
					registry, this.sourceExtractor, this.resourceLoader, this.environment,
					this.importBeanNameGenerator, parser.getImportRegistry());
		}
		this.reader.loadBeanDefinitions(configClasses);
		alreadyParsed.addAll(configClasses);
		processConfig.tag("classCount", () -> String.valueOf(configClasses.size())).end();

		candidates.clear();
		if (registry.getBeanDefinitionCount() > candidateNames.length) {
			String[] newCandidateNames = registry.getBeanDefinitionNames();
			Set<String> oldCandidateNames = Set.of(candidateNames);
			Set<String> alreadyParsedClasses = new HashSet<>();
			for (ConfigurationClass configurationClass : alreadyParsed) {
				alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
			}
			for (String candidateName : newCandidateNames) {
				if (!oldCandidateNames.contains(candidateName)) {
					BeanDefinition bd = registry.getBeanDefinition(candidateName);
					if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
							!alreadyParsedClasses.contains(bd.getBeanClassName())) {
						candidates.add(new BeanDefinitionHolder(bd, candidateName));
					}
				}
			}
			candidateNames = newCandidateNames;
		}
	}
	while (!candidates.isEmpty());

	// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
	if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
		sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
	}

	// Store the PropertySourceDescriptors to contribute them Ahead-of-time if necessary
	this.propertySourceDescriptors = parser.getPropertySourceDescriptors();

	if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory cachingMetadataReaderFactory) {
		// Clear cache in externally provided MetadataReaderFactory; this is a no-op
		// for a shared cache since it'll be cleared by the ApplicationContext.
		cachingMetadataReaderFactory.clearCache();
	}
}

parser.parse(candidates); 这行代码打一个断点,然后一步一步跟下去,就到了 ConfigurationClassParserdoProcessConfigurationClass 方法里,重点关注 processImports 这行:

ConfigurationClassParser#doProcessConfigurationClass
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
/**
 * Apply processing and build a complete {@link ConfigurationClass} by reading the
 * annotations, members and methods from the source class. This method can be called
 * multiple times as relevant sources are discovered.
 * @param configClass the configuration class being build
 * @param sourceClass a source class
 * @return the superclass, or {@code null} if none found or previously processed
 */
@Nullable
protected final SourceClass doProcessConfigurationClass(
		ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
		throws IOException {

	if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
		// Recursively process any member (nested) classes first
		processMemberClasses(configClass, sourceClass, filter);
	}

	// 处理 @PropertySource 注解
	// Process any @PropertySource annotations
	for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
			sourceClass.getMetadata(), org.springframework.context.annotation.PropertySource.class,
			PropertySources.class, true)) {
		if (this.propertySourceRegistry != null) {
			this.propertySourceRegistry.processPropertySource(propertySource);
		}
		else {
			logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
					"]. Reason: Environment must implement ConfigurableEnvironment");
		}
	}

	// 处理 @ComponentScan 注解
	// Process any @ComponentScan annotations
	Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
			sourceClass.getMetadata(), ComponentScan.class, ComponentScans.class);
	if (!componentScans.isEmpty() &&
			!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
		for (AnnotationAttributes componentScan : componentScans) {
			// The config class is annotated with @ComponentScan -> perform the scan immediately
			Set<BeanDefinitionHolder> scannedBeanDefinitions =
					this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
			// Check the set of scanned definitions for any further config classes and parse recursively if needed
			for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
				BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
				if (bdCand == null) {
					bdCand = holder.getBeanDefinition();
				}
				if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
					parse(bdCand.getBeanClassName(), holder.getBeanName());
				}
			}
		}
	}

	// 处理 @Import 注解
	// Process any @Import annotations
	processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

	// 处理 @ImportResource 注解
	// Process any @ImportResource annotations
	AnnotationAttributes importResource =
			AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
	if (importResource != null) {
		String[] resources = importResource.getStringArray("locations");
		Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
		for (String resource : resources) {
			String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
			configClass.addImportedResource(resolvedResource, readerClass);
		}
	}

	// 处理 @Bean 注解的方法
	// Process individual @Bean methods
	Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
	for (MethodMetadata methodMetadata : beanMethods) {
		configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
	}

	// Process default methods on interfaces
	processInterfaces(configClass, sourceClass);

	// Process superclass, if any
	if (sourceClass.getMetadata().hasSuperClass()) {
		String superclass = sourceClass.getMetadata().getSuperClassName();
		if (superclass != null && !superclass.startsWith("java") &&
				!this.knownSuperclasses.containsKey(superclass)) {
			this.knownSuperclasses.put(superclass, configClass);
			// Superclass found, return its annotation metadata and recurse
			return sourceClass.getSuperClass();
		}
	}

	// No superclass -> processing is complete
	return null;
}

请注意这里的 getImports(sourceClass),我们看一下这个方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
 * Returns {@code @Import} class, considering all meta-annotations.
 */
private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
	Set<SourceClass> imports = new LinkedHashSet<>();
	Set<SourceClass> visited = new LinkedHashSet<>();
	collectImports(sourceClass, imports, visited);
	return imports;
}

/**
 * Recursively collect all declared {@code @Import} values. Unlike most
 * meta-annotations it is valid to have several {@code @Import}s declared with
 * different values; the usual process of returning values from the first
 * meta-annotation on a class is not sufficient.
 * <p>For example, it is common for a {@code @Configuration} class to declare direct
 * {@code @Import}s in addition to meta-imports originating from an {@code @Enable}
 * annotation.
 * @param sourceClass the class to search
 * @param imports the imports collected so far
 * @param visited used to track visited classes to prevent infinite recursion
 * @throws IOException if there is any problem reading metadata from the named class
 */
private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
		throws IOException {

	if (visited.add(sourceClass)) {
		for (SourceClass annotation : sourceClass.getAnnotations()) {
			String annName = annotation.getMetadata().getClassName();
			if (!annName.equals(Import.class.getName())) {
				collectImports(annotation, imports, visited);
			}
		}
		imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
	}
}

String annName = annotation.getMetadata().getClassName(); 这行代码打断点,然后调试,注意观察 annName 变量的值,相信肯定可以看到 org.mybatis.spring.annotation.MapperScan,接着就可以看到,通过 sourceClass.getAnnotationAttributes(Import.class.getName(), "value") 解析 @Import 注解,把其中的 org.mybatis.spring.annotation.MapperScannerRegistrar 的相关信息(被封装成了 SourceClass 对象)加入到了 imports 变量中。

下面看一下是如何处理 MapperScannerRegistrar 的。

5.2.3.3. MapperScannerRegistrar

我们接着看 processImports 方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
  Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
  boolean checkForCircularImports) {

    //...此处省去 N 行代码
      else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                    // 很明显,会进入到这个分支
        // Candidate class is an ImportBeanDefinitionRegistrar ->
        // delegate to it to register additional bean definitions
        Class<?> candidateClass = candidate.loadClass();
        ImportBeanDefinitionRegistrar registrar =
            ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
                this.environment, this.resourceLoader, this.registry);
                    // 创建一个实例,然后加入到 configClass 中
        configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
    //...此处省去 N 行代码
}

接着,回到 processConfigBeanDefinitions 方法:

ConfigurationClassPostProcessor#processConfigBeanDefinitions
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/**
 * Build and validate a configuration model based on the registry of
 * {@link Configuration} classes.
 */
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
	//定义一个list存放app 提供的bd(项目当中提供了@Component)
	List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
	//获取容器中注册的所有bd名字
	//7个 TODO 新版已经不是 7 个了。
	String[] candidateNames = registry.getBeanDefinitionNames();

	for (String beanName : candidateNames) {
		BeanDefinition beanDef = registry.getBeanDefinition(beanName);
		if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
			//如果BeanDefinition中的configurationClass属性为full或者lite,则意味着已经处理过了,直接跳过
			//这里需要结合下面的代码才能理解
			if (logger.isDebugEnabled()) {
				logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
			}
		}
		//判断是否是Configuration类,如果加了Configuration下面的这几个注解就不再判断了
		// 还有  add(Component.class.getName());
		//		candidateIndicators.add(ComponentScan.class.getName());
		//		candidateIndicators.add(Import.class.getName());
		//		candidateIndicators.add(ImportResource.class.getName());
		//beanDef == appconfig
		else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
			//BeanDefinitionHolder 也可以看成一个数据结构
			configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
		}
	}

	// Return immediately if no @Configuration classes were found
	if (configCandidates.isEmpty()) {
		return;
	}

	// 排序,根据order,不重要
	// Sort by previously determined @Order value, if applicable
	configCandidates.sort((bd1, bd2) -> {
		int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
		int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
		return Integer.compare(i1, i2);
	});

	// Detect any custom bean name generation strategy supplied through the enclosing application context
	SingletonBeanRegistry sbr = null;
	//如果BeanDefinitionRegistry是SingletonBeanRegistry子类的话,
	// 由于我们当前传入的是DefaultListableBeanFactory,是SingletonBeanRegistry 的子类
	// 因此会将registry强转为SingletonBeanRegistry
	if (registry instanceof SingletonBeanRegistry _sbr) {
		sbr = _sbr;
		if (!this.localBeanNameGeneratorSet) {
			BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
					AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
			if (generator != null) {
				this.componentScanBeanNameGenerator = generator;
				this.importBeanNameGenerator = generator;
			}
		}
	}

	if (this.environment == null) {
		this.environment = new StandardEnvironment();
	}

	// 处理 @Configuration 注解过的类
	// Parse each @Configuration class
	ConfigurationClassParser parser = new ConfigurationClassParser(
			this.metadataReaderFactory, this.problemReporter, this.environment,
			this.resourceLoader, this.componentScanBeanNameGenerator, registry);

	Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
	Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
	do {
		StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
		// TODO 处理 @PropertySource、@ComponentScan、@Import、@ImportResource、@Bean 注解,调试一下
		parser.parse(candidates);
		parser.validate();

		Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
		configClasses.removeAll(alreadyParsed);

		// Read the model and create bean definitions based on its content
		if (this.reader == null) {
			this.reader = new ConfigurationClassBeanDefinitionReader(
					registry, this.sourceExtractor, this.resourceLoader, this.environment,
					this.importBeanNameGenerator, parser.getImportRegistry());
		}
		this.reader.loadBeanDefinitions(configClasses);
		alreadyParsed.addAll(configClasses);
		processConfig.tag("classCount", () -> String.valueOf(configClasses.size())).end();

		candidates.clear();
		if (registry.getBeanDefinitionCount() > candidateNames.length) {
			String[] newCandidateNames = registry.getBeanDefinitionNames();
			Set<String> oldCandidateNames = Set.of(candidateNames);
			Set<String> alreadyParsedClasses = new HashSet<>();
			for (ConfigurationClass configurationClass : alreadyParsed) {
				alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
			}
			for (String candidateName : newCandidateNames) {
				if (!oldCandidateNames.contains(candidateName)) {
					BeanDefinition bd = registry.getBeanDefinition(candidateName);
					if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
							!alreadyParsedClasses.contains(bd.getBeanClassName())) {
						candidates.add(new BeanDefinitionHolder(bd, candidateName));
					}
				}
			}
			candidateNames = newCandidateNames;
		}
	}
	while (!candidates.isEmpty());

	// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
	if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
		sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
	}

	// Store the PropertySourceDescriptors to contribute them Ahead-of-time if necessary
	this.propertySourceDescriptors = parser.getPropertySourceDescriptors();

	if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory cachingMetadataReaderFactory) {
		// Clear cache in externally provided MetadataReaderFactory; this is a no-op
		// for a shared cache since it'll be cleared by the ApplicationContext.
		cachingMetadataReaderFactory.clearCache();
	}
}

进入 this.reader.loadBeanDefinitions(configClasses); 方法:

ConfigurationClassBeanDefinitionReader#loadBeanDefinitions
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
/**
 * Read {@code configurationModel}, registering bean definitions
 * with the registry based on its contents.
 */
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
	TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
	for (ConfigurationClass configClass : configurationModel) {
		loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
	}
}

/**
 * Read a particular {@link ConfigurationClass}, registering bean definitions
 * for the class itself and all of its {@link Bean} methods.
 */
private void loadBeanDefinitionsForConfigurationClass(
		ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {

	if (trackedConditionEvaluator.shouldSkip(configClass)) {
		String beanName = configClass.getBeanName();
		if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
			this.registry.removeBeanDefinition(beanName);
		}
		this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
		return;
	}

	if (configClass.isImported()) {
		registerBeanDefinitionForImportedConfigurationClass(configClass);
	}
	for (BeanMethod beanMethod : configClass.getBeanMethods()) {
		loadBeanDefinitionsForBeanMethod(beanMethod);
	}

	loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
	loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}

private void loadBeanDefinitionsFromImportedResources(
		Map<String, Class<? extends BeanDefinitionReader>> importedResources) {

	Map<Class<?>, BeanDefinitionReader> readerInstanceCache = new HashMap<>();

	importedResources.forEach((resource, readerClass) -> {
		// Default reader selection necessary?
		if (BeanDefinitionReader.class == readerClass) {
			if (StringUtils.endsWithIgnoreCase(resource, ".groovy")) {
				// When clearly asking for Groovy, that's what they'll get...
				readerClass = GroovyBeanDefinitionReader.class;
			}
			else {
				// Primarily ".xml" files but for any other extension as well
				readerClass = XmlBeanDefinitionReader.class;
			}
		}

		BeanDefinitionReader reader = readerInstanceCache.get(readerClass);
		if (reader == null) {
			try {
				// Instantiate the specified BeanDefinitionReader
				reader = readerClass.getConstructor(BeanDefinitionRegistry.class).newInstance(this.registry);
				// Delegate the current ResourceLoader to it if possible
				if (reader instanceof AbstractBeanDefinitionReader abdr) {
					abdr.setResourceLoader(this.resourceLoader);
					abdr.setEnvironment(this.environment);
				}
				readerInstanceCache.put(readerClass, reader);
			}
			catch (Throwable ex) {
				throw new IllegalStateException(
						"Could not instantiate BeanDefinitionReader class [" + readerClass.getName() + "]");
			}
		}

		// TODO SPR-6310: qualify relative path locations as done in AbstractContextLoader.modifyLocations
		reader.loadBeanDefinitions(resource);
	});
}

private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
	registrars.forEach((registrar, metadata) ->
			registrar.registerBeanDefinitions(metadata, this.registry, this.importBeanNameGenerator));
}

到这里就调用到了 MapperScannerRegistrarregisterBeanDefinitions 方法:

MapperScannerRegistrar#registerBeanDefinitions(AnnotationMetadata, BeanDefinitionRegistry)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
/**
 * {@inheritDoc}
 */
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  AnnotationAttributes mapperScanAttrs = AnnotationAttributes
      .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
  if (mapperScanAttrs != null) {
    registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
        generateBaseBeanName(importingClassMetadata, 0));
  }
}

void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
    BeanDefinitionRegistry registry, String beanName) {

  // 注意这行代码:
  BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
  builder.addPropertyValue("processPropertyPlaceHolders", true);

  Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
  if (!Annotation.class.equals(annotationClass)) {
    builder.addPropertyValue("annotationClass", annotationClass);
  }

  Class<?> markerInterface = annoAttrs.getClass("markerInterface");
  if (!Class.class.equals(markerInterface)) {
    builder.addPropertyValue("markerInterface", markerInterface);
  }

  Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
  if (!BeanNameGenerator.class.equals(generatorClass)) {
    builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));
  }

  Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
  if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
    builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
  }

  String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");
  if (StringUtils.hasText(sqlSessionTemplateRef)) {
    builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));
  }

  String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");
  if (StringUtils.hasText(sqlSessionFactoryRef)) {
    builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));
  }

  List<String> basePackages = new ArrayList<>();
  basePackages.addAll(
      Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));

  basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText)
      .collect(Collectors.toList()));

  basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName)
      .collect(Collectors.toList()));

  if (basePackages.isEmpty()) {
    basePackages.add(getDefaultBasePackage(annoMeta));
  }

  String lazyInitialization = annoAttrs.getString("lazyInitialization");
  if (StringUtils.hasText(lazyInitialization)) {
    builder.addPropertyValue("lazyInitialization", lazyInitialization);
  }

  builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));

  registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

}

其实只干了一件事情,就是在想容器中注册了一个类为 MapperScannerConfigurerBeanDefinition,在创建过程中,还把 @MapperScan 注解中的属性给添加到了 BeanDefinition 属性中。下面,来看看 MapperScannerConfigurer 是何方神圣。

5.2.3.4. MapperScannerConfigurer

先看一下 MapperScannerConfigurer 的类型定义:

1
2
public class MapperScannerConfigurer
    implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {

结合上一篇文章 Spring 扩展点概览及实践:BeanDefinitionRegistryPostProcessor 中的介绍,可以知道 BeanDefinitionRegistryPostProcessor 也是 Spring 生命周期中的一环,将其注册到容器中,就可以通过对 postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) 来实现注册自定义 BeanDefinition 的功能。

来看看 postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) 的定义:

MapperScannerConfigurer#postProcessBeanDefinitionRegistry
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
  if (this.processPropertyPlaceHolders) {
    processPropertyPlaceHolders();
  }

  ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
  scanner.setAddToConfig(this.addToConfig);
  scanner.setAnnotationClass(this.annotationClass);
  scanner.setMarkerInterface(this.markerInterface);
  scanner.setSqlSessionFactory(this.sqlSessionFactory);
  scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
  scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
  scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
  scanner.setResourceLoader(this.applicationContext);
  scanner.setBeanNameGenerator(this.nameGenerator);
  scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
  if (StringUtils.hasText(lazyInitialization)) {
    scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
  }
  scanner.registerFilters();
  scanner.scan(
      StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

代码已经非常明确了,就是注册了一个 ClassPathMapperScanner,同事调用了 scanner.scan 方法。下面,来看一下 ClassPathMapperScanner

5.2.3.5. ClassPathMapperScanner

老规矩,先看看 ClassPathMapperScanner 的定义:

1
2
3
4
5
6
7
8
9
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {

  //...此处省去 N 行代码

  private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;

  public ClassPathMapperScanner(BeanDefinitionRegistry registry) {
    super(registry, false);
  }

从这里可以看出,ClassPathMapperScanner 就是一个 ClassPathBeanDefinitionScanner,根据类名可以得知,扫描 class path 并生成 BeanDefinition。来看一下 scan(String…​ basePackages)

ClassPathBeanDefinitionScanner#scan
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
 * 调用类路径 Bean 定义扫描器入口方法。
 *
 * 在该函数中通过调用
 * AnnotationConfigUtils#registerAnnotationConfigProcessors(BeanDefinitionRegistry, Object)
 * 方法,来注册内置的 PostProcessor 等:
 *
 * 1. ConfigurationClassPostProcessor
 * 2. AutowiredAnnotationBeanPostProcessor
 * 3. CommonAnnotationBeanPostProcessor
 * 4. PersistenceAnnotationBeanPostProcessor? -- 这个得看是否需要
 * 5. EventListenerMethodProcessor
 * 6. DefaultEventListenerFactory
 *
 * Perform a scan within the specified base packages.
 * @param basePackages the packages to check for annotated classes
 * @return number of beans registered
 */
public int scan(String... basePackages) {
	// 获取容器中已经注册的 Bean 个数
	int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
	// 启动扫描器扫描指定包
	doScan(basePackages);
	// 注册注解配置(Annotation Config)处理器
	// Register annotation config processors, if necessary.
	if (this.includeAnnotationConfig) {
		AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
	}
	// 返回注册的 Bean 个数
	return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}

这里把实际扫描工作委托给了 doScan(basePackages) 方法,而这个方法被 ClassPathMapperScanner 重写了,来看一下它的实现:

ClassPathMapperScanner#doScan
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
/**
 * Calls the parent search that will search and register all the candidates. Then the registered objects are post
 * processed to set them as MapperFactoryBeans
 */
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
  Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

  if (beanDefinitions.isEmpty()) {
    LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
        + "' package. Please check your configuration.");
  } else {
    processBeanDefinitions(beanDefinitions);
  }

  return beanDefinitions;
}

实际的扫描工作还是由父类 super.doScan(basePackages) 完成,只是又对扫描结果做了进一步处理: processBeanDefinitions(beanDefinitions)

ClassPathMapperScanner#processBeanDefinitions
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
  GenericBeanDefinition definition;
  for (BeanDefinitionHolder holder : beanDefinitions) {
    definition = (GenericBeanDefinition) holder.getBeanDefinition();
    String beanClassName = definition.getBeanClassName();
    LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
        + "' mapperInterface");

    // the mapper interface is the original class of the bean
    // but, the actual class of the bean is MapperFactoryBean
    // 注意这行代码
    definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
    // 注意这行代码
    definition.setBeanClass(this.mapperFactoryBeanClass);

    definition.getPropertyValues().add("addToConfig", this.addToConfig);

    boolean explicitFactoryUsed = false;
    if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
      definition.getPropertyValues().add("sqlSessionFactory",
          new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
      explicitFactoryUsed = true;
    } else if (this.sqlSessionFactory != null) {
      definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
      explicitFactoryUsed = true;
    }

    if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
      if (explicitFactoryUsed) {
        LOGGER.warn(
            () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
      }
      definition.getPropertyValues().add("sqlSessionTemplate",
          new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
      explicitFactoryUsed = true;
    } else if (this.sqlSessionTemplate != null) {
      if (explicitFactoryUsed) {
        LOGGER.warn(
            () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
      }
      definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
      explicitFactoryUsed = true;
    }

    if (!explicitFactoryUsed) {
      LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
      definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    }
    definition.setLazyInit(lazyInitialization);
  }
}

这里特别需要注意的是 definition.setBeanClass(this.mapperFactoryBeanClass); 这行代码。为什么把扫描出来的 MapperBean Class 给设置成 mapperFactoryBeanClass 呢?通过上面的 ClassPathMapperScanner 类型定义可以知道,mapperFactoryBeanClass 就是 MapperFactoryBean

另外,还有一点值得思考,扫描出来的是接口,怎么生成对应的实例呢?带着这两个问题,来看一下 MapperFactoryBean

5.2.3.6. MapperFactoryBean

来看一下 MapperFactoryBean 的类型定义:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

  private Class<T> mapperInterface;

  private boolean addToConfig = true;

  public MapperFactoryBean() {
    // intentionally empty
  }

  public MapperFactoryBean(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void checkDaoConfig() {
    super.checkDaoConfig();

    notNull(this.mapperInterface, "Property 'mapperInterface' is required");

    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        configuration.addMapper(this.mapperInterface);
      } catch (Exception e) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
        throw new IllegalArgumentException(e);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

可以看出 MapperFactoryBean 是一个 FactoryBean,上一篇文章 Spring 扩展点概览及实践:FactoryBean 中提到,FactoryBean 就是专门生产 Bean 的工厂。

再看构造函数 public MapperFactoryBean(Class<T> mapperInterface),结合上一个片段代码中注意的地方可以看出,从 Class Path 扫描出来的 BeanDefinition,把扫描出来的接口设置为构造函数参数 definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); 然后通过实例化 FactoryBean,然后调用 getObject() 就可以获得接口对应的实例对象。

实例化对象的过程是由 MyBATIS 完成的,以后单独开篇来介绍,这里不再多做介绍。

还有个疑问,MyBATIS 是怎么知道 Mapper 接口信息呢?这个问题就要看 checkDaoConfig() 方法了,单步调试代码可以知道父类 DaoSupport#afterPropertiesSet 调用的,在这个方法中,把 Mapper 接口信息条件到了 MyBATIS 中 configuration.addMapper(this.mapperInterface)

自此,MyBATIS 和 Spring 的整个流程就全部介绍完毕了。下面做个小节。

5.2.3.7. 小节

本文从源码角度,深入绍了 MyBATIS 和 Spring 整合过程。整个过程中,用到了 Spring 的如下扩展点:

  1. @Import

  2. MapperScannerRegistrar - ImportBeanDefinitionRegistrar

  3. MapperScannerConfigurer - BeanDefinitionRegistryPostProcessor

  4. ClassPathMapperScanner - ClassPathBeanDefinitionScanner

  5. MapperFactoryBean - FactoryBean

  6. InitializingBean

可见,和 Spring 整合并不是只靠一个扩展点就可以完成的,需要多个扩展点多方配合才能更好地完成整合过程。

5.2.3.8. 为什么在 Spring+MyBATIS 时,一级缓存失效?

在原生 MyBATIS 实现中,在执行查询时,使用的 SqlSessionDefaultSqlSessionDefaultSqlSession 实例是在执行 SqlSession session = sqlSessionFactory.openSession(); 时创建的。执行查询操作也是在 DefaultSqlSession.selectList(String, Object, RowBounds, ResultHandler) 中完成的。

使用 MyBAITS 原生查询
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
 * @author D瓜哥 · https://www.diguage.com
 * @since 2022-07-03 09:47:37
 */
@Test
public void testCacheQuery() {
  DataSource dataSource = getDataSource();
  TransactionFactory transactionFactory = new JdbcTransactionFactory();
  Environment environment =
      new Environment("development", transactionFactory, dataSource);
  Configuration configuration = new Configuration(environment);
  configuration.addMapper(EmployeesMapper.class);
  configuration.setCacheEnabled(true);
  configuration.setMapUnderscoreToCamelCase(true);
  SqlSessionFactory sqlSessionFactory =
      new SqlSessionFactoryBuilder().build(configuration);
  SqlSession session = sqlSessionFactory.openSession();
  EmployeesMapper mapper = session.getMapper(EmployeesMapper.class);
  System.out.println(mapper.getById(10001));
  System.out.println(mapper.getById(10001));
}

/**
 * @author D瓜哥 · https://www.diguage.com
 * @since 2022-07-03 09:47:37
 */
public DataSource getDataSource() {
  HikariDataSource dataSource = new HikariDataSource();
  dataSource.setUsername("root");
  dataSource.setPassword("123456");
  dataSource.setDriverClassName(Driver.class.getName());
  dataSource.setConnectionTimeout(TimeUnit.SECONDS.toMillis(5));

  dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/employees?useUnicode=true" +
      "&characterEncoding=utf-8&autoReconnectForPools=true&autoReconnect=true");
  return dataSource;
}

Spring 的示例请看 EmployeesEmployeesMapperSpringMybatisTest

在 Spring + MyBATIS 搭配中,在执行查询时,使用的 SqlSessionSqlSessionTemplate(由“mybatis-spring”实现)。而 SqlSessionTemplate 的查询执行是委托给 SqlSessionTemplate.sqlSessionProxySqlSession 类型)来操作。 SqlSessionTemplate.sqlSessionProxy 是通过动态代理创建出来的代理实例。在代理实现内部执行时,从创建 SqlSessionTemplate 实例时经构造函数传入的 SqlSessionFactory 对象中获取 SqlSession 对象(创建过程与原生 MyBATIS 的构造过程相同)。最后,再去执行查询操作。

在创建 SqlSessionTemplate.sqlSessionProxy 代理时,代理切面在执行完查询后,执行了 closeSqlSession 操作。正是因为执行了次操作,导致了一级缓存失效。

org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
 * Proxy needed to route MyBatis method calls to the proper SqlSession got from Spring's Transaction Manager It also
 * unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to pass a {@code PersistenceException} to the
 * {@code PersistenceExceptionTranslator}.
 */
private class SqlSessionInterceptor implements InvocationHandler {
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
        SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
    try {
      Object result = method.invoke(sqlSession, args);
      if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
        // force commit even on non-dirty sessions because some databases require
        // a commit/rollback before calling close()
        sqlSession.commit(true);
      }
      return result;
    } catch (Throwable t) {
      Throwable unwrapped = unwrapThrowable(t);
      if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
        // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
        closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        sqlSession = null;
        Throwable translated = SqlSessionTemplate.this.exceptionTranslator
            .translateExceptionIfPossible((PersistenceException) unwrapped);
        if (translated != null) {
          unwrapped = translated;
        }
      }
      throw unwrapped;
    } finally {
      if (sqlSession != null) {
        closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
      }
    }
  }
}

为什么要关闭 SqlSession?因为 Spring 没有把 SqlSession 实例暴露给用户,那么用户不能控制 SqlSession 的关闭操作。所以,在执行完查询操作后,就马上关闭 SqlSession 是一个比较合理的操作。

在 Spring + MyBATIS 中, Mapper 的信息什么时候加入到 Configuration 的? // TODO

5.3. Spring 事务管理

事务是一组原子性的 SQL 查询,或者说是一个独立的工作单元。事务内的所有操作要么全部执行成功,要么全部执行失败。

5.3.1. 四个基本特性

  • Atomicity(原子性):事务是一个不可分割的整体,事务内所有操作要么全部提交成功,要么全部失败回滚。

  • Consistency(一致性):事务执行前后,数据从一个状态到另一个状态必须是一致的(A向B转账,不能出现A扣了钱,B却没收到)。

  • Isolation(隔离性):多个并发事务之间相互隔离,不能互相干扰。或者说一个事务所做的修改在最终提交以前,对其他事务是不可见的。

  • Durablity(持久性):事务完成后,对数据库的更改是永久保存的,不能回滚。

5.3.2. 事务隔离级别

5.3.2.1. Read Uncommitted(未提交读)

在 Read Uncommitted 级别,事务中的修改,即使没有提交,对其他事务也都是可见的。事务可以读取未提交的数据,这也被称为脏读(Dirty Read)。性能不会好太多,但是问题却一大堆,实际应用中一般很少使用。

5.3.2.2. Read Committed(提交读)

大多数数据库系统的默认隔离级别都是 Read Committed。Read Committed 满足前面提到的隔离性的简单定义:一个事务开始时,只能“看见”已经提交的事务所做的修改。换句话说:一个事务从开始直到提交之前,所做的任何修改对其他事务都是不可见的。有时也叫不可重复读(Nonrepeatable Read)。

5.3.2.3. Repeatable Read(可重复读)

Repeatable Read 解决了脏读的问题。但是还是无法解决领一个幻读(Phantom Read)问题。所谓幻读,指的是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行(Phantom Row)。InnoDB 和 XtraDB 存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)解决了幻读的问题。

5.3.2.4. Serializable(可串行化)

Serializable 是最高的隔离级别。它通过强制事务串行执行,避免了前面说的幻读问题。简单来说,Serializable 会在读取的每一行数据上都加锁,所以导致大量的超时和锁争用的问题。实际中,极少使用。

Repeatable Read(可重复读) 是 MySQL 默认事务隔离级别。

5.3.3. 常见错误

5.3.3.1. Phantom Read(幻读)

B 事务读取了两次数据,在这两次的读取过程中A事务添加了数据,B 事务的这两次读取出来的集合不一样。幻读产生的流程如下:

幻读处理流程
Figure 14. 幻读处理流程

这个流程看起来和不可重复读差不多,但幻读强调的集合的增减,而不是单独一条数据的修改。

5.3.3.2. NonRepeatable Read(不可重复读)

B 事务读取了两次数据,在这两次的读取过程中 A 事务修改了数据,B 事务的这两次读取出来的数据不一样。B 事务这种读取的结果,即为不可重复读(Nonrepeatable Read)。相反,“可重复读”在同一个事务中多次读取数据时,能够保证所读数据一样,也就是后续读取不能读到另一个事务已提交的更新数据。不可重复读的产生的流程如下:

不可重复读处理流程
Figure 15. 不可重复读处理流程
5.3.3.3. Dirty Read(脏读)

A 事务执行过程中,B 事务读取了A事务的修改。但是由于某些原因,A 事务可能没有完成提交,发生 RollBack 了操作,则B事务所读取的数据就会是不正确的。这个未提交数据就是脏读(Dirty Read)。

脏读处理流程
Figure 16. 脏读处理流程
5.3.3.4. Lost Update(第一类丢失更新)

在完全未隔离事务的情况下,两个事务更新同一条数据资源,某一事务完成,另一事务异常终止,回滚造成第一个完成的更新也同时丢失 。这个问题现代关系型数据库已经不会发生。

5.3.3.5. Lost Update(第二类丢失更新)

不可重复读有一种特殊情况,两个事务更新同一条数据资源,后完成的事务会造成先完成的事务更新丢失。这种情况就是大名鼎鼎的第二类丢失更新。主流的数据库已经默认屏蔽了第一类丢失更新问题(即:后做的事务撤销,发生回滚造成已完成事务的更新丢失),但我们编程的时候仍需要特别注意第二类丢失更新。它产生的流程如下:

Lost Update(第二类丢失更新)
Figure 17. Lost Update(第二类丢失更新)
5.3.3.6. 小结
“读”之间的关系
Figure 18. “读”之间的关系
数据库事务总结
Figure 19. 数据库事务总结

5.3.4. Spring 中的隔离级别

常量名称 解释说明

ISOLATION_DEFAULT

默认隔离级别,使用数据库默认的事务隔离级别。另外四个与 JDBC 的隔离级别相对应。

ISOLATION_READ_UNCOMMITTED

最低的事务隔离级别。它允许另外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻读。

ISOLATION_READ_COMMITTED

保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。

ISOLATION_REPEATABLE_READ

这种事务隔离级别可以防止脏读,不可重复读。但可能出现幻读。

ISOLATION_SERIALIZABLE

这是花费最高代价但是最可靠的事务隔离解绑。事务被处理为顺序执行。

Spring 的事务隔离级别在 `TransactionDefinition` 中有定义。

5.3.5. Spring 的事务传播行为

常量名称 解释说明

PROPAGATION_REQUIRED

支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择,也是 Spring 默认的事务的传播。

PROPAGATION_SUPPORTS

支持当前事务,如果当前没有事务,就以非事务方式执行。

PROPAGATION_MANDATORY

支持当前事务,如果当前没有事务,就抛出异常。

PROPAGATION_REQUIRES_NEW

新建事务,如果当前存在事务,把当前事务挂起。新建的事务将和被挂起的事务没有任何关系,是两个独立的事务,外层事务失败回滚之后,不能回滚内层事务执行的结果内层事务失败抛出异常,外层事务捕获,也可以不处理回滚操作

PROPAGATION_NOT_SUPPORTED

以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

PROPAGATION_NEVER

以非事务方式执行,如果当前存在事务,则抛出异常。

PROPAGATION_NESTED

如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按 REQUIRED 属性执行。它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影影响。它只对 DataSourceTransactionManaqer 事务管理器起效。

Spring 的事务传播行为在 `TransactionDefinition` 中有定义。

分析一下 Spring 中对事务支持的管理。同时,再分析一下 Java JTA 和 Java CMT。结合二者再来分析一下 Spring 中的事务管理。

PROPAGATION_NOT_SUPPORTED 以非事务方式运行,如果当前存在事务,则把当前事务挂起。

TODO 如何挂起事务?

org.springframework.transaction.support.TransactionTemplate.execute 中,排除 RuntimeException | Error 异常就回滚。那么,在 @Transactional(rollbackFor = Throwable.class) 中指定了回滚异常,怎么生效?

TODO: 增加创建用户并授权的 SQL 语句。

5.3.6. TransactionTemplate 示例

The TransactionTemplate adopts the same approach as other Spring templates, such as the JdbcTemplate. It uses a callback approach (to free application code from having to do the boilerplate acquisition and release transactional resources) and results in code that is intention driven, in that your code focuses solely on what you want to do.

— Using the TransactionTemplate
TransactionTemplateTest
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package com.diguage.truman.tx;

import com.mysql.cj.jdbc.Driver;
import com.zaxxer.hikari.HikariDataSource;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.support.TransactionTemplate;

import javax.sql.DataSource;
import java.util.Date;

/**
 * @author D瓜哥, https://www.diguage.com/
 * @since 2020-09-11 10:20
 */
public class TransactionTemplateTest {

  @Test
  public void test() {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
    applicationContext.register(Config.class);
    applicationContext.refresh();
    EmployeesService employeesService = applicationContext.getBean(EmployeesService.class);
    Employees employees = new Employees();
//    employees.empNo = 5000000;
    employees.birthDate = new Date();
    employees.firstName = "D";
    employees.gender = "M";
    employees.hireDate = new Date();
    employees.lastName = "瓜哥";
    employeesService.save(employees);
    System.out.println(employees);
  }

  @Configuration
  @EnableTransactionManagement
  @Import(EmployeesService.class)
  public static class Config {
    @Bean
    public DataSource dataSource() {
      HikariDataSource dataSource = new HikariDataSource();
      dataSource.setUsername("root");
      dataSource.setPassword("123456");
      dataSource.setDriverClassName(Driver.class.getName());
      dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/employees?useUnicode=true" +
          "&characterEncoding=utf-8&autoReconnectForPools=true&autoReconnect=true");
      return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
      return new JdbcTemplate(dataSource);
    }

    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
      return new DataSourceTransactionManager(dataSource);
    }
  }

  public static class Employees {
    Integer empNo;
    Date birthDate;
    String firstName;
    String lastName;
    String gender;
    Date hireDate;

    @Override
    public String toString() {
      return "Employees{" +
          "empNo=" + empNo +
          ", birthDate=" + birthDate +
          ", firstName='" + firstName + '\'' +
          ", lastName='" + lastName + '\'' +
          ", gender='" + gender + '\'' +
          ", hireDate=" + hireDate +
          '}';
    }
  }

  @Service
  public static class EmployeesService {
    @Resource
    private JdbcTemplate jdbcTemplate;

    private final TransactionTemplate transactionTemplate;

    public EmployeesService(PlatformTransactionManager transactionManager) {
      transactionTemplate = new TransactionTemplate(transactionManager);
      transactionTemplate.setTimeout(Integer.MAX_VALUE);
    }

    public boolean save(Employees employees) {
      Integer result = transactionTemplate.execute(status -> {
        String sql = "INSERT INTO employees(emp_no, birth_date, first_name," +
            " last_name, gender, hire_date) VALUES (?, ?, ?, ?, ?, ?)";
        int updatedCount = jdbcTemplate.update(sql, employees.empNo,
            employees.birthDate, employees.firstName, employees.lastName,
            employees.gender, employees.hireDate);
        return updatedCount;
      });
      return result == 1;
    }
  }
}

TransactionTemplate with rollbackFor - Stack Overflow 中介绍了一种通过在 try-catch 中调用 org.springframework.transaction.TransactionExecution.setRollbackOnly() 方法来实现回滚的操作。但是,时机并不需要。可以看一下 org.springframework.transaction.support.TransactionTemplate.execute 的代码就明白了:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Override
@Nullable
public <T> T execute(TransactionCallback<T> action) throws TransactionException {
	Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
	// 内部封装好的事务管理器
	if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager cpptm) {
		return cpptm.execute(this, action);
	} // 下面是需要手动获取事务,执行方法,提交事务的管理器
	else {
		// 1. 获取事务状态
		TransactionStatus status = this.transactionManager.getTransaction(this);
		T result;
		try {
			// 2. 执行业务逻辑
			result = action.doInTransaction(status);
		}
		catch (RuntimeException | Error ex) {
			// Transactional code threw application exception -> rollback
			// RuntimeException 或 Error -> 回滚
			// TODO 在 @Transactional(rollbackFor = Throwable.class) 中指定了回滚异常,怎么生效?
			rollbackOnException(status, ex);
			throw ex;
		}
		catch (Throwable ex) {
			// Transactional code threw unexpected exception -> rollback
			// 未知异常 -> 回滚
			rollbackOnException(status, ex);
			throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
		}
		// 3. 事务提交
		this.transactionManager.commit(status);
		return result;
	}
}

代码中,直接 catch (Throwable ex),相信大家都了解, Throwable 是 Java 异常类的基类,只要 catch 它,任何在前面没有处理的异常,都会在这里处理,这相当于使用了 Throwable 做了兜底。

当然,如果有异常需要特殊处理,还是可以这样做的。多说一句,由于会先执行这个回调函数,所以,在这里捕获异常,会优先处理。这里处理完,接着往外抛异常,TransactionTemplate 中的异常处理机制就可以处理异常了。

TxTest
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
package com.diguage.truman.tx;

import com.mysql.cj.jdbc.Driver;
import com.zaxxer.hikari.HikariDataSource;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.Transactional;

import javax.sql.DataSource;
import java.util.Date;

/**
 * @author D瓜哥, https://www.diguage.com/
 * @since 2020-09-11 10:20
 */
public class TxTest {

  @Test
  public void test() {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
    applicationContext.register(Config.class);
    applicationContext.refresh();
    EmployeesService employeesService = applicationContext.getBean(EmployeesService.class);
    Employees employees = employeesService.getById(10001);
    System.out.println(employees);
  }

  @Configuration
  @EnableTransactionManagement
  @Import(EmployeesService.class)
  public static class Config {
    @Bean
    public DataSource dataSource() {
      HikariDataSource dataSource = new HikariDataSource();
      dataSource.setUsername("root");
      dataSource.setPassword("123456");
      dataSource.setDriverClassName(Driver.class.getName());
      dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/employees?useUnicode=true" +
          "&characterEncoding=utf-8&autoReconnectForPools=true&autoReconnect=true");
      return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
      return new JdbcTemplate(dataSource);
    }

    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
      return new DataSourceTransactionManager(dataSource);
    }
  }

  public static class Employees {
    Integer empNo;
    Date birthDate;
    String firstName;
    String lastName;
    String gender;
    Date hireDate;

    @Override
    public String toString() {
      return "Employees{" +
          "empNo=" + empNo +
          ", birthDate=" + birthDate +
          ", firstName='" + firstName + '\'' +
          ", lastName='" + lastName + '\'' +
          ", gender='" + gender + '\'' +
          ", hireDate=" + hireDate +
          '}';
    }
  }

  @Service
  public static class EmployeesService {
    @Resource
    private JdbcTemplate jdbcTemplate;

    @Transactional(readOnly = true, rollbackFor = Throwable.class)
    public Employees getById(Integer id) {
      String sql = "SELECT * FROM employees WHERE emp_no = ?";

      return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> {
        Employees employee = new Employees();
        employee.empNo = rs.getInt("emp_no");
        employee.birthDate = rs.getDate("birth_date");
        employee.firstName = rs.getString("first_name");
        employee.lastName = rs.getString("last_name");
        employee.gender = rs.getString("gender");
        employee.hireDate = rs.getDate("hire_date");
        return employee;
      }, new Object[]{id});
    }
  }
}
TxOnCloseTest
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
package com.diguage.truman.tx;

import com.mysql.cj.jdbc.Driver;
import com.zaxxer.hikari.HikariDataSource;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import javax.sql.DataSource;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * @author D瓜哥, https://www.diguage.com/
 * @since 2020-09-11 10:20
 */
public class TxOnCloseTest {

  /**
   * TODO 这个实验还没搞好!
   */
  @Test
  public void test() {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
    applicationContext.register(Config.class);
    applicationContext.refresh();
    EmployeesService employeesService = applicationContext.getBean(EmployeesService.class);
    PlatformTransactionManager transactionManager = applicationContext.getBean(PlatformTransactionManager.class);

    Employees employees = new Employees();
    employees.empNo = (int) System.currentTimeMillis() / 1000;
    employees.birthDate = new Date();
    employees.firstName = "trans";
    employees.lastName = "action";
    employees.gender = "F";
    employees.hireDate = new Date();
    TransactionStatus transactionStatus = transactionManager.getTransaction(new DefaultTransactionDefinition());
    employeesService.save(employees);
    ExecutorService executorService = Executors.newFixedThreadPool(1);
    executorService.execute(() -> applicationContext.close());
    try {
      TimeUnit.SECONDS.sleep(300);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    transactionManager.commit(transactionStatus);

    System.out.println(employees);
  }

  @Configuration
  @EnableTransactionManagement
  @Import(EmployeesService.class)
  public static class Config {
    @Bean
    public DataSource dataSource() {
      // TODO 设置超时时间,事务超时时间以及数据库里面的超时时间。
      HikariDataSource dataSource = new HikariDataSource();
      dataSource.setUsername("root");
      dataSource.setPassword("123456");
      dataSource.setDriverClassName(Driver.class.getName());
      dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/employees?useUnicode=true" +
          "&characterEncoding=utf-8&autoReconnectForPools=true&autoReconnect=true");
      return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
      return new JdbcTemplate(dataSource);
    }

    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
      return new DataSourceTransactionManager(dataSource);
    }
  }

  public static class Employees {
    Integer empNo;
    Date birthDate;
    String firstName;
    String lastName;
    String gender;
    Date hireDate;

    @Override
    public String toString() {
      return "Employees{" +
          "empNo=" + empNo +
          ", birthDate=" + birthDate +
          ", firstName='" + firstName + '\'' +
          ", lastName='" + lastName + '\'' +
          ", gender='" + gender + '\'' +
          ", hireDate=" + hireDate +
          '}';
    }
  }

  @Service
  public static class EmployeesService {
    @Resource
    private JdbcTemplate jdbcTemplate;

    public int save(Employees employees) {
      String sql = "INSERT INTO employees(emp_no, birth_date, first_name," +
          " last_name, gender, hire_date) VALUE (?, ?, ?, ?, ?, ?)";
      return jdbcTemplate.update(sql, employees.empNo, employees.birthDate, employees.firstName, employees.lastName, employees.gender, employees.hireDate);
    }
  }
}

+ :leveloffset: +1

Spring MVC

通过 org.springframework.web.servlet.HttpServletBean.init 方法开始初始化容器的动作,再进一步委托给 org.springframework.web.servlet.FrameworkServlet.initServletBean 方法完成初始化容器。

这个操作和 org.springframework.web.context.ContextLoaderListener.contextInitialized 启动 Spring 容器有什么区别?

Diagram
Diagram

6. 整合 Apache Dubbo

在上一篇文章 Spring 扩展点概览及实践 中介绍了 Spring 内部存在的扩展点。 Spring 扩展点实践:整合 MyBATIS 中,D瓜哥带大家了解了一下 MyBATIS 如何利用 Spring 的扩展点实现了与 Spring 的完美整合。现在,学以致用,我们继续来分析一下 Spring 与 Apache Dubbo 的整合流程。

6.1. 示例程序

Apache Dubbo 仓库中就有很完整的示例。D瓜哥直接拿来使用就不再搭建示例程序了。

首先,需要启动一个 ZooKeeper 实例。查看 Dubbo 的依赖可以看出,最新版代码依赖的 ZooKeeper 是 3.4.13 版。所以,为了最好的兼容性,就要选用 3.4.X 版的 ZooKeeper 服务器。D瓜哥直接使用 Docker 启动 ZooKeeper 了。命令如下:

1
docker run --rm --name zookeeper -d -p 2181:2181 zookeeper:3.4.14

这次我们使用 Apache Dubbodubbo-demo/dubbo-demo-xml 示例。

第二步,启动服务提供者程序,找到 DUBBO/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-provider/src/main/java/org/apache/dubbo/demo/provider/Application.java,运行该类。

第三步,运行服务消费者程序,找到 DUBBO/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-consumer/src/main/java/org/apache/dubbo/demo/consumer/Application.java,运行该类。

如果没有任何错误,则在终端可以看到 result: async result 输出。

在开始正餐之前,D瓜哥先给大家来个开胃菜。

6.2. Spring 插件机制简介

不知道大家有没有想过一个问题:Spring 框架是如何支持越来越多的功能的?

在D瓜哥了解到 Spring 的插件机制后,非常叹服 Spring 精巧的设计和灵活的扩展性。闲言少叙,好戏上演。

这里再问大家一个问题:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           https://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/aop
                           https://www.springframework.org/schema/aop/spring-aop.xsd
                           http://www.springframework.org/schema/context
                           https://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/tx
                           https://www.springframework.org/schema/tx/spring-tx.xsd
                           http://dubbo.apache.org/schema/dubbo
                           http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

	<context:annotation-config/>

	<tx:annotation-driven proxy-target-class="true" order="0"/>

	<aop:config>
		<aop:advisor pointcut="execution(* *..ITestBean.*(..))" advice-ref="txAdvice"/>
	</aop:config>

	<tx:advice id="txAdvice">
		<tx:attributes>
			<tx:method name="get*" timeout="5" read-only="true"/>
			<tx:method name="set*"/>
			<tx:method name="exceptional"/>
		</tx:attributes>
	</tx:advice>


	<bean id="transactionManager" class="org.springframework.transaction.testfixture.CallCountingTransactionManager"/>

	<bean id="testBean" class="org.springframework.beans.testfixture.beans.TestBean"/>

</beans>

这是非常典型的 Spring XML 配置。相信大家都见过。大家有没有想过,Spring 是怎么处理这些不同的命名空间的?如果说 AOP、事务这些是 Spring 内置支持的功能,这样配置,Spring 可以正确解析。但是,Dubbo 的配置又是怎么回事?

要回答这个问题,就要说起 Spring 的插件机制。在 Spring 的插件机制面前,无论是 Dubbo,还是 Spring 的 AOP、事务管理都是人人平等的。它们都是依靠 Spring 的插件机制插拔在 Spring 核心模块之上的。

这篇文章不是专门介绍 Spring 插件机制的。这里抛砖引玉,对 Spring 插件机制做个简介。后续有机会再做更详细的介绍和说明。

要利用 Spring 插件机制,需要做这么几个事情:

  1. 定义自己业务的类。

  2. 编写 XSD 文件,定义自己的 XML 格式,将文件放在 src/main/resources/META-INF 目录下。

  3. 针对每一个标签,定义一个实现 BeanDefinitionParser 接口的类,在 parse 方法中完成对这个标签的解析工作,将其转化成一个 BeanDefinition 对象。

  4. 继承 NamespaceHandlerSupport 类,在 init() 方法中,使用 registerBeanDefinitionParser() 将标签名称和上面写的 BeanDefinitionParser 实现类之间建起起对应关系。

  5. 创建 src/main/resources/META-INF/spring.schemas 文件,在其中写上: http\://www.diguage.com/schema/diguage/diguage.xsd=META-INF/diguage.xsd,为该 XSD 文件定义唯一的命名空间。

  6. 创建 src/main/resources/META-INF/spring.handlers 文件,在其中写上: http\://www.diguage.com/schema/diguage=com.diguage.schema.DiguageNamespaceHandler

完成上面这些步骤就相当于制作了一个 Spring 插件。这样就可以在 Spring XML 配置文件中,像使用 AOP、事务管理那样来使用这个新插件了。

仔细想想,Spring 的插件机制还是挺简单的:首先,定义一个 Bean 类,然后设计 XSD 文件来对 Bean 的属性进行定义。用户在使用插件时,使用 XML 来定义 Bean 类的属性值,再自定义的 BeanDefinitionParser 实现类将 XML 中的配置信息解析出来,封装在 BeanDefinition(关于 BeanDefinition 的更多信息,请移步 深入剖析 Spring 核心数据结构:BeanDefinition)。到了 BeanDefinition 之后,Spring 在内部就可以统一处理了。

下面,结合代理来具体说明一下 Apache Dubbo 的实现过程。

6.3. Apache Dubbo 插件机制解析

Apache Dubbo 最初就说通过 Spring 插件机制实现了它与 Spring 的整合过程。

  1. 相关业务类有 ApplicationConfigModuleConfigRegistryConfigConfigCenterBeanMetadataReportConfigMonitorConfigMetricsConfigSslConfigProviderConfigConsumerConfigProtocolConfigServiceBeanReferenceBean。这些类的命名也都非常讲究,见文知意,与 Dubbo 常见配置可以说是一一对应。

  2. Dubbo 的 XSD 定义在 dubbo.xsd,懂 XSD 的朋友应该都能看出来,这个文件就是规范上一步提到的类的属性的。

  3. DubboBeanDefinitionParser 实现了 BeanDefinitionParser 接口,用于解析 XML 配置,并将其“翻译”为第一步中那些类的对象。另外,还注册了一个 AnnotationBeanDefinitionParser,用来处理 annotation 标签,进而用来处理注解。

  4. DubboNamespaceHandler 继承了 NamespaceHandlerSupport,并且在 init() 方法中完成了对上述类的 DubboBeanDefinitionParser 注册。

  5. dubbo-config/dubbo-config-spring/src/main/resources/META-INF 目录下,有 spring.schemas 文件和 spring.handlers 文件。

下面以调试跟进的方式来分析整个处理过程。

6.4. Apache Dubbo 配置解析

这里使用示例程序中的配置文件:

dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-provider/src/main/resources/spring/dubbo-provider.xml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://dubbo.apache.org/schema/dubbo
                           http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    <dubbo:application metadata-type="remote" name="demo-provider"/>

    <dubbo:metadata-report address="zookeeper://127.0.0.1:2181"/>

    <dubbo:registry address="zookeeper://127.0.0.1:2181"/>

    <dubbo:protocol name="dubbo"/>

    <bean id="demoService" class="org.apache.dubbo.demo.provider.DemoServiceImpl"/>

    <dubbo:service interface="org.apache.dubbo.demo.DemoService" ref="demoService"/>
</beans>

org.apache.dubbo.config.spring.schema.DubboNamespaceHandler#init 方法、 org.apache.dubbo.config.spring.schema.DubboNamespaceHandler#parse 方法 和 org.apache.dubbo.config.spring.schema.DubboBeanDefinitionParser#parse(Element, ParserContext) 方法打断点开始调试。注意:这三个方法都是重载方法,很容易识别。

打好断点后重启服务提供者程序,程序会在 init() 方法处暂停:

org.apache.dubbo.config.spring.schema.DubboNamespaceHandler#init
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@Override
public void init() {
    registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
    registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
    registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
    registerBeanDefinitionParser("config-center", new DubboBeanDefinitionParser(ConfigCenterBean.class, true));
    registerBeanDefinitionParser("metadata-report", new DubboBeanDefinitionParser(MetadataReportConfig.class, true));
    registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
    registerBeanDefinitionParser("metrics", new DubboBeanDefinitionParser(MetricsConfig.class, true));
    registerBeanDefinitionParser("ssl", new DubboBeanDefinitionParser(SslConfig.class, true));
    registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
    registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
    registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
    registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
    registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
    registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
}

从这里可以明显看到,都注册哪些 BeanDefinitionParser,都需要处理哪些标签。点击 registerBeanDefinitionParser 方法就可以看出,所谓的“注册”其实就是将它们放在了 org.springframework.beans.factory.xml.NamespaceHandlerSupport#Map<String, BeanDefinitionParser> parsers 变量中。

这里不要深究,继续向下执行,就会到了 DubboNamespaceHandler#parse 方法:

org.apache.dubbo.config.spring.schema.DubboNamespaceHandler#parse
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
    BeanDefinitionRegistry registry = parserContext.getRegistry();
    registerAnnotationConfigProcessors(registry);
    /**
     * @since 2.7.8
     * issue : https://github.com/apache/dubbo/issues/6275
     */
    registerCommonBeans(registry);
    BeanDefinition beanDefinition = super.parse(element, parserContext);
    setSource(beanDefinition);
    return beanDefinition;
}

这里,我们需要注意的是 registerCommonBeans(registry) 方法:

org.apache.dubbo.config.spring.util.DubboBeanUtils#registerCommonBeans
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
 * Register the common beans
 *
 * @param registry {@link BeanDefinitionRegistry}
 * @see ReferenceAnnotationBeanPostProcessor
 * @see DubboConfigDefaultPropertyValueBeanPostProcessor
 * @see DubboConfigAliasPostProcessor
 * @see DubboLifecycleComponentApplicationListener
 * @see DubboBootstrapApplicationListener
 */
static void registerCommonBeans(BeanDefinitionRegistry registry) {

    // Since 2.5.7 Register @Reference Annotation Bean Processor as an infrastructure Bean
    registerInfrastructureBean(registry, ReferenceAnnotationBeanPostProcessor.BEAN_NAME,
            ReferenceAnnotationBeanPostProcessor.class);

    // Since 2.7.4 [Feature] https://github.com/apache/dubbo/issues/5093
    registerInfrastructureBean(registry, DubboConfigAliasPostProcessor.BEAN_NAME,
            DubboConfigAliasPostProcessor.class);

    // Since 2.7.5 Register DubboLifecycleComponentApplicationListener as an infrastructure Bean
    registerInfrastructureBean(registry, DubboLifecycleComponentApplicationListener.BEAN_NAME,
            DubboLifecycleComponentApplicationListener.class);

    // Since 2.7.4 Register DubboBootstrapApplicationListener as an infrastructure Bean
    registerInfrastructureBean(registry, DubboBootstrapApplicationListener.BEAN_NAME,
            DubboBootstrapApplicationListener.class);

    // Since 2.7.6 Register DubboConfigDefaultPropertyValueBeanPostProcessor as an infrastructure Bean
    registerInfrastructureBean(registry, DubboConfigDefaultPropertyValueBeanPostProcessor.BEAN_NAME,
            DubboConfigDefaultPropertyValueBeanPostProcessor.class);
}

这里需要重点关注的是 ReferenceAnnotationBeanPostProcessorDubboBootstrapApplicationListener,前者设计到 Dubbo 注解的处理,后者着牵涉整个 Dubbo 的启动。先在 DubboBootstrapApplicationListeneronApplicationContextEvent 方法上打上断点。后续涉及到时,再具体分析。

然后,我们单步调试,跟进 BeanDefinition beanDefinition = super.parse(element, parserContext); 这个调用中:

org.springframework.beans.factory.xml.NamespaceHandlerSupport
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
 * Parses the supplied {@link Element} by delegating to the {@link BeanDefinitionParser} that is
 * registered for that {@link Element}.
 */
@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
	// 获取元素解析器
	BeanDefinitionParser parser = findParserForElement(element, parserContext);
	return (parser != null ? parser.parse(element, parserContext) : null);
}

/**
 * Locates the {@link BeanDefinitionParser} from the register implementations using
 * the local name of the supplied {@link Element}.
 */
@Nullable
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
	String localName = parserContext.getDelegate().getLocalName(element);
	BeanDefinitionParser parser = this.parsers.get(localName);
	if (parser == null) {
		parserContext.getReaderContext().fatal(
				"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
	}
	return parser;
}

结合上面的 init(),上面是“放”,现在是根据标签名称来“拿”。这样就找到每个标签对应的 BeanDefinitionParser。这些 BeanDefinitionParser 的作用就是处理对应的标签并将其转化为 BeanDefinition

Dubbo XML 配置的解析就这么些,后续的过程要依赖 Spring 的流程了。

6.5. Dubbo 暴露服务提供者的过程

让程序继续执行,就到了我们上面打断点的地方: DubboBootstrapApplicationListener#onApplicationContextEvent。一路单步调试跟下去,就到了 DubboBootstrap#start 方法。到这一步,Dubbo 就开始启动了。

start() 方法中,调用了 DubboBootstrap#initialize 方法,这个方法就有点像 Spring 的 AbstractApplicationContext#refresh 方法。如果分析 Dubbo 的源代码,这必定是一个好的入口。在 initialize() 方法中,Dubbo 完成了以下功能:

  1. initFrameworkExts() — 初始化框架

  2. startConfigCenter() — 启动配置中心

  3. loadRemoteConfigs() — 加载远程配置

  4. checkGlobalConfigs() — 检查全局配置

  5. startMetadataCenter() — 开始元数据中心,这里特别标明是从 2.7.8 开始的。

  6. initMetadataService() — 初始化元数据服务

  7. initMetadataServiceExports() — 初始化元数据服务导出

  8. initEventListener() — 初始化时间监听。

暂时没有深入研究这些方法的实现。说明也都是直译的方法名。

继续向下执行,进入 DubboBootstrap#exportServices 方法:

org.apache.dubbo.config.bootstrap.DubboBootstrap#exportServices
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
private void exportServices() {
    configManager.getServices().forEach(sc -> {
        // TODO, compatible with ServiceConfig.export()
        ServiceConfig serviceConfig = (ServiceConfig) sc;
        serviceConfig.setBootstrap(this);

        if (exportAsync) {
            ExecutorService executor = executorRepository.getServiceExporterExecutor();
            Future<?> future = executor.submit(() -> {
                sc.export();
                exportedServices.add(sc);
            });
            asyncExportingFutures.add(future);
        } else {
            sc.export();
            exportedServices.add(sc);
        }
    });
}

在这里可以清楚看到,Dubbo 通过 org.apache.dubbo.config.ServiceConfig#export 方法把服务暴露到注册中心的。由于这不是 Dubbo 源码分析,所以,实现细节就不再介绍了。

不知道大家有没有一个疑问:这里的 configManager.getServices() 是如何获取带业务实现类对象呢?

要回答这个问题,需要查看一下 configManager.getServices() 返回的是 Collection<ServiceConfigBase> 对象。我们就从 ServiceConfigBase 上找原因。经过研究发现, ServiceConfigBaseorg.apache.dubbo.config.AbstractConfig 的子类,而 AbstractConfig 中有一个 addIntoConfigManager 方法如下:

org.apache.dubbo.config.AbstractConfig#addIntoConfigManager
1
2
3
4
@PostConstruct
public void addIntoConfigManager() {
    ApplicationModel.getConfigManager().addConfig(this);
}

阅读过 Spring Bean 生命周期概述 文章的朋友应该都清楚,使用 @PostConstruct 的方法会在 Bean 创建过程中,由 AbstractAutowireCapableBeanFactory#invokeInitMethods 方法来统一调用。所以,如果在上面这个方法中打断点,就可以看到调用过程了。

另外,这里给大家介绍一个小技巧:追本溯源,现在开始。从上面的 configManager.getServices() 开始,一步一步打开源代码就会发现, 这些数据是从 org.apache.dubbo.config.context.ConfigManager#configsCache 变量中获取的,那就在这个类中搜 configsCache,找到向这个变量添加元素的地方,会找到如下方法:

org.apache.dubbo.config.context.ConfigManager#addConfig(AbstractConfig, boolean)
1
2
3
4
5
6
7
8
9
protected void addConfig(AbstractConfig config, boolean unique) {
    if (config == null) {
        return;
    }
    write(() -> {
        Map<String, AbstractConfig> configsMap = configsCache.computeIfAbsent(getTagName(config.getClass()), type -> newMap());
        addIfAbsent(config, configsMap, unique);
    });
}

而且,整个类中,这一个地方是向 configsCache 变量添加元素的。在这个类打断点,你就看到所有添加的变量信息。再次启动服务提供者程序,你会发现上面提到的相关业务类 ApplicationConfigModuleConfigRegistryConfigConfigCenterBeanMetadataReportConfigMonitorConfigMetricsConfigSslConfigProviderConfigConsumerConfigProtocolConfigServiceBeanReferenceBean 都是 AbstractConfig 的子类。换句话说,这些类的实例都会注册到 ConfigManager 中。

洋洋洒洒又写了好长好长。还有很多东西没写呢,比如 Dubbo 注解的集成实现,Dubbo 服务消费者的创建过程。限于篇幅原因,这些内容就放在下一篇文章介绍。

6.6. Dubbo 生成服务消费者的过程

先来看看 XML 配置文件:

dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-consumer/src/main/resources/spring/dubbo-consumer.xml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://dubbo.apache.org/schema/dubbo
                           http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    <dubbo:application name="demo-consumer"/>

    <dubbo:registry address="zookeeper://127.0.0.1:2181"/>

    <dubbo:reference id="demoService" check="false" interface="org.apache.dubbo.demo.DemoService"/>

</beans>

我们先看一下 ReferenceBean 类的声明:

org.apache.dubbo.config.spring.ReferenceBean
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class ReferenceBean<T> extends ReferenceConfig<T> implements FactoryBean,
        ApplicationContextAware, InitializingBean, DisposableBean {

    // 此处省略 N 行代码

    @Override
    public Object getObject() {
        return get();
    }

    // 此处省略 N 行代码

    @Override
    @SuppressWarnings({"unchecked"})
    public void afterPropertiesSet() throws Exception {

        // Initializes Dubbo's Config Beans before @Reference bean autowiring
        prepareDubboConfigBeans();

        // lazy init by default.
        if (init == null) {
            init = false;
        }

        // eager init if necessary.
        if (shouldInit()) {
            getObject();
        }
    }

    // 此处省略 N 行代码
}

这个类实现了 FactoryBean 接口,D瓜哥在 Spring 扩展点概览及实践:FactoryBean 中对 FactoryBean 介绍。所以,请在上面的 getObject() 打个断点。

另外,这个类还实现了 InitializingBean,D瓜哥在 Spring Bean 生命周期概述 中介绍了这个接口的用途。不了解的,请移步。

启动服务消费者程序,开始调试代码。跳过上文结束的配置解析阶段,进入到 org.apache.dubbo.config.bootstrap.DubboBootstrap#start 方法中。在这里,它调用了内部私有方法 referServices()。但是,这个方法其实啥也没做。

上面提到,ReferenceBean 实现了 FactoryBean 接口,那么直接在 org.apache.dubbo.config.spring.ReferenceBean#getObject 方法上打断点。当调用 applicationContext.getBean(XXX) 时,就会触发断点,一路跟下去就会发现,现在 org.apache.dubbo.config.ReferenceConfig#init 方法中完成各种初始化准备工作,然后调用 org.apache.dubbo.config.ReferenceConfig#createProxy 方法创建代理。而实际代理的创建工作是由 org.apache.dubbo.rpc.proxy.AbstractProxyFactory#getProxy(Invoker<T>, boolean) 方法创建的。这样说,也不算准确。因为 AbstractProxyFactory 对象是一个子类对象,子类是通过 Dubbo 的类 SPI 加载机制来动态选择创建的。

其实,Dubbo 服务消费者实例只是一个代理,通过代理封装统一的网络请求,实现 RPC 的调用过程。

6.7. Dubbo 注解集成简述

使用 Dubbo 注解集成的入口是 org.apache.dubbo.config.spring.context.annotation.EnableDubbo,直接上代码:

org.apache.dubbo.config.spring.context.annotation.EnableDubbo
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/**
 * Enables Dubbo components as Spring Beans, equals
 * {@link DubboComponentScan} and {@link EnableDubboConfig} combination.
 * <p>
 * Note : {@link EnableDubbo} must base on Spring Framework 4.2 and above
 *
 * @see DubboComponentScan
 * @see EnableDubboConfig
 * @since 2.5.8
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@EnableDubboConfig
@DubboComponentScan
public @interface EnableDubbo {

    /**
     * Base packages to scan for annotated @Service classes.
     * <p>
     * Use {@link #scanBasePackageClasses()} for a type-safe alternative to String-based
     * package names.
     *
     * @return the base packages to scan
     * @see DubboComponentScan#basePackages()
     */
    @AliasFor(annotation = DubboComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};

    /**
     * Type-safe alternative to {@link #scanBasePackages()} for specifying the packages to
     * scan for annotated @Service classes. The package of each class specified will be
     * scanned.
     *
     * @return classes from the base packages to scan
     * @see DubboComponentScan#basePackageClasses
     */
    @AliasFor(annotation = DubboComponentScan.class, attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};


    /**
     * It indicates whether {@link AbstractConfig} binding to multiple Spring Beans.
     *
     * @return the default value is <code>true</code>
     * @see EnableDubboConfig#multiple()
     */
    @AliasFor(annotation = EnableDubboConfig.class, attribute = "multiple")
    boolean multipleConfig() default true;

}

这个注解非常重要。一共有两点需要注意。这个方法就是注解的三个属性,分别给出了三个最重要的参数:

  1. scanBasePackages — 定义了基础扫描的包。通过 @AliasFor 注解表明,这是定义 @DubboComponentScan 注解的 basePackages 属性。

  2. scanBasePackageClasses — 定义扫描的基础类。通过 @AliasFor 注解表明,这是定义 @DubboComponentScan 注解的 basePackageClasses 属性。

  3. multipleConfig — 可以将 AbstractConfig(上一篇文章 Spring 扩展点实践:整合 Apache Dubbo(一) 已经做过说明) 向 Spring 中多次注册。换句话说,你可以配置多个注册中心,配置多个监控中心等等。通过 @AliasFor 注解表明,这是定义 @EnableDubboConfig 注解的 multiple 属性,默认为 true

接下来,让我们看看非常重要的两点内容。

6.7.1. @EnableDubboConfig

@EnableDubbo 注解上面加了 @EnableDubboConfig 注解,我们来看一下它的源码:

org.apache.dubbo.config.spring.context.annotation.EnableDubboConfig
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Import(DubboConfigConfigurationRegistrar.class)
public @interface EnableDubboConfig {

    /**
     * It indicates whether binding to multiple Spring Beans.
     *
     * @return the default value is <code>true</code>
     * @revised 2.5.9
     */
    boolean multiple() default true;

}

这里,我们看到了熟悉的 @ImportDubboConfigConfigurationRegistrar 从名字就能看出应该是实现了 ImportBeanDefinitionRegistrar 接口的,打开代码,果然如此。更

Spring 扩展点概览及实践Spring 扩展点实践:整合 MyBATIS 中有针对 @ImportImportBeanDefinitionRegistrar 的详细介绍。尤其是 MyBATIS 就是使用 ImportBeanDefinitionRegistrar 来做扩展的。不懂的,请移步。

关于 DubboConfigConfigurationRegistrar 的功能,这里做个简要总结:

  1. 使用 @EnableConfigurationBeanBindings 注解,将配置项和对一个的 Bean 类型做一个绑定。如果 multiple 属性为 true,则指出多次注册。

  2. 调用 org.apache.dubbo.config.spring.util.DubboBeanUtils#registerCommonBeans 方法,将公共的 Bean 注册到 Spring 中。这部分内容在 Spring 扩展点实践:整合 Apache Dubbo(一):registerCommonBeans 中已经给出了详细介绍,就不再赘述。

6.7.2. @DubboComponentScan

@EnableDubbo 注解上面加了 @DubboComponentScan 注解,直接上代码:

org.apache.dubbo.config.spring.context.annotation.DubboComponentScan
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(DubboComponentScanRegistrar.class)
public @interface DubboComponentScan {

    /**
     * Alias for the {@link #basePackages()} attribute. Allows for more concise annotation
     * declarations e.g.: {@code @DubboComponentScan("org.my.pkg")} instead of
     * {@code @DubboComponentScan(basePackages="org.my.pkg")}.
     *
     * @return the base packages to scan
     */
    String[] value() default {};

    /**
     * Base packages to scan for annotated @Service classes. {@link #value()} is an
     * alias for (and mutually exclusive with) this attribute.
     * <p>
     * Use {@link #basePackageClasses()} for a type-safe alternative to String-based
     * package names.
     *
     * @return the base packages to scan
     */
    String[] basePackages() default {};

    /**
     * Type-safe alternative to {@link #basePackages()} for specifying the packages to
     * scan for annotated @Service classes. The package of each class specified will be
     * scanned.
     *
     * @return classes from the base packages to scan
     */
    Class<?>[] basePackageClasses() default {};

}

又双叒叕看到了 @Import;又双叒叕看到了 Registrar,只是这次名字叫 DubboComponentScanRegistrar。跟上面的一样,不再赘述。

这里总结一下 DubboComponentScanRegistrar 的功能:注册了一个类为 ServiceAnnotationBeanPostProcessorBeanDefinition,将配置项的配置信息传递给这个 BeanDefinition 实例。 ServiceAnnotationBeanPostProcessor 实现了 BeanDefinitionRegistryPostProcessor 接口,会在 Spring 的启动过程中,通过调用 postProcessBeanDefinitionRegistry 方法来注册相关的 BeanDefinition。关于这部分内容,请移步: Spring AOP 处理流程概述

在 Spring 启动过程中,就会调用 ServiceAnnotationBeanPostProcessorpostProcessBeanDefinitionRegistry 方法,在这个方法中,通过创建 DubboClassPathBeanDefinitionScanner (继承了 ClassPathBeanDefinitionScanner 类)实例,调用 scanner.scan(packageToScan) 来注册 BeanDefinition。另外,有一点需要指出的是: ServiceAnnotationBeanPostProcessor 目前是 @Deprecated,后续推荐使用 ServiceClassPostProcessor,而 ServiceAnnotationBeanPostProcessor 就是 ServiceClassPostProcessor 的子类。所以,目前处理逻辑都集中在了 ServiceClassPostProcessor 中。

关于 Apache Dubbo 与 Spring 的整合原理就全部介绍完毕了。如有什么问题,欢迎留言讨论。以后有时间,写写分布式事务解决方案 Seata 的一些原理。

Appendix A: Spring 奇技淫巧

这里记录一些不太常见的使用技巧。

A.1. 创建同步的 Set 实例

1
Set<String> sets = Collections.newSetFromMap(new ConcurrentHashMap<>(256));

A.2. 注入静态属性

1
2
3
4
5
6
7
8
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property name="staticMethod" value="com.diguage.web.util.SessionUtil.setLocationService"/>
    <property name="arguments">
        <list>
            <ref bean="locationService"/>
        </list>
    </property>
</bean>
1
2
3
4
5
6
7
8
public class SessionUtil {

  private static LocationService locationService;

  public static void setLocationService(LocationService ls) {
    locationService = ls;
  }
}

A.3. 性能监视器

引入依赖
1
2
3
4
5
<dependency>
    <groupId>com.jamonapi</groupId>
    <artifactId>jamon</artifactId>
    <version>2.81</version>
</dependency>
增加 Spring 配置
1
2
3
4
5
6
7
8
<bean id="jamonPerformAdvice" class="org.springframework.aop.interceptor.JamonPerformanceMonitorInterceptor"/>

<aop:config>
    <aop:pointcut id="allServiceMethods" expression="execution(* com.diguage.durian.impl..*(..))"/>
    <aop:pointcut id="allDaoMethods" expression="execution(* com.diguage.durian.dao..*(..))"/>
    <aop:advisor advice-ref="jamonPerformAdvice" pointcut-ref="allServiceMethods"/>
    <aop:advisor advice-ref="jamonPerformAdvice" pointcut-ref="allDaoMethods"/>
</aop:config>
增加日志配置
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<appender name="performanceLogs" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>/tmp/durian_performance.log</file>
    <!-- 根据文件大小保存日志文件 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
        <fileNamePattern>/tmp/durian_performance.%i.log.zip</fileNamePattern>
        <minIndex>1</minIndex>
        <maxIndex>2</maxIndex>
    </rollingPolicy>
    <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
        <maxFileSize>100MB</maxFileSize>
    </triggeringPolicy>

    <encoder>
        <pattern>%date [%thread] %-5level %logger{72} - %msg%n</pattern>
    </encoder>
</appender>

<logger name="org.springframework.aop.interceptor.JamonPerformanceMonitorInterceptor" level="TRACE" additivity="false">
    <appender-ref ref="performanceLogs"/>
</logger>
注意 JamonPerformanceMonitorInterceptor 的日志级别。线上环境的输入级别一般是 INFO ,如果不配置,则这里的日志直接被过滤掉了。

A.4. 生命周期回调

  1. @PostConstruct

  2. @PreDestroy

  3. InitializingBean

  4. DisposableBean

  5. init-method

  6. destroy-method

A.5. 定时调度

Spring Quartz 配置
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 配置方法映射工厂类 -->
    <bean id="updateSnapInventory" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
        <property name="jobClass" value="com.diguage.durian.schedule.SnapInventoryQuartzJobBean"/>
        <property name="durability" value="true"/>
        <property name="requestsRecovery" value="true" />
    </bean>
    <!-- 配置任务调度的的时间/周期 -->
    <bean id="updateSnapInventoryTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
        <property name="jobDetail" ref="updateSnapInventory"/>
        <property name="cronExpression" value="0 */10 * * * ?"/>
    </bean>

    <bean id="schedulerFactoryBean" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!--可选,QuartzScheduler 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了 -->
        <property name="overwriteExistingJobs" value="true" />
        <!--必须的,QuartzScheduler 延时启动,应用启动完后 QuartzScheduler 再启动 -->
        <property name="startupDelay" value="300" />
        <!-- 设置自动启动 -->
        <property name="autoStartup" value="true" />
        <property name="applicationContextSchedulerContextKey" value="applicationContext" />
        <property name="configLocation" value="classpath:spring-quartz.properties" />
        <property name="triggers">
            <list>
                <ref bean="updateSnapInventoryTrigger"/>
                <ref bean="cancelTopTrigger"/>
            </list>
        </property>
    </bean>
</beans>
spring-quartz.properties
#==============================================================
#Configure Main Scheduler Properties
#==============================================================
org.quartz.scheduler.instanceName = quartzScheduler
org.quartz.scheduler.instanceId = AUTO

#============================================================================
# Configure ThreadPool
#============================================================================

org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 5
org.quartz.threadPool.threadPriority = 5

#============================================================================
# Configure JobStore
#============================================================================

org.quartz.jobStore.misfireThreshold = 60000

org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.useProperties = false
org.quartz.jobStore.dataSource = dataSource
org.quartz.jobStore.tablePrefix = QRTZ_

org.quartz.jobStore.isClustered = true
org.quartz.jobStore.clusterCheckinInterval = 20000

#============================================================================
# Configure Datasources
#============================================================================

#org.quartz.dataSource.myDS.driver = oracle.jdbc.driver.OracleDriver
#org.quartz.dataSource.myDS.URL = jdbc:oracle:thin:@polarbear:1521:dev
#org.quartz.dataSource.myDS.user = quartz
#org.quartz.dataSource.myDS.password = quartz
#org.quartz.dataSource.myDS.maxConnections = 5
#org.quartz.dataSource.myDS.validationQuery=select 0 from dual
定时任务的实现
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/**
 * @author D瓜哥,http://www.diguage.com/
 * @since 2016-11-28 16:53
 */
public class SnapInventoryQuartzJobBean extends QuartzJobBean implements ApplicationContextAware {
  private CityService cityService;

  private ItemDao itemDao;

  private WmsInventoryService wmsInventoryService;

  /** 将 Sku 的库存更新到 sku 表中,方便取用。 仅仅在不重要的查询中使用。 */
  @Override
  protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
    // 查询每个城市
    List<City> cityList = cityService.findAll();

    for (City city : cityList) {
      // 根据城市查询出在线的Item对应的itemId 和 skuId
      List<Item> itemList = itemDao.findByCityId(city.getId(), Constants.ITEM_ONLINE);
      if (CollectionUtils.isEmpty(itemList)) {
        continue;
      }
      // 获取每个Item的库存
      Set<Long> skuIds = Sets.newHashSetWithExpectedSize(itemList.size());
      Map<Long, Item> skuIdToItemMap = Maps.newHashMapWithExpectedSize(itemList.size());
      for (Item item : itemList) {
        skuIds.add(item.getSkuId());
        skuIdToItemMap.put(item.getSkuId(), item);
      }
      Map<Long, SkuForSales> skuIdToInvertoryMap =
          wmsInventoryService.checkSkuOnSalesBySiteId(skuIds, city.getId());

      // 将库存更新到 Item 表中
      for (Item item : itemList) {
        SkuForSales inventory = skuIdToInvertoryMap.get(item.getSkuId());
        skuIdToItemMap.get(item.getSkuId()).setSnapInventory(inventory.getCount());
      }
      itemDao.batchUpdateSnapInvertory(itemList);
    }
  }

  /**
   * 处理办法借鉴
   * http://stackoverflow.com/questions/6990767/inject-bean-reference-into-a-quartz-job-in-spring
   *
   * @param applicationContext ApplicationContext 实例
   * @throws BeansException 异常
   */
  @Override
  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    this.cityService = applicationContext.getBean(CityService.class);
    this.itemDao = applicationContext.getBean(ItemDao.class);
    this.wmsInventoryService = applicationContext.getBean(WmsInventoryService.class);
  }
}
注意这里实现的 ApplicationContextAware 接口。由于不能依赖不能注入进来,只能通过这种方式来获取。
MySQL 相关脚本
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
#
# In your Quartz properties file, you'll need to set 
# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#
#
# By: Ron Cordell - roncordell
#  I didn't see this anywhere, so I thought I'd post it here. This is the script from Quartz to create the tables in a MySQL database, modified to use INNODB instead of MYISAM.

DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
DROP TABLE IF EXISTS QRTZ_LOCKS;
DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
DROP TABLE IF EXISTS QRTZ_CALENDARS;

CREATE TABLE QRTZ_JOB_DETAILS(
SCHED_NAME VARCHAR(120) NOT NULL,
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
JOB_CLASS_NAME VARCHAR(250) NOT NULL,
IS_DURABLE VARCHAR(1) NOT NULL,
IS_NONCONCURRENT VARCHAR(1) NOT NULL,
IS_UPDATE_DATA VARCHAR(1) NOT NULL,
REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
NEXT_FIRE_TIME BIGINT(13) NULL,
PREV_FIRE_TIME BIGINT(13) NULL,
PRIORITY INTEGER NULL,
TRIGGER_STATE VARCHAR(16) NOT NULL,
TRIGGER_TYPE VARCHAR(8) NOT NULL,
START_TIME BIGINT(13) NOT NULL,
END_TIME BIGINT(13) NULL,
CALENDAR_NAME VARCHAR(200) NULL,
MISFIRE_INSTR SMALLINT(2) NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_SIMPLE_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
REPEAT_COUNT BIGINT(7) NOT NULL,
REPEAT_INTERVAL BIGINT(12) NOT NULL,
TIMES_TRIGGERED BIGINT(10) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_CRON_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
CRON_EXPRESSION VARCHAR(120) NOT NULL,
TIME_ZONE_ID VARCHAR(80),
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_SIMPROP_TRIGGERS
  (          
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    STR_PROP_1 VARCHAR(512) NULL,
    STR_PROP_2 VARCHAR(512) NULL,
    STR_PROP_3 VARCHAR(512) NULL,
    INT_PROP_1 INT NULL,
    INT_PROP_2 INT NULL,
    LONG_PROP_1 BIGINT NULL,
    LONG_PROP_2 BIGINT NULL,
    DEC_PROP_1 NUMERIC(13,4) NULL,
    DEC_PROP_2 NUMERIC(13,4) NULL,
    BOOL_PROP_1 VARCHAR(1) NULL,
    BOOL_PROP_2 VARCHAR(1) NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) 
    REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_BLOB_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
BLOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
INDEX (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_CALENDARS (
SCHED_NAME VARCHAR(120) NOT NULL,
CALENDAR_NAME VARCHAR(200) NOT NULL,
CALENDAR BLOB NOT NULL,
PRIMARY KEY (SCHED_NAME,CALENDAR_NAME))
ENGINE=InnoDB;

CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_FIRED_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
ENTRY_ID VARCHAR(95) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
INSTANCE_NAME VARCHAR(200) NOT NULL,
FIRED_TIME BIGINT(13) NOT NULL,
SCHED_TIME BIGINT(13) NOT NULL,
PRIORITY INTEGER NOT NULL,
STATE VARCHAR(16) NOT NULL,
JOB_NAME VARCHAR(200) NULL,
JOB_GROUP VARCHAR(200) NULL,
IS_NONCONCURRENT VARCHAR(1) NULL,
REQUESTS_RECOVERY VARCHAR(1) NULL,
PRIMARY KEY (SCHED_NAME,ENTRY_ID))
ENGINE=InnoDB;

CREATE TABLE QRTZ_SCHEDULER_STATE (
SCHED_NAME VARCHAR(120) NOT NULL,
INSTANCE_NAME VARCHAR(200) NOT NULL,
LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
CHECKIN_INTERVAL BIGINT(13) NOT NULL,
PRIMARY KEY (SCHED_NAME,INSTANCE_NAME))
ENGINE=InnoDB;

CREATE TABLE QRTZ_LOCKS (
SCHED_NAME VARCHAR(120) NOT NULL,
LOCK_NAME VARCHAR(40) NOT NULL,
PRIMARY KEY (SCHED_NAME,LOCK_NAME))
ENGINE=InnoDB;

CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY ON QRTZ_JOB_DETAILS(SCHED_NAME,REQUESTS_RECOVERY);
CREATE INDEX IDX_QRTZ_J_GRP ON QRTZ_JOB_DETAILS(SCHED_NAME,JOB_GROUP);

CREATE INDEX IDX_QRTZ_T_J ON QRTZ_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_T_JG ON QRTZ_TRIGGERS(SCHED_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_T_C ON QRTZ_TRIGGERS(SCHED_NAME,CALENDAR_NAME);
CREATE INDEX IDX_QRTZ_T_G ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);
CREATE INDEX IDX_QRTZ_T_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_N_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_N_G_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME ON QRTZ_TRIGGERS(SCHED_NAME,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_ST ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE);

CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME);
CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME,REQUESTS_RECOVERY);
CREATE INDEX IDX_QRTZ_FT_J_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_FT_JG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_FT_T_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP);
CREATE INDEX IDX_QRTZ_FT_TG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);

commit; 
这是 Quartz 2.2.3 提供的脚本。可以从它的分发包中获取更多数据库支持的脚本。

Appendix B: 常用 UML 图

B.1. UML 类图

B.1.1. 简介

B.1.2. 要素

B.1.3. 类之间的关系

UML类图几种关系的总结,泛化 = 实现 > 组合 > 聚合 > 关联 > 依赖 在UML类图中,常见的有以下几种关系: 泛化(Generalization), 实现(Realization),关联(Association),聚合(Aggregation),组合(Composition),依赖(Dependency)

类直接的关系
B.1.3.1. 泛化(Generalization)
泛化关系

是一种继承关系,表示一般与特殊的关系,它指定了子类如何特化父类的所有特征和行为。例如:老虎是动物的一种,即有老虎的特性也有动物的共性。

代码体现

extends 关键字

箭头指向

带三角箭头的实线,箭头指向父类

Diagram
B.1.3.2. 实现(Realization)
实现关系

在这里插入图片描述是一种类与接口的关系,表示类是接口所有特征和行为的实现.

代码体现

implements 关键字

箭头指向

带三角箭头的虚线,箭头指向接口

Diagram
B.1.3.3. 关联(Association)
关联关系

是一种拥有的关系,它使一个类知道另一个类的属性和方法;如:老师与学生,丈夫与妻子关联可以是双向的,也可以是单向的。双向的关联可以有两个箭头或者没有箭头,单向的关联有一个箭头。

代码体现

成员变量

箭头指向

带普通箭头的实心线,指向被拥有者

Diagram

老师与学生是双向关联,老师有多名学生,学生也可能有多名老师。但学生与某课程间的关系为单向关联,一名学生可能要上多门课程,课程是个抽象的东西他不拥有学生。

B.1.3.4. 聚合(Aggregation)
聚合关系

是整体与部分的关系,且部分可以离开整体而单独存在。如车和轮胎是整体和部分的关系,轮胎离开车仍然可以存在。 聚合关系是关联关系的一种,是强的关联关系;关联和聚合在语法上无法区分,必须考察具体的逻辑关系。

代码体现

成员变量

箭头指向

带空心菱形的实心线,菱形指向整体

Diagram

小技巧:空心菱形表示聚合,好聚好散,所以生命周期可以不同。

B.1.3.5. 组合(Composition)
组合关系

是整体与部分的关系,但部分不能离开整体而单独存在。如公司和部门是整体和部分的关系,没有公司就不存在部门。

组合关系是关联关系的一种,是比聚合关系还要强的关系,它要求普通的聚合关系中代表整体的对象负责代表部分的对象的生命周期。

代码体现

成员变量

箭头指向

带实心菱形的实线,菱形指向整体

Diagram

B.2. UML 序列图

B.2.1. 简介

B.2.2. 要素

B.2.3. 类之间的关系

B.2.4. 示例

Appendix C: 常用工具

  1. ASCII diagrams—​ASCIIFlow — 画 Ascii diagrams 图。画完 后,通过 asciidoctor-diagramditaaa 转换成图片。

C.1. Asciidoctor Diagram

Appendix D: XML 扩展标记语言

D.1. XML 扩展标记语言概述

XML 指扩展标记语言。

XML 被设计用来传输和存储数据。

D.2. DTD 文档类型定义

文档类型定义(DTD)可定义合法的XML文档构建模块。它使用一系列合法的元素来定义文档的结构。 DTD 可被成行地声明于 XML 文档中,也可作为一个外部引用。

D.3. XML Schema 语言

XML Schema 是基于 XML 的 DTD 替代者。

XML Schema 描述 XML 文档的结构。

XML Schema 语言也称作 XML Schema 定义(XML Schema Definition,XSD)。

Appendix E: 参考资料