前言
本文档是 D瓜哥 阅读 Spring 源码以及相关文档时的笔记。对学习内容做一些总结和提炼,分享出来也方便大家一起学习,共同进步。
友情支持
如果您觉得这个笔记对您有所帮助,看在D瓜哥码字的辛苦上,请友情支持一下,D瓜哥感激不尽,😜
有些打赏的朋友希望可以加个好友,欢迎关注D瓜哥的微信公众号,这样就可以通过公众号的回复直接给我发信息。
公众号的微信号是: jikerizhi (“极客日志”全拼)。因为众所周知的原因,有时图片加载不出来。如果图片加载不出来可以直接通过搜索微信号来查找我的公众号。 |
官网及版本库
本文档的版本库托管在 Github 上,另外单独发布。
- “地瓜哥”博客网
-
https://www.diguage.com/ 。D瓜哥的个人博客。欢迎光临,不过,内容很杂乱,请见谅。不见谅,你来打我啊,😂😂
- 本文档官网
-
https://diguage.github.io/spring-framework/ 。为了方便阅读,这里展示了处理好的文档。阅读请点击这个网址。
- 本文档版本库
-
https://github.com/diguage/spring-framework 。由于组织方式的特殊性,坦白讲,不建议大家发 PR。有问题,欢迎发 Issue 讨论。
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。 |
如果在执行第一步时,提示找不到命令时,请执行
|
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。我们先按照这个文档来进行操作一 遍:
-
在终端(Windows 系统上,推荐使用 Cmder)中,进入到 Spring 源码根目录
SPRING_HOME
。 -
预编译
spring-oxm
模块:./gradlew cleanIdea :spring-oxm:compileTestJava
;在 Windows 系统上,请执行gradlew cleanIdea :spring-oxm:compileTestJava
。 -
导入到 IDEA 中:File → New → Project from Existing Sources… → 选中 Spring 源码 目录 → OK → import from external model → Gradle → Next → Finish,然后经过漫长的等待 后就成功导入到 IDEA 中了。
-
设计 Project 的 JDK 版本。这里分析 Spring 6.1.0-SNAPSHOT,要求 JDK 的版本最低为 JDK 8。
-
官方文档上显示,需要排除
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 方式的测试运行配置来解决这个问题。 如下图:
上图中建立的整个测试类的运行配置。如果只想运行单个测试方法,只需要修改一下
Script parameters。将其修改为: --tests "org.springframework.beans.factory.DefaultListableBeanFactoryTests.testUnreferencedSingletonWasInstantiated"
即可。
新建完成后,就可以点击运行或者调试按钮来进行测试了。
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 框架是一个分层、分模块的架构。包括了一系列的模块。如下图所示:
3. IoC 的实现原理
TODO: 感觉可以通过向 ClassPathScanningCandidateComponentProvider
中添加过滤注解来实现扩展功能。抽空尝试一下。
TODO: 除了 singleton 和 prototype 外,其他 Scope 类型的 Bean 实例是怎么缓存的?
TODO: default-autowire="byName"
等实现自动装配的功能在哪里实现的?怎么实现的?是否会遍历 Bean 的所有属性? org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean
中有相关类似实现。
autowiring 的实现过程:
-
对 Bean 的属性代调用 getBean()方法,完成依赖 Bean 的初始化和依赖注入。
-
将依赖 Bean 的属性引用设置到被依赖的 Bean 属性上。
-
将依赖 Bean 的名称和被依赖 Bean 的名称存储在 IOC 容器的集合中。
对属性的注入过程分以下两种情况:
-
属性值类型不需要强制转换时,不需要解析属性值,直接准备进行依赖注入。
-
属性值需要进行类型强制转换时,如对其他对象的引用等,首先需要解析属性值,然后对解析后的属性值进行依赖注入。
Spring IoC 容器是如何将属性的值注入到 Bean 实例对象中去的:
-
对于集合类型的属性,将其属性值解析为目标类型的集合后直接赋值给属性。
-
对于非集合类型的属性,大量使用了 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 配置的时候,也许我们会这样的问题:
-
Bean 怎么表示?
-
Bean 的依赖怎么表示?
-
init-method
方法怎么存储? -
Bean 的一些属性,比如
lazy-init
等,怎么表示? -
Bean 构造函数的参数怎么存储?
-
…
Java 也有类似的问题,比如怎么表示一个类?Java 通过反射 API 来解决这个问题:
-
Class
-
Method
-
Field
-
Constructor
-
Annotation
但是,为什么 Spring 还要自己定义一套呢?主要原因是 Java 反射 API 不满足 Spring 的需求,比如,它没办法表示哪些类是 SCOPE_SINGLETON
,哪些类是 SCOPE_PROTOTYPE
。
另外,Spring 的 Bean 抽象也并不是完全自定义的,它是基于 Java 反射 API 又增加了自定义功能,其核心 API 就是 BeanDefinition
。下面,我们来仔细看一下它的继承体系以及内部核心属性。
3.1.2. 继承体系
-
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
。有两个重要的属性:AnnotationMetadata
、MethodMetadata
分别表示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. 核心属性
下面主要介绍一下它的核心内部属性:
-
Map<String, Object> attributes = new LinkedHashMap<>()
:配置的属性以及属性值。 -
Object source
:存储 Bean 来源,有时是 XMLElement
对象。还可以是其他对象。 -
Object beanClass
:Bean 的类型定义,有时是Class
类型的对象;有时是类的全限定名,此时就是String
类型。 -
abstractFlag = false
:默认为false
。如果为true
,则表示不打算实例化该 Bean,仅仅作为其他 Bean 的父 Bean。一般与parent
一起使用,设置abstract
的 Bean 定义不需要创建实例,仅仅作为parent
来进行一些通用的配置,后面的 Bean 通过设置parent
来获取相应的配置信息,从而达到简化配置的目的。 -
Boolean lazyInit
:是否懒加载。 -
int autowireMode = AUTOWIRE_NO
:注入模式,默认为AUTOWIRE_NO
。一共有五个可选项:-
AUTOWIRE_NO
— 不自动注入。 -
AUTOWIRE_BY_NAME
— 根据名称自动注入。 -
AUTOWIRE_BY_TYPE
— 根据类型自动注入。 -
AUTOWIRE_CONSTRUCTOR
— 自动根据构造函数注入。 -
AUTOWIRE_AUTODETECT
— 自动检测。
-
-
int dependencyCheck = DEPENDENCY_CHECK_NONE
:依赖检测。一共有四个可选项:-
DEPENDENCY_CHECK_NONE
— 不进行依赖检测。 -
DEPENDENCY_CHECK_OBJECTS
— 只检测对象引用。 -
DEPENDENCY_CHECK_SIMPLE
— 只检测简单对象:基础类型、Enum
、CharSequence
、Number
、Date
、Temporal
、URI
、URL
、Locale
、Class
以及这些类型的数组对象。 -
DEPENDENCY_CHECK_ALL
— 检测所有依赖。
-
-
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#injectionMetadataCache
和AutowiredAnnotationBeanPostProcessor#injectionMetadataCache
属性中。在 Spring Bean 生命周期概述 中描述的关于属性的依赖读取和注入也是靠这个两个属性来保存依赖关系的。在 深入剖析 Spring 核心数据结构:BeanFactory :
Map<String, Set<String>> dependentBeanMap
中提到的DefaultSingletonBeanRegistry#dependentBeanMap
和DefaultSingletonBeanRegistry#dependenciesForBeanMap
两个属性,保存了这个dependsOn
依赖关系的正向关系和反向关系。当工厂销毁时,也会通过dependentBeanMap
属性,先销毁依赖的 Bean,然后再销毁自身。 -
boolean autowireCandidate = true
:声明是否是其他依赖的候选 Bean;只会影响根据类型注入的情况,不会影响根据名称明确指明依赖的情况。 -
boolean primary = false
:是否是首选 Bean,标注了@Primary
则为true
。当 A 类型的 Bean 需要注入 B 类型的实现类,并且 B 类型的实现类有多个,在按类型将 B 的实现类注入到 A 中时,优先注入该属性为true
的实现类,当然如果同一个类的实现类有多个primary
为true
,则抛出异常。 -
Map<String, AutowireCandidateQualifier> qualifiers = new LinkedHashMap<>()
: -
Supplier<?> instanceSupplier
:产生对象的生产者。 -
boolean nonPublicAccessAllowed = true
:是否允许访问非public
的构造器和方法。 -
boolean lenientConstructorResolution = true
:是否采用宽容模式来解析构造函数。如果是false
,则只要参数类型不匹配就抛出异常。 -
String factoryBeanName
:当 Bean 的创建方式是以工厂进行创建的时候,该方法设置工厂的名称。 -
String factoryMethodName
:工厂创建 Bean 时,设置创建 Bean 的方法名称。 -
ConstructorArgumentValues constructorArgumentValues
:构造函数参数值。 -
MutablePropertyValues propertyValues
: 获取类的属性和属性值的类PropertyValue
。 -
MethodOverrides methodOverrides = new MethodOverrides()
: -
String initMethodName
:初始化方法名,对应init-method
或者@PostConstruct
标注的方法。 -
String destroyMethodName
:销毁方法名,对应` 或 `@PreDestroy
标注的方法。 -
boolean enforceInitMethod = true
:强制初始化方法,默认是false
。如果为true
,而且initMethodName
为空,则报错。 -
boolean enforceDestroyMethod = true
:强制销毁方法,默认是false
。如果为true
,而且destroyMethodName
为空,则报错。 -
boolean synthetic = false
:是否是合成的。 -
int role = BeanDefinition.ROLE_APPLICATION
:Bean 角色。可选项有三个:-
ROLE_APPLICATION
— 为应用程序定义。 -
ROLE_SUPPORT
— 为应用程序定义的比较大的对象。 -
ROLE_INFRASTRUCTURE
— 内部定义的基础 Bean。
-
-
String description
:Bean 描述。 -
Resource resource
:Bean 的来源Resource
对象。 -
AnnotationMetadata metadata
: 注解元信息。 -
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
的继承体系就是一个非常典型的例子。我们来看一下它的继承体系:
-
AliasRegistry
:别名注册器。Spring 中,别名注册相关的功能就是从这里实现的。 -
SimpleAliasRegistry
:别名注册器的一个简单实现,从内部属性可以看出,它是把别名映射信息存到一个Map
中了。 -
DefaultSingletonBeanRegistry
:默认的单例 Bean 注册器,从内部属性来说,也是基于Map
实现的。 -
FactoryBeanRegistrySupport
:FactoryBean
注册器。 -
SingletonBeanRegistry
:单例 Bean 注册器。 -
BeanDefinitionRegistry
:BeanDefinition
注册器。 -
BeanFactory
:容器的基类。 -
ListableBeanFactory
:在基本容器基础上,增加了遍历相关功能。 -
HierarchicalBeanFactory
:在基本容器基础上,增加了父子上下级容器关联。 -
AutowireCapableBeanFactory
:在基本容器基础上,增加了自动注入功能。 -
ConfigurableBeanFactory
:对容器增加可配置性,比如父级容器、ClassLoader
、TypeConverter
等。 -
ConfigurableListableBeanFactory
:可配置可遍历容器。 -
AbstractBeanFactory
:容器的抽象实现类,实现了容器的基础功能。 -
AbstractAutowireCapableBeanFactory
:带自动装配功能的抽象容器类。 -
DefaultListableBeanFactory
:这是 Spring 内部使用的默认容器实现。也是 Spring 中最重要的一个类。
3.2.2. 核心属性
3.2.2.1. Registry
-
Map<String, String> aliasMap = new ConcurrentHashMap<>(16)
:别名到 Bean 名称的映射。 -
Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256)
:Bean 名称到单例 Bean 的映射。可以理解成,这就是所谓的容器。 -
Map<String, Object> earlySingletonObjects = new HashMap<>(16)
:Bean 到“未成熟”单例 Bean 的映射。该 Bean 对象只是被创建出来,但是还没有注入依赖。在容器解决循环依赖时,用于存储中间状态。 -
Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16)
:Bean 名称到 Bean 的ObjectFactory
对象的映射,在容器解决循环依赖时,用于存储中间状态。关于这三个属性的进一步说明,请移步: 源码剖析 Spring 循环依赖。
-
Set<String> registeredSingletons = new LinkedHashSet<>(256)
:已经被注册过的 Bean 名称集合。 -
Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16))
:正在创建的 Bean 名称集合。 -
Set<String> inCreationCheckExclusions = Collections.newSetFromMap(new ConcurrentHashMap<>(16))
:不需要检查的正在创建的 Bean 名称集合。 -
Set<Exception> suppressedExceptions
:存储创建过程中发现的异常。 -
boolean singletonsCurrentlyInDestruction = false
:是否正在销毁单例 Bean。 -
Map<String, Object> disposableBeans = new LinkedHashMap<>()
:需要在销毁时释放资源的 Bean。在AbstractBeanFactory#registerDisposableBeanIfNecessary
中可以看到,所有的单例 Bean 都通过DisposableBeanAdapter
适配器添加到该属性中了。在DefaultSingletonBeanRegistry#destroySingleton
和DefaultSingletonBeanRegistry#destroySingletons
中执行destroy()
操作。 -
Map<String, Set<String>> containedBeanMap = new ConcurrentHashMap<>(16)
:在 Bean 名称之间包含映射:Bean 名称到 Bean 所包含的一组 Bean 名称。 -
Map<String, Set<String>> dependentBeanMap = new ConcurrentHashMap<>(64)
该属性和下面的
dependenciesForBeanMap
属性的详细说明,请在 深入剖析 Spring 核心数据结构:BeanDefinition :String[] dependsOn
中查看。 -
Map<String, Set<String>> dependenciesForBeanMap = new ConcurrentHashMap<>(64)
:与上面的dependentBeanMap
属性正好一正一反的关系。两个相加,就是双向映射。 -
Map<String, Object> factoryBeanObjectCache = new ConcurrentHashMap<>(16)
:由FactoryBean
创建的单例对象的缓存。
3.2.2.2. BeanFactory
-
BeanFactory parentBeanFactory
:父容器。 -
ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader()
:类加载器。 -
ClassLoader tempClassLoader
:临时类加载器。 -
BeanExpressionResolver beanExpressionResolver
:Bean 定义值中表达式的解析策略。 -
ConversionService conversionService
: Spring 3.0 以后出现,用于类型转换,用于替代PropertyEditors
。 -
Set<PropertyEditorRegistrar> propertyEditorRegistrars = new LinkedHashSet<>(4)
:属性编辑器注册器集合。 -
Map<Class<?>, Class<? extends PropertyEditor>> customEditors = new HashMap<>(4)
:类型到自定义的属性编辑器的映射。 -
TypeConverter typeConverter
:类型转换器,用于覆盖默认的PropertyEditor
机制。 -
List<StringValueResolver> embeddedValueResolvers = new CopyOnWriteArrayList<>()
:内置的字符串值解析器列表。 -
List<BeanPostProcessor> beanPostProcessors = new BeanPostProcessorCacheAwareList()
:BeanPostProcessor
列表。关于它的内容,在 Spring Bean 生命周期概述 中有详细地介绍。 -
BeanPostProcessorCache beanPostProcessorCache
:BeanPostProcessor
缓存,会根据类型,缓存到不同的列表中。 -
Map<String, Scope> scopes = new LinkedHashMap<>(8)
:scope
字符串到具体Scope
实例的映射。 -
Map<String, RootBeanDefinition> mergedBeanDefinitions = new ConcurrentHashMap<>(256)
:Bean 名称到RootBeanDefinition
的映射。 -
Set<String> alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256))
: 已经创建的 Bean 名称。 -
ThreadLocal<Object> prototypesCurrentlyInCreation = new NamedThreadLocal<>("Prototype beans currently in creation")
:正常创建的 Bean。 -
InstantiationStrategy instantiationStrategy
:Bean 实例创建策略。 -
ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer()
:方法参数名的解析策略。 -
boolean allowCircularReferences = true
:是否循环依赖。 -
boolean allowRawInjectionDespiteWrapping = false
:在循环引用的情况下,是否注入原始 Bean 实例,即使注入的 Bean 最终被包装。 -
Set<Class<?>> ignoredDependencyTypes = new HashSet<>()
:忽略的依赖类型。 -
Set<Class<?>> ignoredDependencyInterfaces = new HashSet<>()
:忽略的依赖接口。 -
NamedThreadLocal<String> currentlyCreatedBean = new NamedThreadLocal<>("Currently created bean")
:正在创建的 Bean。 -
ConcurrentMap<String, BeanWrapper> factoryBeanInstanceCache = new ConcurrentHashMap<>()
:工厂 Bean 实例。 -
ConcurrentMap<Class<?>, Method[]> factoryMethodCandidateCache = new ConcurrentHashMap<>()
:工厂方法缓存。 -
ConcurrentMap<Class<?>, PropertyDescriptor[]> filteredPropertyDescriptorsCache = new ConcurrentHashMap<>()
:过滤后的PropertyDescriptor
缓存。 -
Map<String, Reference<DefaultListableBeanFactory>> serializableFactories = new ConcurrentHashMap<>(8)
:可序列化的DefaultListableBeanFactory
。 -
boolean allowBeanDefinitionOverriding = true
:是否运行BeanDefinition
覆盖。 -
boolean allowEagerClassLoading = true
:是否允许类急切加载。 -
Comparator<Object> dependencyComparator
:依赖排序器。 -
AutowireCandidateResolver autowireCandidateResolver = SimpleAutowireCandidateResolver.INSTANCE
:注入候选者解析器。 -
Map<Class<?>, Object> resolvableDependencies = new ConcurrentHashMap<>(16)
:依赖类型到合适的注入对象的映射。 -
Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256)
:Bean 名称到BeanDefinition
的映射。关于BeanDefinition
在 深入剖析 Spring 核心数据结构:BeanDefinition 有更详细的介绍。 -
Map<String, BeanDefinitionHolder> mergedBeanDefinitionHolders = new ConcurrentHashMap<>(256)
:Bean 名称到BeanDefinitionHolder
的映射。 -
Map<Class<?>, String[]> allBeanNamesByType = new ConcurrentHashMap<>(64)
:类型到所有该类型的 Bean 名称的映射。 -
Map<Class<?>, String[]> singletonBeanNamesByType = new ConcurrentHashMap<>(64)
:类型到所有该类型的单例 Bean 名称的映射。 -
List<String> beanDefinitionNames = new ArrayList<>(256)
:Bean 名称列表。 -
Set<String> manualSingletonNames = new LinkedHashSet<>(16)
: -
String[] frozenBeanDefinitionNames
:冻结的 Bean 名称。 -
boolean configurationFrozen
:配置是否冻结。
从上面这些属性可以看出,所谓的容器,其实就是一个 Map
属性 Map<String, Object> singletonObjects
。而 Bean 别名也是一个 Map
属性 Map<String, String> aliasMap
。从别名到 Bean 实例只需要做两个 Map
查找就可以完成了。
在网上查了查资料,没有对这些属性做比较详细的介绍,这个文章也有很多不完善的地方,回头随着 D瓜哥对 Spring 代码的了解后续再逐步完善。
3.3. FactoryBean
详解
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 | 获取 FactoryBean 的 getObject() 方法创建的实例。 |
由 FactoryBean
实现类创建的 Bean 实例,在实际 getBean
之前,不会创建。 会根据 isSingleton()
方法的返回值来决定创建之后是否缓存实例。
FactoryBean
实现类创建的 Bean 实例并不是存储在 singletonObjects
实例变量中(由 DefaultSingletonBeanRegistry
声明, AbstractBeanFactory
间接继承了 DefaultSingletonBeanRegistry
),而是保存在 factoryBeanObjectCache
实例变量中(由 FactoryBeanRegistrySupport
声明, AbstractBeanFactory
直接继承了 FactoryBeanRegistrySupport
)。具体情况,请参考类图: BeanFactory 继承体系及关键属性。
3.4. Environment
Environment
主要用于读取当前应用运行环境的环境变量和一些配置信息。另外,常见的指定不同配置的 spring.profiles.active
的处理,也是由 Environment
来处理。
3.5. ApplicationContext
总使用 AnnotationConfigApplicationContext
做实验,有可能会遗漏一些重要的细节。增加一个 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
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;
}
}
}
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. 流程概要
下面完整流程有些太复杂,所以,提炼一个简要的过程,方便糊弄面试官,哈哈哈😆
-
创建容器,读取
applicationContext.register(Config.class)
指定的配置。 -
准备
BeanFactory
,注册容器本身和BeanFactory
实例,以及注册环境配置信息等。 -
执行
BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry
注册BeanDefinition
。有三点需要注意:-
目前只有一个
ConfigurationClassPostProcessor
实现类,Spring 中大量的 Bean 都是在这一步被该类注册到容器中的。 -
执行顺序是 ①
PriorityOrdered
②Ordered
③ 普通的顺序来执行 -
在执行上一步是,如果发现注册了
BeanDefinitionRegistryPostProcessor
类型的 Bean,就会在循环里继续调用postProcessBeanDefinitionRegistry
方法。MyBATIS 和 Spring 整合的MapperScannerConfigurer
类就是在这一步执行的。
-
-
执行
BeanFactoryPostProcessor#postProcessBeanFactory
方法。目前只有一个ConfigurationClassPostProcessor
实现类。 -
注册
CommonAnnotationBeanPostProcessor
和AutowiredAnnotationBeanPostProcessor
为BeanPostProcessor
。 -
注册
ApplicationEventMulticaster
,用于广播事件的。 -
注册
ApplicationListener
-
预加载以及注册所有非懒加载的 Bean
3.6.2. 完整启动流程
-
调用
prepareRefresh()
方法,初始化属性源(property source)配置。 -
调用
obtainFreshBeanFactory()
获得ConfigurableListableBeanFactory
对象。 -
调用
prepareBeanFactory
,准备BeanFactory
,添加必要的 Bean;添加ApplicationContextAwareProcessor
、ApplicationListenerDetector
处理器;注册环境相关的 Bean。 -
下面通过
AbstractApplicationContext#invokeBeanFactoryPostProcessors
方法,开始执行BeanDefinitionRegistryPostProcessor
和BeanFactoryPostProcessor
相关的方法。这个方法流程起始也很简单:目前,除了用户自定义的
BeanDefinitionRegistryPostProcessor
和BeanFactoryPostProcessor
外,Spring 内置的,只有ConfigurationClassPostProcessor
一个类。所以,把这个类的实现摸清楚了,AbstractApplicationContext#invokeBeanFactoryPostProcessors
就可以跳过了。-
首先,执行
BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry
方法,顺序如下:-
用户手动添加的
BeanDefinitionRegistryPostProcessor
; -
实现
PriorityOrdered
接口的BeanDefinitionRegistryPostProcessor
; -
实现
Ordered
接口的BeanDefinitionRegistryPostProcessor
; -
普通
BeanDefinitionRegistryPostProcessor
,只要发现有新加入的,就循环调用。
-
-
然后,执行
BeanFactoryPostProcessor#postProcessBeanFactory
方法。顺序如下:-
实现
BeanDefinitionRegistryPostProcessor
接口的类; -
实现
BeanFactoryPostProcessor
接口的类。
-
-
-
先执行用户手动添加的
BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry(DefaultListableBeanFactory)
关于
BeanDefinitionRegistryPostProcessor
的处理流程,D瓜哥在 Spring 扩展点概览及实践:BeanDefinitionRegistryPostProcessor 中有更详细的描述,不了解的朋友请参考那篇文章的介绍。 -
创建
ConfigurationClassPostProcessor
对象,并针对该对象依次执行-
构造函数
-
ApplicationListenerDetector#postProcessMergedBeanDefinition(RootBeanDefinition, ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor)
-
调用用户手动添加的
BeanPostProcessor#postProcessBeforeInitialization
方法 -
ApplicationContextAwareProcessor#postProcessBeforeInitialization(ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor)
-
ApplicationListenerDetector#postProcessBeforeInitialization(ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor)
-
执行
init
方法 -
调用用户手动添加的
BeanPostProcessor#postProcessAfterInitialization
方法 -
ApplicationContextAwareProcessor#postProcessAfterInitialization(ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor)
— 由于ApplicationContextAwareProcessor
并没有该方法,所以不执行。 -
ApplicationListenerDetector#postProcessBeforeInitialization(ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor)
-
-
执行
ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry(DefaultListableBeanFactory)
— 在这里,处理@Configuration
、@Import
、@ImportResource
、@Bean
和 。 -
执行用户手动添加的
BeanDefinitionRegistryPostProcessor#postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
-
执行
ConfigurationClassPostProcessor#postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
— 在这里给@Configuration
标注的类,生成 cglib 增强后的代理类。注意:在这里,还增加了一个ImportAwareBeanPostProcessor
后置处理器。因为
ConfigurationClassPostProcessor
是一个InstantiationAwareBeanPostProcessor
实例。所以,实例化ConfigurationClassPostProcessor
对象并加入到容器后。这句话啥意思?想想再补充一下。 -
创建了
EventListenerMethodProcessor
实例,和创建ConfigurationClassPostProcessor
时类似,依次执行-
InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation
— 目前有ImportAwareBeanPostProcessor
。 -
构造函数
-
MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition
— 目前有ApplicationListenerDetector
。 -
InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation
-
InstantiationAwareBeanPostProcessor#postProcessProperties
— 目前有ImportAwareBeanPostProcessor
。 -
InstantiationAwareBeanPostProcessor#postProcessPropertyValues
— 从 5.1 开始废弃,使用上面方法代替。 -
BeanPostProcessor#postProcessBeforeInitialization
— 目前有-
用户手动添加的
BeanPostProcessor
-
ApplicationContextAwareProcessor
-
ApplicationListenerDetector
-
ImportAwareBeanPostProcessor
-
-
init
-
BeanPostProcessor#postProcessAfterInitialization
方法。 — 与postProcessBeforeInitialization
相同,不再赘述。有一点需要注意,上面增加了
ImportAwareBeanPostProcessor
实例,这里也会执行。以下都是如此,不再赘述。
-
-
实例化用户通过
BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry(DefaultListableBeanFactory)
或者@Configuration
添加的BeanFactoryPostProcessor
,以及 Spring 自己添加的BeanFactoryPostProcessor
。依次执行如下方法:-
InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation
— 目前有ImportAwareBeanPostProcessor
。 -
Bean 的构造函数
-
MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition
— 目前有ApplicationListenerDetector
。 -
InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation
-
InstantiationAwareBeanPostProcessor#postProcessProperties
— 目前有ImportAwareBeanPostProcessor
。 -
InstantiationAwareBeanPostProcessor#postProcessPropertyValues
— 从 5.1 开始废弃,使用上面方法代替。 -
BeanPostProcessor#postProcessBeforeInitialization
— 目前有-
用户手动添加的
BeanPostProcessor
-
ApplicationContextAwareProcessor
-
ApplicationListenerDetector
-
ImportAwareBeanPostProcessor
-
-
init
-
BeanPostProcessor#postProcessAfterInitialization
方法
-
-
调用上一步创建的
BeanFactoryPostProcessor
对象的postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
方法。这里目前包含EventListenerMethodProcessor
对象。EventListenerMethodProcessor
是AnnotationConfigApplicationContext()
初始化时,创建new AnnotatedBeanDefinitionReader(this)
对象时,通过调用AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry)
方法注册到容器中的。-
这里调用
EventListenerMethodProcessor#postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
,创建EventListenerFactory
对象,依次执行这个
EventListenerFactory
对象不重要。或者说,目前没有发现它特别重要的地方。-
InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation
-
Bean 的构造函数
-
MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition
-
InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation
-
InstantiationAwareBeanPostProcessor#postProcessProperties
-
InstantiationAwareBeanPostProcessor#postProcessPropertyValues
— 从 5.1 开始废弃,使用上面方法代替。 -
BeanPostProcessor#postProcessBeforeInitialization
-
init
-
BeanPostProcessor#postProcessAfterInitialization
方法
-
-
-
到此为止,
invokeBeanFactoryPostProcessors(beanFactory)
方法调用完毕。 -
下面开始调用
registerBeanPostProcessors(beanFactory)
方法。 -
添加
PostProcessorRegistrationDelegate.BeanPostProcessorChecker
实例,以下执行BeanPostProcessor
方法时,都会带上。 -
创建
AutowiredAnnotationBeanPostProcessor
、CommonAnnotationBeanPostProcessor
对象,依次执行如下方法:-
InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation
— 目前有ImportAwareBeanPostProcessor
。 -
构造函数
-
MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition
— 目前有ApplicationListenerDetector
。 -
InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation
-
InstantiationAwareBeanPostProcessor#postProcessProperties
-
InstantiationAwareBeanPostProcessor#postProcessPropertyValues
— 从 5.1 开始废弃,使用上面方法代替。 -
AutowiredAnnotationBeanPostProcessor#setBeanFactory(DefaultListableBeanFactory)
— 完成BeanNameAware
,BeanClassLoaderAware
,BeanFactoryAware
三个Aware
的注入。通过AbstractAutowireCapableBeanFactory#invokeAwareMethods
方法来完成。 -
BeanPostProcessor#postProcessBeforeInitialization
— 目前有-
用户手动添加的
BeanPostProcessor
-
ApplicationContextAwareProcessor
— 完成如下六个Aware
的注入:-
EnvironmentAware
-
EmbeddedValueResolverAware
-
ResourceLoaderAware
-
ApplicationEventPublisherAware
-
MessageSourceAware
-
ApplicationContextAware
-
-
ApplicationListenerDetector
-
ImportAwareBeanPostProcessor
-
BeanPostProcessorChecker
-
-
init
-
BeanPostProcessor#postProcessAfterInitialization
方法
-
-
将
AutowiredAnnotationBeanPostProcessor
、CommonAnnotationBeanPostProcessor
对象注册到容器中。以下会随着BeanPostProcessor
的调用,也会被执行。 -
创建
AnnotationAwareAspectJAutoProxyCreator
对象,依次执行如下方法:-
InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation
— 目前有如下三个:-
ImportAwareBeanPostProcessor
-
CommonAnnotationBeanPostProcessor
-
AutowiredAnnotationBeanPostProcessor
-
-
构造函数
-
MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition
— 目前有如下三个:-
ApplicationListenerDetector
-
CommonAnnotationBeanPostProcessor
— 收集依赖信息。 -
AutowiredAnnotationBeanPostProcessor
— 收集依赖信息。
-
-
InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation
-
InstantiationAwareBeanPostProcessor#postProcessProperties
目前有如下三个:-
ImportAwareBeanPostProcessor
-
CommonAnnotationBeanPostProcessor
— 完成依赖注入。 -
AutowiredAnnotationBeanPostProcessor
— 完成依赖注入。
-
-
InstantiationAwareBeanPostProcessor#postProcessPropertyValues
— 从 5.1 开始废弃,使用上面方法代替。 -
BeanPostProcessor#postProcessBeforeInitialization
— 目前有-
用户手动添加的
BeanPostProcessor
-
ApplicationContextAwareProcessor
— 完成如下六个Aware
的注入:-
EnvironmentAware
-
EmbeddedValueResolverAware
-
ResourceLoaderAware
-
ApplicationEventPublisherAware
-
MessageSourceAware
-
ApplicationContextAware
-
-
ApplicationListenerDetector
-
ImportAwareBeanPostProcessor
-
BeanPostProcessorChecker
-
CommonAnnotationBeanPostProcessor
-
AutowiredAnnotationBeanPostProcessor
-
-
init
-
BeanPostProcessor#postProcessAfterInitialization
方法
-
-
将
AnnotationAwareAspectJAutoProxyCreator
对象注册到容器中。以下会随着BeanPostProcessor
的调用,也会被执行。 -
重新添加
ApplicationListenerDetector
,其实就是换了个位置,将其调整到了最后。 -
到此为止,
registerBeanPostProcessors(beanFactory)
方法调用完毕。 -
调用
initMessageSource()
方法,注册MessageSource
Bean。 -
调用
initApplicationEventMulticaster()
方法,注册SimpleApplicationEventMulticaster
对象, -
调用
onRefresh()
方法,这是空方法,方便做扩展。 -
调用
registerListeners()
方法,但是似乎什么也没做。 -
调用
finishBeanFactoryInitialization(beanFactory)
方法,这个方法中,最重要的一个操作就是实例化非懒加载的所有 Bean,在DefaultListableBeanFactory#preInstantiateSingletons
中完成这些操作。目前,除了用户自己实现的,还有七个如下的BeanPostProcessor
:-
ApplicationContextAwareProcessor
-
ConfigurationClassPostProcessor
-
BeanPostProcessorChecker
-
AnnotationAwareAspectJAutoProxyCreator
-
CommonAnnotationBeanPostProcessor
-
AutowiredAnnotationBeanPostProcessor
-
ApplicationListenerDetector
这部分内容放在下一篇文章 Spring Bean 生命周期概述 再展开来讲。
-
-
调用
finishRefresh()
— 启动生命周期函数,广播刷新完成通知。具体如下:-
清理
Resource
缓存(也就是被扫描到的各种类,自定义类,以及相关父类和所实现的接口)。(像是在ImportSelector
中声明的类。但是没有找到添加到缓存的地方?) -
注册
LifecycleProcessor
,并通过它启动所有的LifecycleProcessor
和它自身。没有看出来干什么用的? -
广播
ContextRefreshedEvent
事件。 -
将
ConfigurableApplicationContext
注册到LiveBeansView
上,如果它存在的话。 -
清理各种缓存
-
启动过程中的反射相关缓存,比如
init-method
,Aware
相关的方法,注入需要的字段等等; -
AnnotationFilter
相关缓存; -
注解元素缓存和生命周期函数(
Aware
、InitializingBean
、`BeanFactoryPostProcessor`等)缓存清空 -
解析类型缓存清空
-
反省结果清空
-
-
在下一篇文章 Spring Bean 生命周期概述 中,D瓜哥将针对 Spring Bean 的整个生命周期展开详细说明。
3.6.3. 附录:启动日志
下面是启动日志。有删减,为了方便阅读,增加了序号和层次。
-
调用
prepareRefresh()
方法,初始化属性源(property source)配置。 -
调用
obtainFreshBeanFactory()
获得ConfigurableListableBeanFactory
对象。 -
准备
BeanFactory
,添加必要的 Bean,在prepareBeanFactory
中完成。 -
下面通过
invokeBeanFactoryPostProcessors
方法,开始执行BeanFactoryPostProcessor
相关的方法 -
LogBeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry(DefaultListableBeanFactory)
— 用户自己手动添加的BeanDefinitionRegistryPostProcessor
实例 -
创建
ConfigurationClassPostProcessor
Bean-
构造函数
-
ApplicationListenerDetector#postProcessMergedBeanDefinition(RootBeanDefinition, ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor)
—ApplicationListenerDetector
实例是在prepareBeanFactory
方法中,加入到容器中的。 -
LogBeanPostProcessor#postProcessBeforeInitialization(ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor)
— 用户自己手动添加 -
LogDestructionAwareBeanPostProcessor#postProcessBeforeInitialization(ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor)
— 用户自己手动添加,继承默认实现。 -
ApplicationContextAwareProcessor#postProcessBeforeInitialization(ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor)
—ApplicationContextAwareProcessor
实例是在prepareBeanFactory
方法中,加入到容器中的。处理六种Aware
注入。 -
ApplicationListenerDetector#postProcessBeforeInitialization(ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor)
-
LogBeanPostProcessor#postProcessAfterInitialization(ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor)
-
LogDestructionAwareBeanPostProcessor#postProcessAfterInitialization(ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor)
— 用户自己手动添加,继承默认实现,没有任何操作。 -
ApplicationContextAwareProcessor#postProcessAfterInitialization(ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor)
— 继承默认实现,没有任何操作。 -
ApplicationListenerDetector#postProcessAfterInitialization(ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor)
-
-
ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry(DefaultListableBeanFactory)
— 在这里,处理@Configuration
、@Import
、@ImportResource
、@Bean
和 。 -
LogBeanDefinitionRegistryPostProcessor#postProcessBeanFactory(DefaultListableBeanFactory)
-
ConfigurationClassPostProcessor#postProcessBeanFactory(DefaultListableBeanFactory)
— 在这里给@Configuration
标注的类,生成 cglib 增强后的代理类。注意:在这里,还增加了一个ImportAwareBeanPostProcessor
后置处理器。因为
ConfigurationClassPostProcessor
是一个InstantiationAwareBeanPostProcessor
实例。所以,实例化ConfigurationClassPostProcessor
对象并加入到容器后。这句话啥意思?想想再补充一下。 -
创建
EventListenerMethodProcessor
Bean, Name:org.springframework.context.event.internalEventListenerProcessor
-
ImportAwareBeanPostProcessor#postProcessBeforeInstantiation(EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)
-
构造函数
-
ApplicationListenerDetector#postProcessMergedBeanDefinition(RootBeanDefinition, EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)
-
ImportAwareBeanPostProcessor#postProcessAfterInstantiation(EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)
-
ImportAwareBeanPostProcessor#postProcessProperties(MutablePropertyValues, EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)
-
LogBeanPostProcessor#postProcessBeforeInitialization(EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)
-
LogDestructionAwareBeanPostProcessor#postProcessBeforeInitialization(EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)
-
ApplicationContextAwareProcessor#postProcessBeforeInitialization(EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)
-
ApplicationListenerDetector#postProcessBeforeInitialization(EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)
-
ImportAwareBeanPostProcessor#postProcessBeforeInitialization(EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)
-
LogBeanPostProcessor#postProcessAfterInitialization(EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)
-
LogDestructionAwareBeanPostProcessor#postProcessAfterInitialization(EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)
-
ApplicationContextAwareProcessor#postProcessAfterInitialization(EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)
-
ApplicationListenerDetector#postProcessAfterInitialization(EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)
-
ImportAwareBeanPostProcessor#postProcessAfterInitialization(EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)
-
-
创建自定义
LogBeanFactoryPostProcessor
,通过上面LogBeanDefinitionRegistryPostProcessor
的postProcessBeanDefinitionRegistry
方法添加。在这一步创建用户通过BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry(DefaultListableBeanFactory)
或者@Configuration
添加的BeanFactoryPostProcessor
,以及 Spring 自己添加的BeanFactoryPostProcessor
等类的相关 Bean。-
ImportAwareBeanPostProcessor#postProcessBeforeInstantiation(LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)
-
ApplicationListenerDetector#postProcessMergedBeanDefinition(RootBeanDefinition, LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)
-
ImportAwareBeanPostProcessor#postProcessAfterInstantiation(LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)
-
ImportAwareBeanPostProcessor#postProcessProperties(MutablePropertyValues, LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)
-
LogBeanPostProcessor#postProcessBeforeInitialization(LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)
-
LogDestructionAwareBeanPostProcessor#postProcessBeforeInitialization(LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)
-
ApplicationContextAwareProcessor#postProcessBeforeInitialization(LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)
-
ApplicationListenerDetector#postProcessBeforeInitialization(LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)
-
ImportAwareBeanPostProcessor#postProcessBeforeInitialization(LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)
-
LogBeanPostProcessor#postProcessAfterInitialization(LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)
-
LogDestructionAwareBeanPostProcessor#postProcessAfterInitialization(LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)
-
ApplicationContextAwareProcessor#postProcessAfterInitialization(LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)
-
ApplicationListenerDetector#postProcessAfterInitialization(LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)
-
ImportAwareBeanPostProcessor#postProcessAfterInitialization(LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)
-
-
这里会调用上一步创建的
BeanFactoryPostProcessor
对象的postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
方法。这里目前包含EventListenerMethodProcessor
对象。EventListenerMethodProcessor
是AnnotationConfigApplicationContext()
初始化时,创建new AnnotatedBeanDefinitionReader(this)
对象时,通过调用AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry)
方法注册到容器中的。 -
LogBeanFactoryPostProcessor#postProcessBeanFactory(DefaultListableBeanFactory)
-
到此为止,
invokeBeanFactoryPostProcessors(beanFactory)
方法调用完毕。 -
下面开始调用
registerBeanPostProcessors(beanFactory)
方法。 -
添加
PostProcessorRegistrationDelegate.BeanPostProcessorChecker
实例,以下执行BeanPostProcessor
方法时,都会带上。 -
创建
AutowiredAnnotationBeanPostProcessor
Bean,Name:org.springframework.context.annotation.internalAutowiredAnnotationProcessor
-
ImportAwareBeanPostProcessor#postProcessBeforeInstantiation(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)
-
ApplicationListenerDetector#postProcessMergedBeanDefinition(RootBeanDefinition, AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)
-
ImportAwareBeanPostProcessor#postProcessAfterInstantiation(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)
-
ImportAwareBeanPostProcessor#postProcessProperties(MutablePropertyValues, AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)
-
AutowiredAnnotationBeanPostProcessor#setBeanFactory(DefaultListableBeanFactory)
-
LogBeanPostProcessor#postProcessBeforeInitialization(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)
-
LogDestructionAwareBeanPostProcessor#postProcessBeforeInitialization(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)
-
ApplicationContextAwareProcessor#postProcessBeforeInitialization(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)
-
ApplicationListenerDetector#postProcessBeforeInitialization(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)
-
ImportAwareBeanPostProcessor#postProcessBeforeInitialization(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)
-
BeanPostProcessorChecker#postProcessBeforeInitialization(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)
-
LogBeanPostProcessor#postProcessAfterInitialization(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)
-
LogDestructionAwareBeanPostProcessor#postProcessAfterInitialization(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)
-
ApplicationContextAwareProcessor#postProcessAfterInitialization(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)
-
ApplicationListenerDetector#postProcessAfterInitialization(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)
-
ImportAwareBeanPostProcessor#postProcessAfterInitialization(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)
-
BeanPostProcessorChecker#postProcessAfterInitialization(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)
-
-
创建
CommonAnnotationBeanPostProcessor
Bean,Name:org.springframework.context.annotation.internalCommonAnnotationProcessor
-
ImportAwareBeanPostProcessor#postProcessBeforeInstantiation(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)
-
ApplicationListenerDetector#postProcessMergedBeanDefinition(RootBeanDefinition, CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)
-
ImportAwareBeanPostProcessor#postProcessAfterInstantiation(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)
-
ImportAwareBeanPostProcessor#postProcessProperties(MutablePropertyValues, CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)
-
LogBeanPostProcessor#postProcessBeforeInitialization(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)
-
LogDestructionAwareBeanPostProcessor#postProcessBeforeInitialization(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)
-
ApplicationContextAwareProcessor#postProcessBeforeInitialization(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)
-
ApplicationListenerDetector#postProcessBeforeInitialization(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)
-
ImportAwareBeanPostProcessor#postProcessBeforeInitialization(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)
-
BeanPostProcessorChecker#postProcessBeforeInitialization(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)
-
LogBeanPostProcessor#postProcessAfterInitialization(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)
-
LogDestructionAwareBeanPostProcessor#postProcessAfterInitialization(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)
-
ApplicationContextAwareProcessor#postProcessAfterInitialization(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)
-
ApplicationListenerDetector#postProcessAfterInitialization(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)
-
ImportAwareBeanPostProcessor#postProcessAfterInitialization(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)
-
BeanPostProcessorChecker#postProcessAfterInitialization(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)
-
-
创建
AnnotationAwareAspectJAutoProxyCreator
,Name:org.springframework.aop.config.internalAutoProxyCreator
。也许是因为配置了@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
。这个再探究竟?-
ImportAwareBeanPostProcessor#postProcessBeforeInstantiation(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)
-
CommonAnnotationBeanPostProcessor#postProcessBeforeInstantiation(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)
-
AutowiredAnnotationBeanPostProcessor#postProcessBeforeInstantiation(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)
-
ApplicationListenerDetector#postProcessMergedBeanDefinition(RootBeanDefinition, AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)
-
CommonAnnotationBeanPostProcessor#postProcessMergedBeanDefinition(RootBeanDefinition, AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)
-
AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition(RootBeanDefinition, AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)
-
ImportAwareBeanPostProcessor#postProcessAfterInstantiation(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)
-
CommonAnnotationBeanPostProcessor#postProcessAfterInstantiation(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)
-
AutowiredAnnotationBeanPostProcessor#postProcessAfterInstantiation(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)
-
ImportAwareBeanPostProcessor#postProcessProperties(MutablePropertyValues, AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)
-
CommonAnnotationBeanPostProcessor#postProcessProperties(MutablePropertyValues, AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)
-
AutowiredAnnotationBeanPostProcessor#postProcessProperties(MutablePropertyValues, AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)
-
LogBeanPostProcessor#postProcessBeforeInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)
-
LogDestructionAwareBeanPostProcessor#postProcessBeforeInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)
-
ApplicationContextAwareProcessor#postProcessBeforeInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)
-
ApplicationListenerDetector#postProcessBeforeInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)
-
ImportAwareBeanPostProcessor#postProcessBeforeInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)
-
BeanPostProcessorChecker#postProcessBeforeInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)
-
CommonAnnotationBeanPostProcessor#postProcessBeforeInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)
-
AutowiredAnnotationBeanPostProcessor#postProcessBeforeInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)
-
LogBeanPostProcessor#postProcessAfterInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)
-
LogDestructionAwareBeanPostProcessor#postProcessAfterInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)
-
ApplicationContextAwareProcessor#postProcessAfterInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)
-
ApplicationListenerDetector#postProcessAfterInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)
-
ImportAwareBeanPostProcessor#postProcessAfterInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)
-
BeanPostProcessorChecker#postProcessAfterInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)
-
CommonAnnotationBeanPostProcessor#postProcessAfterInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)
-
AutowiredAnnotationBeanPostProcessor#postProcessAfterInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)
-
-
预加载
Config
、UserService
等 Bean。下面以UserService
为例:-
ImportAwareBeanPostProcessor#postProcessBeforeInstantiation(UserService, UserService)
-
AnnotationAwareAspectJAutoProxyCreator#postProcessBeforeInstantiation(UserService, UserService)
-
CommonAnnotationBeanPostProcessor#postProcessBeforeInstantiation(UserService, UserService)
-
AutowiredAnnotationBeanPostProcessor#postProcessBeforeInstantiation(UserService, UserService)
-
构造函数
-
CommonAnnotationBeanPostProcessor#postProcessMergedBeanDefinition(RootBeanDefinition, UserService, UserService)
-
AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition(RootBeanDefinition, UserService, UserService)
-
ApplicationListenerDetector#postProcessMergedBeanDefinition(RootBeanDefinition, UserService, UserService)
-
ImportAwareBeanPostProcessor#postProcessAfterInstantiation(UserService, UserService)
-
AnnotationAwareAspectJAutoProxyCreator#postProcessAfterInstantiation(UserService, UserService)
-
CommonAnnotationBeanPostProcessor#postProcessAfterInstantiation(UserService, UserService)
-
AutowiredAnnotationBeanPostProcessor#postProcessAfterInstantiation(UserService, UserService)
-
ImportAwareBeanPostProcessor#postProcessProperties(MutablePropertyValues, UserService, UserService)
-
AnnotationAwareAspectJAutoProxyCreator#postProcessProperties(MutablePropertyValues, UserService, UserService)
-
AnnotationAwareAspectJAutoProxyCreator#postProcessPropertyValues(MutablePropertyValues, PropertyDescriptor[], UserService, UserService)
-
CommonAnnotationBeanPostProcessor#postProcessProperties(MutablePropertyValues, UserService, UserService)
-
AutowiredAnnotationBeanPostProcessor#postProcessProperties(MutablePropertyValues, UserService, UserService)
-
UserService#setBeanFactory(DefaultListableBeanFactory)
-
LogBeanPostProcessor#postProcessBeforeInitialization(UserService, UserService)
-
LogDestructionAwareBeanPostProcessor#postProcessBeforeInitialization(UserService, UserService)
-
ApplicationContextAwareProcessor#postProcessBeforeInitialization(UserService, UserService)
-
UserService#setApplicationContext(AnnotationConfigApplicationContext)
-
ImportAwareBeanPostProcessor#postProcessBeforeInitialization(UserService, UserService)
-
BeanPostProcessorChecker#postProcessBeforeInitialization(UserService, UserService)
-
AnnotationAwareAspectJAutoProxyCreator#postProcessBeforeInitialization(UserService, UserService)
-
CommonAnnotationBeanPostProcessor#postProcessBeforeInitialization(UserService, UserService)
-
AutowiredAnnotationBeanPostProcessor#postProcessBeforeInitialization(UserService, UserService)
-
ApplicationListenerDetector#postProcessBeforeInitialization(UserService, UserService)
-
UserService#afterPropertiesSet()
-
UserService#init()
-
LogBeanPostProcessor#postProcessAfterInitialization(UserService, UserService)
-
LogDestructionAwareBeanPostProcessor#postProcessAfterInitialization(UserService, UserService)
-
ApplicationContextAwareProcessor#postProcessAfterInitialization(UserService, UserService)
-
ImportAwareBeanPostProcessor#postProcessAfterInitialization(UserService, UserService)
-
BeanPostProcessorChecker#postProcessAfterInitialization(UserService, UserService)
-
AnnotationAwareAspectJAutoProxyCreator#postProcessAfterInitialization(UserService, UserService)
-
CommonAnnotationBeanPostProcessor#postProcessAfterInitialization(UserService, UserService)
-
AutowiredAnnotationBeanPostProcessor#postProcessAfterInitialization(UserService, UserService)
-
ApplicationListenerDetector#postProcessAfterInitialization(UserService, UserService)
-
-
销毁 Bean,
beanFactory.destroyBean(bean)
-
LogDestructionAwareBeanPostProcessor#postProcessBeforeDestruction(UserService, UserService)
-
UserService#destroy()
-
不知道有没有人关注这个附录日志,这里再重复一遍:在下一篇文章 Spring Bean 生命周期概述 中,D瓜哥将针对 Spring Bean 的整个生命周期展开详细说明。
3.7. Bean 生命周期概述
在 Spring 启动流程概述 中,分析了 Spring 的启动流程。本文就来说明一下 Spring Bean 整个生命周期。如果有不清楚的地方,可以参考上文的“附录:启动日志”。
直接上图:Spring Bean 生命周期流程图。内容较多,图片文字偏小,请放大看(矢量图,可以任意放大):
下面是文字说明。
3.7.1. Bean 生命周期简述
-
调用
InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation
,主要是判断AnnotationAwareAspectJAutoProxyCreator
是否可以生成代理。 -
调用构造函数
-
调用
MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition
,主要是通过CommonAnnotationBeanPostProcessor
、AutowiredAnnotationBeanPostProcessor
收集依赖信息。 -
InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation
,这步什么也没做。 -
调用
InstantiationAwareBeanPostProcessor#postProcessProperties
,主要是完成依赖注入。 -
调用
AutowiredAnnotationBeanPostProcessor#setBeanFactory
,注入BeanFactory
等相关信息。 -
调用
BeanPostProcessor#postProcessBeforeInitialization
,主要是注入ApplicationContext
等相关信息。 -
调用
InitializingBean#afterPropertiesSet
、init-method
方法 -
调用
BeanPostProcessor#postProcessAfterInitialization
,主要是生成 AOP 代理类。
3.7.2. Bean 生命周期详解
从 getBean()
方法获取 Bean 时,如果缓存中没有对应的 Bean,则会创建 Bean,整个流程如下:
-
InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation
— 目前有如下四个:-
ImportAwareBeanPostProcessor
— 继承父类实现,无所事事。 -
AnnotationAwareAspectJAutoProxyCreator
— 继承父类实现,判断是否属于基础切面类,如果有指定的 Target 则生成代理。 -
CommonAnnotationBeanPostProcessor
— 无所事事。 -
AutowiredAnnotationBeanPostProcessor
— 继承父类实现,无所事事。
-
-
构造函数
-
MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition
— 目前有如下三个:-
CommonAnnotationBeanPostProcessor
— 收集@Resource
依赖信息,initMethods
和destroyMethods
等信息。(就是@PostConstruct
和@PreDestroy
标注的方法。)这些信息被缓存到了this.injectionMetadataCache
变量中,注入时从这个变量中取值。 -
AutowiredAnnotationBeanPostProcessor
— 收集@Autowired
的依赖信息。这些信息被缓存到了this.injectionMetadataCache
变量中,注入时从这个变量中取值。 -
ApplicationListenerDetector
— 判断 Bean 是否是一个ApplicationListener
,是则保留,在后面的postProcessAfterInitialization
方法中,加入到容器的applicationListeners
中。
-
-
InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation
— 与上面的postProcessBeforeInstantiation
方法对应,目前有如下四个:-
ImportAwareBeanPostProcessor
— 继承父类实现,无所事事。 -
AnnotationAwareAspectJAutoProxyCreator
— 继承父类实现,无所事事。 -
CommonAnnotationBeanPostProcessor
— 无所事事。 -
AutowiredAnnotationBeanPostProcessor
— 无所事事。
-
-
InstantiationAwareBeanPostProcessor#postProcessProperties
— 目前有如下三个:-
ImportAwareBeanPostProcessor
— 如果 Bean 是EnhancedConfiguration
(它继承了BeanFactoryAware
) 的实现类,则注入BeanFactory
。 -
AnnotationAwareAspectJAutoProxyCreator
— 无所事事。 -
CommonAnnotationBeanPostProcessor
— 完成@Resource
依赖注入。在这里会递归创建所依赖 Bean。调试代码,弄清楚。
-
AutowiredAnnotationBeanPostProcessor
— 完成@Autowired
和@Value
注入
-
-
InstantiationAwareBeanPostProcessor#postProcessPropertyValues
— 从 5.1 开始废弃,使用上面方法代替。这里要注意,并不是执行完四个类的 postProcessProperties
方法,再去执行四个类的postProcessPropertyValues
方法。而是以类为顺序的,执行完一个类的postProcessProperties
方法,然后去执行postProcessPropertyValues
方法。执行完一个类,再去执行下一个类。这个现象在下面的日志中有反应。 -
AutowiredAnnotationBeanPostProcessor#setBeanFactory(DefaultListableBeanFactory)
— 通过AbstractAutowireCapableBeanFactory#invokeAwareMethods
方法如下Aware
注入:-
BeanNameAware
-
BeanClassLoaderAware
-
BeanFactoryAware
-
-
BeanPostProcessor#postProcessBeforeInitialization
— 目前有-
用户手动添加的
BeanPostProcessor
-
ApplicationContextAwareProcessor
— 完成如下六个Aware
的注入:-
EnvironmentAware
-
EmbeddedValueResolverAware
-
ResourceLoaderAware
-
ApplicationEventPublisherAware
-
MessageSourceAware
-
ApplicationContextAware
-
-
ImportAwareBeanPostProcessor
— 如果实现了ImportAware
接口,则注入importMetadata
信息。 -
BeanPostProcessorChecker
— 无所事事。 -
AnnotationAwareAspectJAutoProxyCreator
— 无所事事。 -
CommonAnnotationBeanPostProcessor
— 要调用LifecycleMetadata#invokeInitMethods
方法,但是,里面去没有任何实现,似乎调用了全局设置的初始化操作。需要找文档确认一下。 -
AutowiredAnnotationBeanPostProcessor
— 继承父类实现,无所事事。 -
ApplicationListenerDetector
— 无所事事。
-
-
InitializingBean#afterPropertiesSet()
-
init-method
-
BeanPostProcessor#postProcessAfterInitialization
方法 — 目前有-
用户手动添加的
BeanPostProcessor
-
ApplicationContextAwareProcessor
— 继承默认实现,无所事事。 -
ImportAwareBeanPostProcessor
— 继承默认实现,无所事事。 -
BeanPostProcessorChecker
— 如果 Bean 是BeanPostProcessor
子类,则检查BeanPostProcessor
数量。 -
AnnotationAwareAspectJAutoProxyCreator
— 检查 Bean 和提前暴露的引用是否相同,不同则重新生成代理对象。注意:绝大部分的 AOP 代理生成都是在这个方法中完成的。在 Spring AOP 源码分析:入门 中有更详细的说明。 -
CommonAnnotationBeanPostProcessor
— 继承父类实现,无所事事。 -
AutowiredAnnotationBeanPostProcessor
— 继承父类实现,无所事事。 -
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
对象:
-
ConfigurationClassPostProcessor
-
AnnotationAwareAspectJAutoProxyCreator
-
CommonAnnotationBeanPostProcessor
-
AutowiredAnnotationBeanPostProcessor
3.7.3.3. MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition
通过 AbstractAutowireCapableBeanFactory#applyMergedBeanDefinitionPostProcessors
调用 MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition
方法。变量: getBeanPostProcessorCache().mergedDefinition
。这个方法主要干什么?通过 CommonAnnotationBeanPostProcessor#applyMergedBeanDefinitionPostProcessors
调用 CommonAnnotationBeanPostProcessor#findResourceMetadata
可以看出,这个地方可以获取依赖信息。带验证。系统中有如下四个类:
-
CommonAnnotationBeanPostProcessor
-
AutowiredAnnotationBeanPostProcessor
-
ApplicationListenerDetector
-
InitDestroyAnnotationBeanPostProcessor
— 这个有吗?没有加入到变量中。
3.7.3.4. InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation
有一点重要的信息,日志中没有体现出来。设置 Bean 的属性是在执行 BeanPostProcessor
调用之前完成的。在 AbstractAutowireCapableBeanFactory#doCreateBean
方法中,调用了 AbstractAutowireCapableBeanFactory#populateBean
方法来设置属性,然后去调用的 BeanPostProcessor
和 init
方法。populateBean
方法是通过调用 InstantiationAwareBeanPostProcessor#postProcessProperties
方法来完成注入,其中 CommonAnnotationBeanPostProcessor
,AutowiredAnnotationBeanPostProcessor
分别处理不同的注解。下面是 populateBean
方法更详细的说明。
在注入 Bean 属性之前,调用 InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation
。(从变量 getBeanPostProcessorCache().instantiationAware
中获取列表。)容器完成初始化后,有 ImportAwareBeanPostProcessor
,AnnotationAwareAspectJAutoProxyCreator
, CommonAnnotationBeanPostProcessor
,AutowiredAnnotationBeanPostProcessor
四个 InstantiationAwareBeanPostProcessor
对象。但是,这四个类,没有做任何操作。如果返回值为 false
则中断,不再继续遍历 InstantiationAwareBeanPostProcessor
列表。
-
ConfigurationClassPostProcessor
-
AnnotationAwareAspectJAutoProxyCreator
-
CommonAnnotationBeanPostProcessor
-
AutowiredAnnotationBeanPostProcessor
3.7.3.5. InstantiationAwareBeanPostProcessor#postProcessProperties
接着调用 InstantiationAwareBeanPostProcessor#postProcessProperties
方法来完成属性注入。
3.7.3.6. InstantiationAwareBeanPostProcessor#postProcessPropertyValues
然后再执行 InstantiationAwareBeanPostProcessor#postProcessPropertyValues
。这个方法马上从 5.1 开始要废弃掉,使用上述 postProcessProperties
代替。
到这里 populateBean
方法结束。
3.7.4. Bean 销毁流程
-
调用
beanFactory.destroyBean(bean)
方法,开始销毁 Bean。 -
调用
DestructionAwareBeanPostProcessor#postProcessBeforeDestruction(Object bean, String beanName)
—ApplicationListenerDetector
就是一个DestructionAwareBeanPostProcessor
。但是,Bean 销毁时,不知道为什么没有被调用。 -
调用
DisposableBean#destroy()
方法 -
如果还有
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 — 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
实例可以分为三种:ImportSelector
、ImportBeanDefinitionRegistrar
和常规组件类。示例如下:
1
2
3
4
@Configuration
@Import(LogImportSelector.class)
public static class Config {
}
在 org.springframework.context.annotation.ConfigurationClassParser#processImports
方法中,集中了对 @Import
注解的处理。从代码可以非常清晰地看出,分了三种情况进行处理:
-
ImportSelector
-
ImportBeanDefinitionRegistrar
-
常规组件
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.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
可以注册 BeanFactoryPostProcessor
的 BeanDefinition
。
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
,或者将 Scope
从 SINGLETON
改为 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>)
方法来处理。根据代码可以整理出处理流程如下:
-
如果
beanFactory
是一个BeanDefinitionRegistry
实例,则:-
首先处理参数传过来的
List<BeanFactoryPostProcessor> beanFactoryPostProcessors
对象-
如果
postProcessor
是BeanDefinitionRegistryPostProcessor
实现类,则直接调用postProcessBeanDefinitionRegistry
,然后加入到List<BeanDefinitionRegistryPostProcessor> registryProcessors
列表中; -
如果不是,则加入到
List<BeanFactoryPostProcessor> regularPostProcessors
列表中;
-
-
从
BeanFactory
中通过beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false)
方法获取BeanDefinitionRegistryPostProcessor
名称列表。筛选出实现了PriorityOrdered
接口的实例,然后排序再逐一调用postProcessBeanDefinitionRegistry
方法。最后,加入到List<BeanDefinitionRegistryPostProcessor> registryProcessors
列表中。 -
从
BeanFactory
中通过beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false)
方法获取BeanDefinitionRegistryPostProcessor
名称列表。筛选出实现了Ordered
接口的实例,然后排序再逐一调用postProcessBeanDefinitionRegistry
方法。最后,加入到List<BeanDefinitionRegistryPostProcessor> registryProcessors
列表中。(注意:上一步已经调用过的则不再重复调用。) -
从
BeanFactory
中通过beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false)
方法获取BeanDefinitionRegistryPostProcessor
名称列表。剔除掉前两步调用过的类,排序再逐一调用postProcessBeanDefinitionRegistry
方法。最后,加入到List<BeanDefinitionRegistryPostProcessor> registryProcessors
列表中。要强调的一点是:这里是通过一个循环来反复执行这一步,D瓜哥认为是在调用postProcessBeanDefinitionRegistry
方法中,有会参数新注册的BeanDefinitionRegistryPostProcessor
,所以需要反复调用。大家如果有不同见解,也欢迎留言讨论。 -
调用
BeanDefinitionRegistryPostProcessor
对象的postProcessBeanFactory
方法; -
调用
BeanFactoryPostProcessor
对象的postProcessBeanFactory
方法;
-
-
如果
beanFactory
不是BeanDefinitionRegistry
实例,则直接调用BeanFactoryPostProcessor
对象的postProcessBeanFactory
方法; -
从
BeanFactory
中通过beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false)
方法获取BeanFactoryPostProcessor
名称列表。将其分为:-
实现
PriorityOrdered
接口的实例 -
实现
Ordered
接口的实例 -
未排序的实例
按照这个顺序,排除已经处理过的实例,再分类,然后排序再跟着这个顺序依次逐一调用
BeanFactoryPostProcessor
对象的postProcessBeanFactory
方法;
-
-
最后,向
BeanFactory
注册ApplicationListenerDetector
实例。
3.8.4. InstantiationAwareBeanPostProcessor
注意区分 Instantiation
和 Initialization
。
-
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瓜哥个人认为 FactoryBean
和 ObjectFactory
功能有些重叠,都是为了创建对象而设计的。
通过 ObjectFactory
的文档,Spring 给出了官方解释:
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 —
* 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 内置了大量的应用:
-
ApplicationContextAwareProcessor
—Aware
接口的处理。 -
InitDestroyAnnotationBeanPostProcessor
—init-method
和destroy-method
方法的调用。 -
InstantiationAwareBeanPostProcessor
-
CommonAnnotationBeanPostProcessor
— 常用注解@Resource
、@PostConstruct
和@PreDestroy
的解析。 -
AutowiredAnnotationBeanPostProcessor
— 常用注解@Autowired
、@Value
和@Inject
的解析。 -
BeanValidationPostProcessor
— 字段校验。 -
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)
来分别调用 postProcessBeforeInitialization
和 postProcessAfterInitialization
方法。
3.8.9. 各种 Aware
有时,自己开发的代码可能需要 ApplicationContext
或者 BeanFactory
等实例。则可以通过实现相应的 Aware
接口来获得对应的实例。目前有如下这些 Aware
接口:
-
ApplicationContextAware
-
ApplicationEventPublisherAware
-
BeanClassLoaderAware
-
BeanFactoryAware
-
BeanNameAware
-
BootstrapContextAware
-
EmbeddedValueResolverAware
-
EnvironmentAware
-
ImportAware
-
LoadTimeWeaverAware
-
MessageSourceAware
-
NotificationPublisherAware
-
ResourceLoaderAware
-
SchedulerContextAware
-
ServletConfigAware
-
ServletContextAware
在代码 org.springframework.context.support.ApplicationContextAwareProcessor#invokeAwareInterfaces
中,集中处理了 EnvironmentAware
、EmbeddedValueResolverAware
、ResourceLoaderAware
、ApplicationEventPublisherAware
、MessageSourceAware
和 ApplicationContextAware
等六种 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.10. InitializingBean
与 init-method
设置 init-method
方法和实现 InitializingBean
方法达到的效果是一样的。在代码 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#invokeInitMethods
中可以看到很详细的处理流程:
-
判断 Bean 是否是
InitializingBean
实例,如果是,则做类型转换,然后再调用其afterPropertiesSet()
方法; -
获取
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;
}
}
由于 DestructionAwareBeanPostProcessor
是 BeanPostProcessor
子类,由此可见,可以像操作 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. DisposableBean
与 destroy-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
方法中实现的。
和 InitializingBean
与 init-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 整合主要涉及下面几个类:
-
LocalSessionFactoryBean
— 声明 Hibernate 配置信息;或者注入数据库连接池对象。 -
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;
}
}
上述示例代码中的循环依赖情况如下:
3.9.2. 源码剖析
3.9.2.1. 三级缓存
D瓜哥在 深入剖析 Spring 核心数据结构:BeanFactory 中,概要性地对 BeanFactory
的属性做了一一说明。
而其中的“三级缓存”属性,则是解决循环依赖问题的关键所在:
-
Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256)
:Bean 名称到单例 Bean 的映射,用于存放完全初始化好的 Bean。可以理解成,这就是所谓的容器。这是一级缓存。 -
Map<String, Object> earlySingletonObjects = new HashMap<>(16)
:Bean 到“未成熟”单例 Bean 的映射。该 Bean 对象只是被创建出来,但是还没有注入依赖。在容器解决循环依赖时,用于存储中间状态。这是二级缓存。 -
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 的内部函数调用,有可以描述成如下:
在 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 的生命周期做了概要的介绍。这里就体现出来 CommonAnnotationBeanPostProcessor
和 AutowiredAnnotationBeanPostProcessor
的作用。上面我们用的是 @Autowired
注解。所以,这里使用 AutowiredAnnotationBeanPostProcessor
来处理。
依赖注入的调用链
查找依赖的调用链很繁琐,中间有牵涉到 Bean 创建的过程,这里只列出调用过程中的主要方法列表,需要请根据自己需要来单步调试。
完成依赖注入的调用链如下:
-
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean
-
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessProperties
-
org.springframework.beans.factory.annotation.InjectionMetadata#inject
-
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject
-
org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveDependency
-
org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency
-
org.springframework.beans.factory.config.DependencyDescriptor#resolveCandidate
-
org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String)
-
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 → B
和 B → A
两层循环依赖来说明问题
-
通过
applicationContext.getBean(A.class)
方法,委托给AbstractBeanFactory#doGetBean
方法来尝试获取 Bean;获取不到则开始创建;-
Bean 是调用
instanceWrapper = createBeanInstance(beanName, mbd, args);
方法创建出来了实例,然后又通过addSingletonFactory(beanName, () → getEarlyBeanReference(beanName, mbd, bean));
将已经创建的实例封装到一个ObjectFactory<?> singletonFactory
匿名类中,放入到三级缓存中。 -
在
populateBean(beanName, mbd, instanceWrapper);
方法,通过CommonAnnotationBeanPostProcessor
和AutowiredAnnotationBeanPostProcessor
的postProcessProperties
查找依赖,完成注入。-
查找依赖时,就会通过调用
getBean(beanName)
获取 BeanB
。此时,还没有 BeanB
,则会从这里的第二步开始执行,创建实例,封装后加入到三级缓存singletonFactories
中,调用populateBean(beanName, mbd, instanceWrapper);
方法,通过CommonAnnotationBeanPostProcessor
和AutowiredAnnotationBeanPostProcessor
的postProcessProperties
查找依赖,完成注入。依赖注入的过程,请看 依赖注入的调用链 小节。 -
到这里,就要查找 Bean
A
了,一二三级缓存依次来查找(DefaultSingletonBeanRegistry#getSingleton(beanName, allowEarlyReference)
),在三级缓存中,找到了对应的ObjectFactory<?> singletonFactory
实例,然后调用getObject()
方法,获得A
的实例,将其加入到二级缓存中,将三级中的相关内容清理掉。从这里也可以看出,通过AbstractBeanFactory#doGetBean
方法获得的 Bean 不一定是完全初始化好的 Bean,有可能是一个未完成初始化的实例对象。 -
获得
A
的实例后,就可以完成 BeanB
的初始化,调用DefaultSingletonBeanRegistry#addSingleton
方法,将其加入一级缓存singletonObjects
中,也就是容器中。(由于 BeanB
可以直接完成依赖注入,则它不会从三级缓存跳到二级缓存。最后的三级缓存在调用addSingleton
方法时,直接被清理掉了。)
-
-
到这里就可以获取 Bean
B
了,然后完成A
的依赖注入。
-
-
最后,通过调用
DefaultSingletonBeanRegistry#addSingleton
方法,将 BeanA
加入到一级缓存singletonObjects
中,也就是容器中。所有的初始化工作就完成了。
需要注意的是,有两种情况,Spring 是没办法完成循环注入的:
-
构造函数注入 — 这种要求在实例之前创建好依赖的实例,但是明显无法完成,所以不能解决循环依赖。
-
PROTOTYPE
类型的 Bean 相互依赖 — 刚刚看到,上面的三级缓存变量都是为SINGLETON
类型的 Bean 准备的。PROTOTYPE
类型的 Bean 在检查到循环依赖时,就直接抛异常了。
3.11. 事件发布
容器启动伊始,就会检查容器内是否存在名称为 applicationEventMulticaster
的 ApplicationEventMulticaster
对象实例。有的话就使用提供的实现,没有则默认初始化一个 SimpleApplicationEventMulticaster
作为将会使用的 ApplicationEventMulticaster
。
在 refresh()
时,先调用 initMessageSource()
初始化 MessageSource
实例;然后调用 initApplicationEventMulticaster()
初始化 ApplicationEventMulticaster
。
Spring 是以 Bean 为核心的。Bean 的配置、配置的读取和解析、以合适的数据结构对 Bean 元数据进行各种操作等。
Spring 是如何解决构造函数依赖的呢?以及如何注入呢?
3.12. 内置 PostProcessor
的注册
在 AnnotationConfigUtils.registerAnnotationConfigProcessors(BeanDefinitionRegistry)
方法中,注册了 Spring 内置的一些核心 PostProcessor
:
-
ConfigurationClassPostProcessor
-
AutowiredAnnotationBeanPostProcessor
-
CommonAnnotationBeanPostProcessor
-
PersistenceAnnotationBeanPostProcessor
? — 这个得看是否需要 -
EventListenerMethodProcessor
-
DefaultEventListenerFactory
通过对 AnnotationConfigUtils.registerAnnotationConfigProcessors
调用追踪来看,在如下地方进行了调用:
-
通过
AnnotationConfigBeanDefinitionParser.parse
在处理<context:annotation-config/>
时; -
通过
ComponentScanBeanDefinitionParser.parse
在处理<context:component-scan/>
时; -
通过
AnnotatedBeanDefinitionReader
构造函数在初始化时; -
通过
ClassPathBeanDefinitionScanner.scan
在扫描类路径时;
覆盖了 XML 配置文件和注解配置两种最核心的场景。
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 定义的基本数据结构,存放属性值、构造函数参数值等等。
-
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
也是一种策略模式,加载资源的策略。
classpath*:
与 classpath:
的唯一区别就在于,如果能够在 classpath 中找到多个指定的资源,则返回多个。
ApplicationContext
继承了 ResourcePatternResolver
,当 然就间接实现了 ResourceLoader
接口。所以,任何的 ApplicationContext
实现都可以看作是一个 ResourceLoader
甚至 ResourcePatternResolver
。而这就是 ApplicationContext
支持 Spring 内统一资源加载策略的真相。
AbstractApplicationContext
继承了 DefaultResourceLoader
,那么,它的 getResource(String)
当然就直接用 DefaultResourceLoader
的了。
AbstractApplicationContext
类的内 部声明有一个 resourcePatternResolver
,类型是 ResourcePatternResolver
,对应的实例类型为 PathMatchingResourcePatternResolver
。
ApplicationContext
的实现类在作为 ResourceLoader
或者 ResourcePatternResolver
时候的行为,完全就是委派给了 PathMatchingResourcePatternResolver
和 DefaultResourceLoader
来做。
Resource
类图
3.13.2. 标签解析
<bean>
的子标签 <property>
会根据属性不同,被解析成不同的对象,例如 TypedStringValue
、 RuntimeBeanReference
,然后再被封装成 PropertyValue
对象,最后被存放在 GenericBeanDefinition
对象的 MutablePropertyValues propertyValues
属性(在 AbstractBeanDefinition
中声明)中。在首次获取对象时,根据这里的信息再逐步转化成不同对象。
这里还有一点需求说明:
-
GenericBeanDefinition
对象对应 XML 等原始配置文件中对 Bean 的定义和配置。 -
RootBeanDefinition
则代表生成实例时, Bean 对应的定义和配置。是根据GenericBeanDefinition
的配置来生成的。如果GenericBeanDefinition
定义的是子 Bean 的话,则会同时合并父类的相关属性。这个合并怎么实现的? -
BeanWrapperImpl
对象是对创建后的实例对象的包装,被存储在 -
对象之间的依赖关系存放在
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#dependentBeanMap
(Map<String, Set<String>>
,bean name -→ Set of dependent bean names) 中。 而AbstractBeanFactory
通过继承FactoryBeanRegistrySupport
, 而FactoryBeanRegistrySupport
继承了DefaultSingletonBeanRegistry
,进而获得了对这个关系的访问。 -
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
中dependentBeanMap
和dependenciesForBeanMap
是什么关系?-
例如 A ref B, A ref C,就是 类 A 中有属性 B 和 C。
-
在
dependenciesForBeanMap
存到就是 A → (B, C); -
而
dependentBeanMap
中存的是 B → A 和 C → A。这个还要进一步确认。
-
疑问:
-
org.springframework.beans.factory.support.ConstructorResolver#autowireConstructor
中this.beanFactory.initBeanWrapper(bw)
,为什么把对BeanWrapper
对初始化封装在BeanFactory
而不是BeanWrapper
内部?
3.13.3. 注解解析
说明一下常用的注解的处理过程:
-
@Component
-
@Service
-
@Repository
-
@Controller
-
@Autowired
-
@Resource
-
@Value
-
@Qualifier
-
@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;
}
-
可以解析到其他的
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 中看到最权威的介绍。
-
Join point(连接点): 所谓的连接点是指那些被拦截到的点。在 Spring 中,连接点指的是方法,因为 Spring 只支持方法类型的连接点。在 Spring 中,使用
-
Pointcut(切入点): 所谓的切入点,是指要对哪些 Join point(连接点) 进行拦截的定义。如果 Join point(连接点) 是全集,那么 Pointcut(切入点) 就是被选中的子集。写 AOP 代码的时候,一般是用 Pointcut(切入点) 表达式进行对 Join point(连接点) 进行选择。
-
Advice(通知/增强): 所谓的通知就是指拦截到 Join point(连接点) 之后所要做的事情。通知根据作用位置不同,又细分为:
-
Before advice(前置通知): 在 Join point(连接点) 之前运行的通知。这种通知,不能阻止执行流程继续到 Join point(连接点)。
-
After returning advice(后置通知): 在 Join point(连接点) 之后运行的通知。当然,如果在 Join point(连接点) 执行过程中,抛出异常,则可能就不执行了。
-
After throwing advice(异常通知): 方法抛出异常后,将会执行的通知。
-
After (finally) advice(最终通知): 无论如何都会执行的通知,即使抛出异常。
-
Around advice(环绕通知): 围绕在 Join point(连接点) 的通知,方法执行前和执行后,都可以执行自定义行为。同时,也可以决定是返回 Join point(连接点) 的返回值,还是返回自定义的返回值。
-
-
Aspect(切面): 是切入点和通知的结合。
-
Advisor(通知器/顾问): 和 Aspect(切面) 很相似。
-
Introduction(引介): 引介是一种特殊的通知在不修改类代码的前提下,Introduction 可以在运行期为类动态地添加一些方法或属性。
-
Target object(目标对象): 代理的目标对象。
-
AOP proxy(代理): 一个类被AOP织入增强后,就产生一个结果代理类。
-
Weaving(织入): 是指把增强应用到目标对象来创建新的代理对象的过程。Spring 是通过实现后置处理器
BeanPostProcessor
接口来实现织入的。也就是在 Bean 完成初始化之后,通过给目标对象生成代理对象,并交由 Spring IoC 容器来接管,这样再去容器中获取到的目标对象就是已经增强过的代理对象。
一图胜千言,通过下面的图来总结一下,希望能够帮助读者记忆。
4.1.2. 实现原理
Spring AOP 的实现原理说起来只有一句话:如果使用接口,则用 JDK 的动态代理实现;如果没有实现接口,则使用 CGLIB 通过字节码技术来实现。如图:
JDK 的动态代理和 CGLIB 字节码技术在实现上也略有不同。如图:
关于动态代理,D瓜哥有篇文章还在酝酿,稍后发布出来,再详细介绍。
4.1.3. AOP 织入流程
在 Spring Bean 生命周期概述 中介绍的 Spring Bean 声明周期流程,再结合上面提到的实现原理,如果让你设计一个 AOP 功能,你会怎么设计?
大家想一想,AOP 的三要素是什么?无非就是:① Target object(目标对象),解决增强的作用对象问题;② Pointcut(切入点),解决在哪里增强的问题;③ Advice(通知/增强),解决怎么争强的问题。到这里,思路应该比较清晰了:
-
第一步:创建实例对象
-
第二步:提取切面信息,包括 Pointcut(切入点)、Advice(通知/增强)。
-
第三步:判断实例对象是否符合 Pointcut(切入点) 的选择条件;如果符合,执行下一步;否则直接跳过。
-
第四步:创建代理,在 Target object(目标对象) 的 Pointcut(切入点) 上,Weaving(织入) Advice(通知/增强) 操作。
很多人以为这就完事了,以后方法调用直接执行增强、执行原始方法就完事了。其实,并不是这样的。如下图:
经过前面四步处理后,Spring 把 Target object(目标对象) 和 Advice(通知/增强) 编织在一起后,再调用代理对象的方法,代理对象就会把调用再次处理,匹配的通知和方法按顺序执行;不匹配的方法,则不会执行通知,而是只执行方法本身。
洋洋洒洒又写了好长篇幅,关于这部分的源码分析另外单独开一篇文章详细介绍吧。
4.2. 入门
在上一篇文章 Spring AOP 处理流程概述 中,对 Spring AOP 有了一个整体认识。这篇文章就带大家做一个细致的源码分析。
4.2.1. 登堂入室
使用 Spring AOP 也很简单,只需要在配置类上加上 @EnableAspectJAutoProxy
注解即可。这个注解处理过程与 Spring 扩展点实践:整合 MyBATIS 中 “@MapperScan
处理” 类似,不同的是,Spring AOP 注册了 AnnotationAwareAspectJAutoProxyCreator
,它是一个 InstantiationAwareBeanPostProcessor
。具体的类图如下:
在正式开始源码分析之前,有一点必须强调一下:Spring AOP 只是借用了 AspectJ 的一些注解和个别关键 API,而整体实现是 Spring 自己完成的,并不是基于 AspectJ 实现的。这一点跟很多人的认识是不一样的,需要特别指出。
D瓜哥在 Spring Bean 生命周期概述 中指出:创建 AOP 代理对象,有两个时机:
-
调用
InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation
时,通过调用AnnotationAwareAspectJAutoProxyCreator
对象的postProcessBeforeInstantiation
方法来创建对象; -
调用
BeanPostProcessor#postProcessAfterInitialization
时,通过调用AnnotationAwareAspectJAutoProxyCreator
对象的postProcessAfterInitialization
方法来创建对象;
下面分别对这两个方法做更详细的介绍。
4.2.2. AnnotationAwareAspectJAutoProxyCreator#postProcessBeforeInstantiation
AnnotationAwareAspectJAutoProxyCreator
的 postProcessBeforeInstantiation
方法是从 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;
}
请注意代码中语法高亮的两行代码:
-
getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource)
获取了所有符合条件的增强信息。 -
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
分析,我们可以看出,核心处理也就是两个操作:
-
getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource)
获取了所有符合条件的增强信息。 -
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(Code Generator Library)是一个高性能的代码生成库,被广泛应用于 AOP 框架(Spring)中以提供方法拦截功能,主要以继承目标类的方式来进行拦截实现,因此 CGLIB 可以对无接口的类进行代理。
CGLIB代理主要通过操作字节码的方式为对象引入方法调用时访问操作,底层使用了ASM来操作字节码生成新的类,ASM是一个短小精悍的字节码操作框架。CGLIB的应用栈如下:
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(通知/增强)链。
在 JdkDynamicAopProxy
的 invoke
方法中,通过创建 ReflectiveMethodInvocation
对象,调用其 proceed()
方法,来完成增强的链式调用。
在 CglibAopProxy
的 intercept
方法中,通过创建 CglibMethodInvocation
对象,调用其 proceed()
方法,来完成增强的链式调用。 CglibMethodInvocation
继承了 ReflectiveMethodInvocation
。其实, CglibMethodInvocation
也是通过调用父类方法完成 AOP 切面调用的。这里就不再贴代码赘述了。
4.6. PointcutAdvisor
org.springframework.aop.PointcutAdvisor
才是真正的定义一个 Pointcut
和一个 Advice
的 Advisor
。
DefaultPointcutAdvisor
是最通用的 PointcutAdvisor
实现。
NameMatchMethodPointcutAdvisor
是细化后的 DefaultPointcutAdvisor
。
IntroductionAdvisor
与 PointcutAdvisor
最本质的区别是 IntroductionAdvisor
只能应用于类级别的拦截,只能使用 Introduction 型的 Advisor
。
Ordered
接口用于指定 Bean 的优先级。数字越小,优先级越高。
4.7. Spring AOP 的织入
ProxyFactory
是最基本的一个织入器实现。 ProxyFactory
只需要指定如下两个最基本的东西:
-
对其要进行织入的目标对象。
-
将要应用到目标对象的 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
实现。
不同 AopProxy
实现的实例化过程采用工厂模式(确切地说是抽象工厂模式)进行封装,即通过 org.springframework.aop.framework.AopProxyFactory
创建 AopProxy
实例。
AdvisedSupport
就是一个生成代理对象所需要的信息的载体。
ProxyFactory
集 AopProxy
和 AdvisedSupport
于一身,可以通过 ProxyFactory
设置生成代理对象所需要的相关信息,也可以通过 ProxyFactory
取得最终生成的代理对象。前者是 AdvisedSupport
的职责,后者是 AopProxy
的职责。
ProxyFactory
只是 Spring AOP 中最基本的织入器实现,还有其他的几个 "兄弟":
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 是否符合拦截条件,首先需要知道拦截条件是什么?我们通过某种方式,告知自动代理实现类都有哪些拦截条件:
-
通过外部配置文件传入;
-
在具体类的元数据来指明。
BeanNameAutoProxyCreator
通过指定一组容器内的目标对象对应的 beanNames
,将指定的一组拦截器应用到这些目标对象之上。
将 DefaultAdvisorAutoProxyCreator
注册到容器后,它会自动搜寻容器内的所有 Advisor
,然后根据各个 Advisor
所提供的拦截信息,为符合条件的容器中的目标对象生成相应的代理对象。为了避免将不必要的横切逻辑织入到不需要的目标对象之上,应该尽量细化各个 Advisor
的定义。
InstantiationAwareBeanPostProcessor
有 "短路" 功能。
4.8. TargetSource
TargetSource
的作用就好像是为目标对象在外面加了一个壳,或者说,它就像是目标对象的容器。当每个针对目标对象的方法调用经历层层拦截而到达调用链的终点的时候,就该调用目标对象上定义的方法了。但这时,Spring AOP 做了点儿手脚,它不是直接调用这个目标对象上的方法,而是通过“插足于”调用链与实际目标对象之间的某个 TargetSource
来取得具体目标对象,然后再调用从 TargetSource
中取得的目标对象上的相应方法。
TargetSource
最主要的特性是,每次方法调用都会触发 TargetSource
的 getTarget()
方法, 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 目前只支持 perthis
和 pertarget
两种模式,其他模式不支持。
CommonsPool2TargetSource
使用 Apache Commons Pool 2 来提供对象池的支持。
ThreadLocalTargetSource
为不同线程调用提供不同的目标对象。保证各自线程上对目标对象的调用,可以被分配到当前线程对应的那个目标对象实例上。注意: scope
要设置成 prototype
。
4.9. @AspectJ AOP
AspectJ AOP 的解析和处理是通过 org.springframework.aop.config.AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary
方法将所需要的 BeanPostProcessor
, AnnotationAwareAspectJAutoProxyCreator
注册到 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. this
与 target
在 AspectJ 中,this
指代调用方法一方所在的对象(caller);target
指代被调用方法所在的对象 (callee)。这样通常可以同时使用这两个标志符限定方法的调用关系。比如,如果 Object1
、 Object2
都会调用 Object3
的某个方法。那么,Pointcut 表达式定义 this(Object2) && target(Object3)
只会当 Object2
调用 Object3
上的方法的时候才会匹配,而 Object1
调用 object3
上的方法则不会被匹配。
Spring AOP 中的 this
和 target
标志符语义,有别于 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 定义起得作用差不多。如果通过 this
和 target
指定具体类型,会怎么样呢?如下所示:
1
2
3
4
5
// `this` 指代的是目标对象的代理对象。当目标对象的代理对象是 `TargetFoo` 类型时,则匹配。
this(TargetFoo)
// `target` 指代的就是目标对象。当目标对象是 `TargetFoo` 类型时,则匹配。
target(TargetFoo)
这时,对于基于接口的代理和基于类的代理来说,效果就不同了。对于前者来说, target(TargetFoo)
可以匹配目标对象中的所有 Joinpoint,因为目标对象确实是 TargetFoo
类型,而 this(TargetFoo)
则不可以。此时,这两个标志符出现分歧了。不过,对于后者,即基于类的代理来说,这两个 Pointcut 表达式限定的语文还是基本相同的。
通常,this
和 target
标志符都是在 Pointcut 表达式中与其他标志符结合使用,以进一步加强匹配的限定规则,比如:
1
2
3
4
5
execution(void com.diguage.*.doSomething(*)) && this(TargetFoo)
或者
execution(void com.diguage.*.doSomething(*)) && target(TargetFoo)
当然,我们也可以将 this
和 target
结合到一个 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)
来说,
-
如果 (proxyTargetClass = true),则切面会执行;
-
如果 (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 将被匹配,否则将不会被匹配。
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.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);
}
}
}
ComposablePointcut
是 Spring AOP 提供可以进行 Pointcut
逻辑运算的 Pointcut
实现。
ControlFlowPointcut
是在某个类调动时拦截,其他类调用时不调用。每次运行都需要做检查,性能差,慎重选择。
感觉 Advisor
和 Advice
表示的意思是一样的。不一样的是 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
,从 populateBean
→ applyPropertyValues
→ resolveValueIfNecessary
。
跟踪代码发现, RuntimeBeanReference
类型的依赖关系最后会像 dependsOn
属性那样,注册到 dependentBeanMap
和 dependenciesForBeanMap
属性中。
com.alibaba.spring.beans.factory.annotation.EnableConfigurationBeanBindings
这是什么鬼?干啥的? — 将基于proprieties 文件的配置项和对应的 Bean 建立起关联关系。
这是什么?
AOP 如何实现几种不同的通知的?怎么一步一步调用下去?怎么保存现场?
Spring 中不同 Aspect 直接是如何排序的?如何实现在前置通知和后置通知在正确位置执行?
4.10. AOP 应用案例
4.10.1. 异常处理
在 “Effective Java Exceptions( Part 1, Part 2, Part 3)” 中,作者将 unchecked exception 对应的情况称之为 Fault,而将 checked exception 对应的情况称之为 Contingency。而 Fault Barrier 要处理的就是对应的 Fault 的情况,即 unchecked exception。
可以实现一个对应 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 直接将横切逻辑织入目标对象,将代理对象和目标对象合二为一,调用就不会出现这个问题了。
5. 数据访问
5.1. JdbcTemplate
SQLExceptionTranslator
是一个接口,如果你需要在 SQLException
和 org.springframework.dao.DataAccessException
之间作转换,那么必须实现该接口。
转换器类的实现可以采用一般通用的做法(比如使用 JDBC 的 SQLState code),如果为了使转换更准确,也可以进行定制(比如使用 Oracle 的 error code)。
SQLErrorCodeSQLExceptionTranslator
是 SQLExceptionTranslator
的默认实现。该实现使用指定数据库厂商的 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
接口,该接口的两个实现类 SQLErrorCodeSQLExceptionTranslator
和 SQLStateSQLExceptionTranslator
分别负责处理 SQLException
中错误代码和 SQL 状态码的转换工作 。
SQLErrorCodeSQLExceptionTranslator.doTranslate
是真正实现从错误码到异常的转换工作。在 sql-error-codes.xml
文件中定义异常类型,实现可扩展性。
在两个地方完成异常转换工作:
-
在执行 SQL 时报错,这个时候就要进行回滚。所以,在回滚时,执行异常转换。
TODO dgg 如果"关闭事务"(事务是否可以关闭?)或只读事务时,有事务吗?会执行回滚吗?
-
在提交时报错,进行异常转换。
5.2. Spring 整合 ORM 框架
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,需要的话,自行下载。
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);
}
}
}
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);
}
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);
这行代码打一个断点,然后一步一步跟下去,就到了 ConfigurationClassParser
的 doProcessConfigurationClass
方法里,重点关注 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));
}
到这里就调用到了 MapperScannerRegistrar
的 registerBeanDefinitions
方法:
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());
}
其实只干了一件事情,就是在想容器中注册了一个类为 MapperScannerConfigurer
的 BeanDefinition
,在创建过程中,还把 @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);
这行代码。为什么把扫描出来的 Mapper
的 Bean 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 的如下扩展点:
-
@Import
-
MapperScannerRegistrar
-ImportBeanDefinitionRegistrar
-
MapperScannerConfigurer
-BeanDefinitionRegistryPostProcessor
-
ClassPathMapperScanner
-ClassPathBeanDefinitionScanner
-
MapperFactoryBean
-FactoryBean
-
InitializingBean
可见,和 Spring 整合并不是只靠一个扩展点就可以完成的,需要多个扩展点多方配合才能更好地完成整合过程。
5.2.3.8. 为什么在 Spring+MyBATIS 时,一级缓存失效?
在原生 MyBATIS 实现中,在执行查询时,使用的 SqlSession
是 DefaultSqlSession
, DefaultSqlSession
实例是在执行 SqlSession session = sqlSessionFactory.openSession();
时创建的。执行查询操作也是在 DefaultSqlSession.selectList(String, Object, RowBounds, ResultHandler)
中完成的。
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 的示例请看 Employees、 EmployeesMapper、 SpringMybatisTest。
在 Spring + MyBATIS 搭配中,在执行查询时,使用的 SqlSession
是 SqlSessionTemplate
(由“mybatis-spring”实现)。而 SqlSessionTemplate
的查询执行是委托给 SqlSessionTemplate.sqlSessionProxy
(SqlSession
类型)来操作。 SqlSessionTemplate.sqlSessionProxy
是通过动态代理创建出来的代理实例。在代理实现内部执行时,从创建 SqlSessionTemplate
实例时经构造函数传入的 SqlSessionFactory
对象中获取 SqlSession
对象(创建过程与原生 MyBATIS 的构造过程相同)。最后,再去执行查询操作。
在创建 SqlSessionTemplate.sqlSessionProxy
代理时,代理切面在执行完查询后,执行了 closeSqlSession
操作。正是因为执行了次操作,导致了一级缓存失效。
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.3. 常见错误
5.3.3.1. Phantom Read(幻读)
B 事务读取了两次数据,在这两次的读取过程中A事务添加了数据,B 事务的这两次读取出来的集合不一样。幻读产生的流程如下:
这个流程看起来和不可重复读差不多,但幻读强调的集合的增减,而不是单独一条数据的修改。
5.3.3.2. NonRepeatable Read(不可重复读)
B 事务读取了两次数据,在这两次的读取过程中 A 事务修改了数据,B 事务的这两次读取出来的数据不一样。B 事务这种读取的结果,即为不可重复读(Nonrepeatable Read)。相反,“可重复读”在同一个事务中多次读取数据时,能够保证所读数据一样,也就是后续读取不能读到另一个事务已提交的更新数据。不可重复读的产生的流程如下:
5.3.3.3. Dirty Read(脏读)
A 事务执行过程中,B 事务读取了A事务的修改。但是由于某些原因,A 事务可能没有完成提交,发生 RollBack 了操作,则B事务所读取的数据就会是不正确的。这个未提交数据就是脏读(Dirty Read)。
5.3.3.4. Lost Update(第一类丢失更新)
在完全未隔离事务的情况下,两个事务更新同一条数据资源,某一事务完成,另一事务异常终止,回滚造成第一个完成的更新也同时丢失 。这个问题现代关系型数据库已经不会发生。
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 |
如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按 |
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.
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
中的异常处理机制就可以处理异常了。
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});
}
}
}
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 容器有什么区别?
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 Dubbo 的 dubbo-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 插件机制,需要做这么几个事情:
-
定义自己业务的类。
-
编写 XSD 文件,定义自己的 XML 格式,将文件放在
src/main/resources/META-INF
目录下。 -
针对每一个标签,定义一个实现
BeanDefinitionParser
接口的类,在parse
方法中完成对这个标签的解析工作,将其转化成一个BeanDefinition
对象。 -
继承
NamespaceHandlerSupport
类,在init()
方法中,使用registerBeanDefinitionParser()
将标签名称和上面写的BeanDefinitionParser
实现类之间建起起对应关系。 -
创建
src/main/resources/META-INF/spring.schemas
文件,在其中写上:http\://www.diguage.com/schema/diguage/diguage.xsd=META-INF/diguage.xsd
,为该 XSD 文件定义唯一的命名空间。 -
创建
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 的整合过程。
-
相关业务类有
ApplicationConfig
、ModuleConfig
、RegistryConfig
、ConfigCenterBean
、MetadataReportConfig
、MonitorConfig
、MetricsConfig
、SslConfig
、ProviderConfig
、ConsumerConfig
、ProtocolConfig
、ServiceBean
和ReferenceBean
。这些类的命名也都非常讲究,见文知意,与 Dubbo 常见配置可以说是一一对应。 -
Dubbo 的 XSD 定义在 dubbo.xsd,懂 XSD 的朋友应该都能看出来,这个文件就是规范上一步提到的类的属性的。
-
DubboBeanDefinitionParser
实现了BeanDefinitionParser
接口,用于解析 XML 配置,并将其“翻译”为第一步中那些类的对象。另外,还注册了一个AnnotationBeanDefinitionParser
,用来处理annotation
标签,进而用来处理注解。 -
DubboNamespaceHandler
继承了NamespaceHandlerSupport
,并且在init()
方法中完成了对上述类的DubboBeanDefinitionParser
注册。 -
在
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);
}
这里需要重点关注的是 ReferenceAnnotationBeanPostProcessor
和 DubboBootstrapApplicationListener
,前者设计到 Dubbo 注解的处理,后者着牵涉整个 Dubbo 的启动。先在 DubboBootstrapApplicationListener
的 onApplicationContextEvent
方法上打上断点。后续涉及到时,再具体分析。
然后,我们单步调试,跟进 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 完成了以下功能:
-
initFrameworkExts()
— 初始化框架 -
startConfigCenter()
— 启动配置中心 -
loadRemoteConfigs()
— 加载远程配置 -
checkGlobalConfigs()
— 检查全局配置 -
startMetadataCenter()
— 开始元数据中心,这里特别标明是从 2.7.8 开始的。 -
initMetadataService()
— 初始化元数据服务 -
initMetadataServiceExports()
— 初始化元数据服务导出 -
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
上找原因。经过研究发现, ServiceConfigBase
是 org.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
变量添加元素的。在这个类打断点,你就看到所有添加的变量信息。再次启动服务提供者程序,你会发现上面提到的相关业务类 ApplicationConfig
、 ModuleConfig
、 RegistryConfig
、 ConfigCenterBean
、 MetadataReportConfig
、 MonitorConfig
、 MetricsConfig
、 SslConfig
、 ProviderConfig
、 ConsumerConfig
、 ProtocolConfig
、 ServiceBean
和 ReferenceBean
都是 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;
}
这个注解非常重要。一共有两点需要注意。这个方法就是注解的三个属性,分别给出了三个最重要的参数:
-
scanBasePackages
— 定义了基础扫描的包。通过@AliasFor
注解表明,这是定义@DubboComponentScan
注解的basePackages
属性。 -
scanBasePackageClasses
— 定义扫描的基础类。通过@AliasFor
注解表明,这是定义@DubboComponentScan
注解的basePackageClasses
属性。 -
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;
}
这里,我们看到了熟悉的 @Import
。 DubboConfigConfigurationRegistrar
从名字就能看出应该是实现了 ImportBeanDefinitionRegistrar
接口的,打开代码,果然如此。更
在 Spring 扩展点概览及实践 和 Spring 扩展点实践:整合 MyBATIS 中有针对 @Import
和 ImportBeanDefinitionRegistrar
的详细介绍。尤其是 MyBATIS 就是使用 ImportBeanDefinitionRegistrar
来做扩展的。不懂的,请移步。
关于 DubboConfigConfigurationRegistrar
的功能,这里做个简要总结:
-
使用
@EnableConfigurationBeanBindings
注解,将配置项和对一个的 Bean 类型做一个绑定。如果multiple
属性为true
,则指出多次注册。 -
调用
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
的功能:注册了一个类为 ServiceAnnotationBeanPostProcessor
的 BeanDefinition
,将配置项的配置信息传递给这个 BeanDefinition
实例。 ServiceAnnotationBeanPostProcessor
实现了 BeanDefinitionRegistryPostProcessor
接口,会在 Spring 的启动过程中,通过调用 postProcessBeanDefinitionRegistry
方法来注册相关的 BeanDefinition
。关于这部分内容,请移步: Spring AOP 处理流程概述。
在 Spring 启动过程中,就会调用 ServiceAnnotationBeanPostProcessor
的 postProcessBeanDefinitionRegistry
方法,在这个方法中,通过创建 DubboClassPathBeanDefinitionScanner
(继承了 ClassPathBeanDefinitionScanner
类)实例,调用 scanner.scan(packageToScan)
来注册 BeanDefinition
。另外,有一点需要指出的是: ServiceAnnotationBeanPostProcessor
目前是 @Deprecated
,后续推荐使用 ServiceClassPostProcessor
,而 ServiceAnnotationBeanPostProcessor
就是 ServiceClassPostProcessor
的子类。所以,目前处理逻辑都集中在了 ServiceClassPostProcessor
中。
关于 Apache Dubbo 与 Spring 的整合原理就全部介绍完毕了。如有什么问题,欢迎留言讨论。以后有时间,写写分布式事务解决方案 Seata 的一些原理。
Appendix A: Spring 奇技淫巧
这里记录一些不太常见的使用技巧。
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>
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.5. 定时调度
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>
#==============================================================
#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 接口。由于不能依赖不能注入进来,只能通过这种方式来获取。
|
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.3. 类之间的关系
UML类图几种关系的总结,泛化 = 实现 > 组合 > 聚合 > 关联 > 依赖 在UML类图中,常见的有以下几种关系: 泛化(Generalization), 实现(Realization),关联(Association),聚合(Aggregation),组合(Composition),依赖(Dependency)
B.1.3.1. 泛化(Generalization)
- 泛化关系
-
是一种继承关系,表示一般与特殊的关系,它指定了子类如何特化父类的所有特征和行为。例如:老虎是动物的一种,即有老虎的特性也有动物的共性。
- 代码体现
-
extends
关键字 - 箭头指向
-
带三角箭头的实线,箭头指向父类
B.1.3.2. 实现(Realization)
- 实现关系
-
在这里插入图片描述是一种类与接口的关系,表示类是接口所有特征和行为的实现.
- 代码体现
-
implements
关键字 - 箭头指向
-
带三角箭头的虚线,箭头指向接口
B.1.3.3. 关联(Association)
- 关联关系
-
是一种拥有的关系,它使一个类知道另一个类的属性和方法;如:老师与学生,丈夫与妻子关联可以是双向的,也可以是单向的。双向的关联可以有两个箭头或者没有箭头,单向的关联有一个箭头。
- 代码体现
-
成员变量
- 箭头指向
-
带普通箭头的实心线,指向被拥有者
老师与学生是双向关联,老师有多名学生,学生也可能有多名老师。但学生与某课程间的关系为单向关联,一名学生可能要上多门课程,课程是个抽象的东西他不拥有学生。
Appendix C: 常用工具
-
ASCII diagrams—ASCIIFlow — 画 Ascii diagrams 图。画完 后,通过
asciidoctor-diagram
的ditaaa
转换成图片。