0

Mockito 源码分析(4)——Annotation

 2 years ago
source link: https://jitwxs.cn/post/Mockito-%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%EF%BC%884%EF%BC%89%E2%80%94%E2%80%94Annotation
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Mockito 源码分析(4)——Annotation

在本篇文章中,将主要对 Mockito 中 @Mock、@Spy、@InjectMocks 这三个主要注解进行源码分析。

我们知道,如果想要使用 Mockito,要么需要在测试类的 @Before,执行 org.mockito.MockitoAnnotations#initMocks,要么在测试类上添加 @RunWith(MockitoJUnitRunner.class) 注解。下面就让我们从 MockitoAnnotations#initMocks 方法看起吧。

MockitoAnnotations#initMocks

initMocks() 方法一共就两行代码:加载 AnnotationEngine,调用 process() 方法,传入的 testClass 为需要启用 Mockito 的测试类:

org.mockito.MockitoAnnotations#initMocks

先来看下 AnnotationEngine 的获取逻辑:

  • 当 GlobalConfiguration 实例化时,调用 createConfig() 方法,得到 IMockitoConfiguration 实现。只要没有进行额外配置,这里的实现是 DefaultMockitoConfiguration

  • 调用 GlobalConfiguration#tryGetPluginAnnotationEngine 得到 AnnotationEngine。对于默认实现(DefaultMockitoConfiguration )来说,直接从 Plugins 读取实现即可:

    org.mockito.internal.configuration.InjectingAnnotationEngine

    Plugins 的源码追踪过程就不赘述了,前文在分析 MockerMaker 时已经带大家走过一次了。

org.mockito.internal.configuration.GlobalConfiguration

接下来让我们看 process 方法,功能也比较清晰:

  • processIndependentAnnotations() 初始化 @Mock、@Spy 等注解标识的对象
  • processInjectMocks() 尝试将 Mock 对象注入到 @InjectMocks 中

org.mockito.internal.configuration.InjectingAnnotationEngine

InjectingAnnotationEngine#processIndependentAnnotations

这个方法的功能是:

  • 通过 IndependentAnnotationEngine#process 方法,初始化 @Mock 等注解标识的对象
  • 通过 SpyAnnotationEngine#process 方法,初始化 @Spy 等注解标识的对象
  • 向上查找当前类的父类,重复执行,直到当前类为 Object 为止

org.mockito.internal.configuration.InjectingAnnotationEngine#processIndependentAnnotations

IndependentAnnotationEngine#process

  • 获取当前 class 标注了注解的字段
  • 调用 createMockFor() 方法
  • 校验注解是否是有效的注解,比如 @Mock
  • 如果是有效注解,Mockito 生成一个对象(即 proxy 代理类实例)
  • alreadyAssigned 字段确保一个字段仅能标注一个有效注解
  • 调用 setField() 方法,将生成的对象赋上去

org.mockito.internal.configuration.IndependentAnnotationEngine#process

① createMockFor() 方法实现如下:

  • IndependentAnnotationEngine 仅支持 @Mock 和 @Captor 注解,每个注解有自己对应的处理器
  • 如果是其他注解,返回一个用于容错的 FieldAnnotationProcessor 注解

org.mockito.internal.configuration.IndependentAnnotationEngine#createMockFor

下面以 @Mock 注解为例,它的处理器是 MockAnnotationProcessor,让我们看下它的 process() 实现:

  • 通过 @Mock 的 Annotation 属性,初始化 MockSettings
  • 然后调用 Mockito#mock(),至此完成对该字段的 mock

org.mockito.internal.configuration.MockAnnotationProcessor#processAnnotationForMock

② setField() 方法实现如下,比较简单就不介绍了。

org.mockito.internal.util.reflection.FieldSetter#setField

SpyAnnotationEngine#process

  • 获取当前 class 标注了 @Spy 但没有同时标注 @InjectMocks 注解的字段

  • 如果这些字段,同时标注了 @Mock 和 @Captor,则抛出异常

    由此可见,@Spy 和 @Mock、@Captor 是不能共存的

  • 尝试直接获取字段的实例

    • 如果实例不为 null,且已经是 Mcok 对象了:这里调用了 Mockito.reset,注释解释了可能连续两次调用 initMocks 导致的
    • 如果实例不为 null,且还不是 Mcok 对象:调用 spyInstance() 方法,返回一个 Mock 对象,并赋到字段上
    • 如果实例为 null:调用 spyNewInstance(),框架帮忙初始化,返回一个 Mock 对象,并赋到字段上

org.mockito.internal.configuration.SpyAnnotationEngine#process

对于 spyInstance() 方法,跟 Mockito#spy() 方法比较,几乎一模一样,就不再介绍了。

org.mockito.internal.configuration.SpyAnnotationEngine#spyInstance

下面再看 spyNewInstance() 方法,主体的思想,也是想办法构造出一个实例出来,然后走 Mockito Spy 那一套:

org.mockito.internal.configuration.SpyAnnotationEngine#spyNewInstance

看 spyNewInstance() 这一段代码时候,我其实有一些疑惑的:

(1)Mockito#spy() 也有一个根据 class 构建的 API 方法,这边为什么不能直接用那个呢,而要再写这么一大堆?

org.mockito.Mockito#spy(java.lang.Class)

(2)前文在分析 Mock 那块代码时,看到对 proxy class 的实例化,使用的是 objenesis 框架,为什么这边不也用这个框架来做呢?

org.mockito.internal.creation.instance.ObjenesisInstantiator

InjectingAnnotationEngine#processInjectMocks

这个方法的功能是:

  • 执行 injectMocs() 方法,完成当前 class 的 @InjectMocks 注入
  • 向上查找当前类的父类,重复执行,直到当前类为 Object 为止

org.mockito.internal.configuration.InjectingAnnotationEngine#processInjectMocks

重点看 injectMock() 方法:

① 这行代码的逻辑,找到当前类下所有标注了 @InjectMocks 注解的字段,并保存到 mockDependentFields 集合中。

org.mockito.internal.configuration.injection.scanner.InjectMocksScanner

② 这行代码逻辑,把当前类字段中,被 Mockito 管理的属性,都保存到 mocks 集合中。

org.mockito.internal.configuration.injection.scanner.MockScanner

③ 上面两行代码完成了数据准备,得到了:当前类及其父类中,所有要执行 InjectMock 的字段,所有已经准备好的 Mock 对象。

调用 DefaultInjectionEngine#injectMocksOnFields 真正开始注入:

org.mockito.internal.configuration.DefaultInjectionEngine

可以看到采用责任链模式:

  • injectionStrategies 字段的责任链:ConstructorInjection -> PropertyAndSetterInjection
  • postInjectionStrategies字段的责任链:SpyOnInjectedFieldsHandler

FieldInitializer

在分析最后一个 apply() 方法前,先插队分析下 org.mockito.internal.util.reflection.FieldInitializer 这个类,会在下面的 apply() 方法中用到。

先看下这个类的成员变量:

  • field:字段,这里其实就是指 @InjectMocks 标注的那个字段
  • fieldOwner:指明了这个 field 属于哪个对象
  • instantiator:当 field 字段默认没有初始化时实例,提供一个初始化策略:
    • ParameterizedConstructorInstantiator:通过有参构造的方式实例化,它就是 ConstructorInjection 的策略
    • NoArgConstructorInstantiator:通过无参构造 + 设置属性的方式实例化,它就是 PropertyAndSetterInjection 的策略

org.mockito.internal.util.reflection.FieldInitializer

再看下 initialize() 方法,它返回一个 FieldInitializationReport 对象,这个对象中最关键的就是 fieldInstance 字段,它其实就是 @InjectMocks 标注的那个字段的实例。

org.mockito.internal.util.reflection.FieldInitializer#initialize

通过调用 acquireFieldInstance() 方法来获取这个实例,而在这个方法中:

  • 先直接获取字段对应的实例(基本为 null)
  • 如果取不到,就得走 instantiator 的初始化策略了(这个到下面分析 OngoingMockInjection#apply 再来看)

org.mockito.internal.util.reflection.FieldInitializer#acquireFieldInstance

OngoingMockInjection#apply

现在来分析 apply() 这个方法,这里的 field 就是单个 injectMocks 字段:

// org.mockito.internal.configuration.injection.MockInjection.OngoingMockInjection#apply
public void apply() {
    for (Field field : fields) {
        injectionStrategies.process(field, fieldOwner, mocks);
        postInjectionStrategies.process(field, fieldOwner, mocks);
    }
}

先看 injectionStrategies 负责的责任链:

(1)org.mockito.internal.configuration.injection.ConstructorInjection#process

org.mockito.internal.configuration.injection.ConstructorInjection#processInjection

如上图所示,它构造的 FieldInitializer 对象中的 instantiator 策略是 ParameterizedConstructorInstantiator,在这个策略中:

  • 先对所有的构造方法排序,选出其中一个构造方法
  • 从所有的 mock 对象中,筛出构造方法需要的参数
  • 尝试通过这个构造方法创建实例
  • 如果创建成功,则设置到 field 上;如果失败,则抛出异常,由上层捕获。

20210921235818019

① 如何选取出一个构造方法呢?规则是:优先取参数数量少的,但至少也得有一个参数。如下图所所示:

org.mockito.internal.util.reflection.FieldInitializer.ParameterizedConstructorInstantiator#biggestConstructor

② 从所有的 mock 对象中,筛出上一步构造方法中需要的对象。如下图所所示:

org.mockito.internal.configuration.injection.ConstructorInjection.SimpleArgumentResolver#resolveTypeInstances

(2)org.mockito.internal.configuration.injection.PropertyAndSetterInjection

如果 ConstructorInjection 无法选出的话,就走到 PropertyAndSetterInjection 了,在这个方法中:

  • 通过 NoArgConstructorInstantiator,调用无参构造,初始化对象,并赋值到字段上
  • 如果获初始化失败,cannotInitializeForInjectMocksAnnotation() 抛出异常
  • 从所有的 mock 对象中,尝试获取到一个匹配的

20210922001235441

① NoArgConstructorInstantiator#instantiate 方法比较简单,单纯的通过无参构造创建实例。

org.mockito.internal.util.reflection.FieldInitializer.NoArgConstructorInstantiator#instantiate

② PropertyAndSetterInjection#injectMockCandidates方法主要功能是:

  • orderedInstanceFieldsFrom() 确定需要被注入的所有字段

  • injectMockCandidatesOnFields() 负责注入

    没太明白为什么要调用两次 injectMockCandidatesOnFields(),有懂的同学吗?

  • mockCandidateFilter() 责任链模式实现了字段匹配的功能

org.mockito.internal.configuration.injection.PropertyAndSetterInjection#injectMockCandidates

这里重点看下如何匹配的那个责任链:

  • TypeBasedCandidateFilter#TypeBasedCandidateFilter:匹配类型

    org.mockito.internal.configuration.injection.filter.TypeBasedCandidateFilter#TypeBasedCandidateFilter

  • NameBasedCandidateFilter#filterCandidate:匹配参数名

    org.mockito.internal.configuration.injection.filter.NameBasedCandidateFilter#filterCandidate

  • TerminalMockCandidateFilter#filterCandidate:先尝试走 set 方法赋值,不行的话走反射赋值。

    org.mockito.internal.configuration.injection.filter.TerminalMockCandidateFilter#filterCandidate

Conclusion

通过上面的内容,为大家讲解了 @Mock、@Spy、@InjectMocks 注解是如何工作的。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK