springboot + junit + mockito真香, 可 springboot 是创建 @MockBean/@SpyBean 代理对象的呢?
@MockBean/@SpyBean 代理对象怎么来的
为了搞懂 mockito 底层是如何通过代理对象走到代理的逻辑的, 我可太难了
测试代码
首先我写了一段测试代码
@MockBean
private UserService userService;
@Test
public void testGetUserById(){
Long id=1L;
User mockUser=new User();
mockUser.setId(id);
mockUser.setName("Mock User");
Mockito.when(userService.findUserById(id)).thenReturn(mockUser);
User result=userService.findUserById(id);
System.out.println(result);
}
通过调试我发现 userService 对象不是我们的对象, 而是一个新的不知名对象
class cn.gd.cz.hong.sqlite.service.UserService$MockitoMock$219418843

发现这个对象有一个 mock 相关的拦截器
class org.mockito.internal.creation.bytebuddy.MockMethodInterceptor
转念一想, jar 包肯定有 new MockMethodInterceptor 的操作
后来又想 既然在spring 容器中运行, 一般不得有个 PostProcessor
问了下ChatGPT
知道是 MockitoPostProcessor
期间问了 ChatGPT4.0 一些问题, 说的云里雾里, 最后还是源码靠谱的说
MockitoContextCustomizerFactory | 帮我们注入 MockitoContextCustomizer

# Spring Test ContextCustomizerFactories
org.springframework.test.context.ContextCustomizerFactory=\
org.springframework.boot.test.context.ImportsContextCustomizerFactory,\
org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizerFactory,\
org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory,\
org.springframework.boot.test.mock.mockito.MockitoContextCustomizerFactory,\
org.springframework.boot.test.web.client.TestRestTemplateContextCustomizerFactory,\
org.springframework.boot.test.web.reactive.server.WebTestClientContextCustomizerFactory
# Test Execution Listeners
org.springframework.test.context.TestExecutionListener=\
org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener,\
org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener
# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.test.web.SpringBootTestRandomPortEnvironmentPostProcessor
org.springframework.boot.test.mock.mockito.MockitoContextCustomizerFactory
MockitoContextCustomizer | 注入 MockitoPostProcessor
又是谁帮我们把 MockitoPostProcessor 注入到容器中的
class MockitoContextCustomizer implements ContextCustomizer {
private final Set<Definition> definitions;
MockitoContextCustomizer(Set<? extends Definition> definitions) {
this.definitions = new LinkedHashSet<>(definitions);
}
@Override
public void customizeContext(ConfigurableApplicationContext context,
MergedContextConfiguration mergedContextConfiguration) {
if (context instanceof BeanDefinitionRegistry) {
MockitoPostProcessor.register((BeanDefinitionRegistry) context, this.definitions);
}
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj == null || obj.getClass() != getClass()) {
return false;
}
MockitoContextCustomizer other = (MockitoContextCustomizer) obj;
return this.definitions.equals(other.definitions);
}
@Override
public int hashCode() {
return this.definitions.hashCode();
}
}
MockitoContextCustomizer 是一个实现了 ContextCustomizer 接口的类,它用于在 Spring Boot
测试环境中定制和修改应用上下文。MockitoContextCustomizer 主要用于注册 MockitoPostProcessor,以支持 @MockBean
和 @SpyBean 注解。
MockitoContextCustomizer 的主要部分如下:
-
构造函数:接收一个
Definition集合,将其存储在类的实例变量definitions中。 -
customizeContext方法:这个方法在 Spring Boot
测试环境初始化时被调用,用于修改或定制应用上下文。在这个方法中,如果context是一个BeanDefinitionRegistry
实例,那么将调用MockitoPostProcessor.register方法,将definitions中的定义注册到 Spring 容器中。 -
equals和hashCode方法:用于比较两个MockitoContextCustomizer实例是否相等。相等的条件是它们的definitions相等。
总之,MockitoContextCustomizer 的主要作用是在 Spring Boot 测试环境中注册 MockitoPostProcessor
,以便支持使用 @MockBean 和 @SpyBean 注解创建和注入模拟对象。
@Override
public void customizeContext(ConfigurableApplicationContext context,
MergedContextConfiguration mergedContextConfiguration){
if(context instanceof BeanDefinitionRegistry){
MockitoPostProcessor.register((BeanDefinitionRegistry)context,this.definitions);
}
}
-> MockitoPostProcessor.register
将咱们的 MockitoPostProcessor 加到 Beandefinition
public static void register(BeanDefinitionRegistry registry,Class<?extends MockitoPostProcessor> postProcessor,
Set<Definition> definitions){
SpyPostProcessor.register(registry);
BeanDefinition definition=getOrAddBeanDefinition(registry,postProcessor);
ValueHolder constructorArg=definition.getConstructorArgumentValues().getIndexedArgumentValue(0,Set.class);
Set<Definition> existing=(Set<Definition>)constructorArg.getValue();
if(definitions!=null){
existing.addAll(definitions);
MockitoPostProcessor | 注册 @MockBean/@SpyBean
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)throws BeansException{
Assert.isInstanceOf(BeanDefinitionRegistry.class,beanFactory,
"@MockBean can only be used on bean factories that implement BeanDefinitionRegistry");
postProcessBeanFactory(beanFactory,(BeanDefinitionRegistry)beanFactory);
}
private void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory,BeanDefinitionRegistry registry){
beanFactory.registerSingleton(MockitoBeans.class.getName(),this.mockitoBeans);
DefinitionsParser parser=new DefinitionsParser(this.definitions);
for(Class<?> configurationClass:getConfigurationClasses(beanFactory)){
parser.parse(configurationClass);
}
Set<Definition> definitions=parser.getDefinitions();
for(Definition definition:definitions){
Field field=parser.getField(definition);
register(beanFactory,registry,definition,field);
}
}
org.springframework.boot.test.mock.mockito.MockitoPostProcessor#register
private void register(ConfigurableListableBeanFactory beanFactory,BeanDefinitionRegistry registry,
Definition definition,Field field){
if(definition instanceof MockDefinition){
registerMock(beanFactory,registry,(MockDefinition)definition,field);
}
else if(definition instanceof SpyDefinition){
registerSpy(beanFactory,registry,(SpyDefinition)definition,field);
}
}
注册 MockBean过程如下:
org.springframework.boot.test.mock.mockito.MockDefinition.createMock() -> org.springframework.boot.test.mock.mockito.MockitoPostProcessor.registerMock() -> org.springframework.boot.test.mock.mockito.MockitoPostProcessor.register() -> org.mockito.Mockito.mock() -> org.mockito.internal.MockitoCore.mock() -> org.mockito.internal.util.MockUtil.mock() -> org.mockito.internal.creation.bytebuddy.ByteBuddyMockMaker.createMock() -> org.mockito.internal.creation.bytebuddy.SubclassByteBuddyMockMaker.createMock()
org.mockito.internal.creation.bytebuddy.SubclassByteBuddyMockMaker#createMock
@Override
public<T> T createMock(MockCreationSettings<T> settings,MockHandler handler){
Class<?extends T> mockedProxyType=createMockType(settings);
Instantiator instantiator=Plugins.getInstantiatorProvider().getInstantiator(settings);
T mockInstance=null;
try{
mockInstance=instantiator.newInstance(mockedProxyType);
MockAccess mockAccess=(MockAccess)mockInstance;
mockAccess.setMockitoInterceptor(new MockMethodInterceptor(handler,settings));
return ensureMockIsAssignableToMockedType(settings,mockInstance);
}catch(ClassCastException cce){
throw new MockitoException(
join(
"ClassCastException occurred while creating the mockito mock :",
" class to mock : "+describeClass(settings.getTypeToMock()),
" created class : "+describeClass(mockedProxyType),
" proxy instance class : "+describeClass(mockInstance),
" instance creation by : "+instantiator.getClass().getSimpleName(),
"",
"You might experience classloading issues, please ask the mockito mailing-list.",
""),
cce);
}catch(org.mockito.creation.instance.InstantiationException e){
throw new MockitoException(
"Unable to create mock instance of type '"
+mockedProxyType.getSuperclass().getSimpleName()
+"'",
e);
}
}
org.mockito.internal.creation.bytebuddy.TypeCachingBytecodeGenerator#mockClass
@Override
public<T> Class<T> mockClass(final MockFeatures<T> params){
lock.readLock().lock();
try{
ClassLoader classLoader=params.mockedType.getClassLoader();return(Class<T>)
typeCache.findOrInsert(
classLoader,
new MockitoMockKey(
params.mockedType,
params.interfaces,
params.serializableMode,
params.stripAnnotations),
new Callable<Class<?>>(){
@Override
public Class<?> call()throws Exception{
return bytecodeGenerator.mockClass(params);
}
},
BOOTSTRAP_LOCK);
}catch(IllegalArgumentException exception){
Throwable cause=exception.getCause();
if(cause instanceof RuntimeException){
throw(RuntimeException)cause;
}else{
throw exception;
}
}finally{
lock.readLock().unlock();
}
}
private final Implementation dispatcher = to(DispatcherDefaultingToRealMethod.class);
上面这段代码是关键,
/*
* Copyright (c) 2016 Mockito contributors
* This program is made available under the terms of the MIT License.
*/
package org.mockito.internal.creation.bytebuddy;
class SubclassBytecodeGenerator implements BytecodeGenerator {
private static final String CODEGEN_PACKAGE = "org.mockito.codegen.";
private final SubclassLoader loader;
private final ModuleHandler handler;
private final ByteBuddy byteBuddy;
private final Random random;
private final Implementation readReplace;
private final ElementMatcher<? super MethodDescription> matcher;
private final Implementation dispatcher = to(DispatcherDefaultingToRealMethod.class);
private final Implementation hashCode = to(MockMethodInterceptor.ForHashCode.class);
private final Implementation equals = to(MockMethodInterceptor.ForEquals.class);
private final Implementation writeReplace = to(MockMethodInterceptor.ForWriteReplace.class);
// ...
}
new Callable<Class<?>>(){
@Override
public Class<?> call()throws Exception{
return bytecodeGenerator.mockClass(params);
}
}
这段代码利用 bytebuddy 帮我们动态创建字节码
来到这个
org.mockito.internal.creation.bytebuddy.SubclassBytecodeGenerator#mockClass
/**
* 使用 ByteBuddy 创建 Mock 类,并使用给定的参数配置 Mock 对象。
*
* @param features 包含要为 Mock 对象设置的特征的 MockFeatures 对象
* @return 创建的 Mock 类的 Class 对象
*/
@Override
public<T> Class<?extends T> mockClass(MockFeatures<T> features){
// 创建类加载器
ClassLoader classLoader=
new MultipleParentClassLoader.Builder()
.appendMostSpecific(getAllTypes(features.mockedType))
.appendMostSpecific(features.interfaces)
.appendMostSpecific(currentThread().getContextClassLoader())
.appendMostSpecific(MockAccess.class)
.build();
// 如果不需要创建新的类加载器,并且 Mock 不是基于 JDK 类型,则尝试在用户运行时包中定义 Mock 类,以允许 Mock 包私有类型和方法。
// 这还需要我们能够通过重写或显式特权来访问 Mock 类的包,目标包被打开以允许 Mockito 访问。
boolean localMock=
classLoader==features.mockedType.getClassLoader()
&&features.serializableMode!=SerializableMode.ACROSS_CLASSLOADERS
&&!isComingFromJDK(features.mockedType)
&&(loader.isDisrespectingOpenness()
||handler.isOpened(features.mockedType,MockAccess.class));
String typeName;
if(localMock
||loader instanceof MultipleParentClassLoader
&&!isComingFromJDK(features.mockedType)){
typeName=features.mockedType.getName();
}else{
typeName=
InjectionBase.class.getPackage().getName()
+"."
+features.mockedType.getSimpleName();
}
String name=
String.format("%s$%s$%d",typeName,"MockitoMock",Math.abs(random.nextInt()));
// 调整模块图表以处理 Mock 类
if(localMock){
handler.adjustModuleGraph(features.mockedType,MockAccess.class,false,true);
for(Class<?> iFace:features.interfaces){
handler.adjustModuleGraph(iFace,features.mockedType,true,false);
handler.adjustModuleGraph(features.mockedType,iFace,false,true);
}
}else{
boolean exported=handler.isExported(features.mockedType);
Iterator<Class<?>>it=features.interfaces.iterator();
while(exported&&it.hasNext()){
exported=handler.isExported(it.next());
}
// 检查是否所有 Mock 的类型都没有限定地导出,以避免生成钩子类型。除非必要,否则我们期望大多数 Mock 类型都符合此条件,这使得这是一个值得进行的性能优化。
if(exported){
assertVisibility(features.mockedType);
for(Class<?> iFace:features.interfaces){
assertVisibility(iFace);
}
}else{
Class<?> hook=handler.injectionBase(classLoader,typeName);
assertVisibility(features.mockedType);
handler.adjustModuleGraph(features.mockedType,hook,true,false);
for(Class<?> iFace:features.interfaces){
assertVisibility(iFace);
handler.adjustModuleGraph(iFace,hook,true,false);
}
}
}
// 使用 ByteBuddy 构建 Mock 对象
DynamicType.Builder<T> builder=
byteBuddy
.subclass(features.mockedType)
.name(name.ignoreAlso(isGroovyMethod())
.annotateType(
features.stripAnnotations
?new Annotation[0]
:features.mockedType.getAnnotations())
.implement(new ArrayList<Type>(features.interfaces))
.method(matcher)
.intercept(dispatcher)
.transform(withModifiers(SynchronizationState.PLAIN))
.attribute(
features.stripAnnotations
?MethodAttributeAppender.NoOp.INSTANCE
:INCLUDING_RECEIVER)
.method(isHashCode())
.intercept(hashCode)
.method(isEquals())
.intercept(equals)
.serialVersionUid(42L)
.defineField("mockitoInterceptor",MockMethodInterceptor.class,PRIVATE)
.implement(MockAccess.class)
.intercept(FieldAccessor.ofBeanProperty());
// 如果 features.serializableMode 为 SerializableMode.ACROSS_CLASSLOADERS,则实现 CrossClassLoaderSerializableMock 接口并拦截 writeReplace 方法
if(features.serializableMode==SerializableMode.ACROSS_CLASSLOADERS){
builder=
builder.implement(CrossClassLoaderSerializableMock.class)
.intercept(writeReplace);
}
// 如果有 readReplace 方法,则定义 readObject 方法并拦截它
if(readReplace!=null){
builder=
builder.defineMethod("readObject",void.class,Visibility.PRIVATE)
.withParameters(ObjectInputStream.class)
.throwing(ClassNotFoundException.class,IOException.class)
.intercept(readReplace);
}
// 忽略包私有类型或方法
if(name.startsWith(CODEGEN_PACKAGE)||classLoader instanceof MultipleParentClassLoader){
builder=
builder.ignoreAlso(
isPackagePrivate()
.or(returns(isPackagePrivate()))
.or(hasParameters(whereAny(hasType(isPackagePrivate())))));
}
// 创建并加载 Mock 类
return builder.make()
.load(
classLoader,
loader.resolveStrategy(features.mockedType,classLoader,localMock))
.getLoaded();
}
}
该方法创建一个类加载器,并使用 ByteBuddy 创建 Mock 类。这段代码使用注释解释了一系列步骤,包括:
- 创建类加载器
- 创建 Mock 类的名称
- 调整模块图表以处理 Mock 类
- 使用 ByteBuddy 构建 Mock 对象
- 如果需要,则实现 CrossClassLoaderSerializableMock 接口并拦截 writeReplace 方法
- 如果存在 readReplace 方法,则定义 readObject 方法并拦截它
- 忽略包私有类型或方法
- 创建并加载 Mock 类
DynamicType.Builder<T> builder=
byteBuddy
.subclass(features.mockedType) // 构建子类,被Mock的类型作为父类
.name(name) // 设置子类名称
.ignoreAlso(isGroovyMethod()) // 忽略Groovy方法
.annotateType(
features.stripAnnotations
?new Annotation[0] // 如果 features.stripAnnotations 为 true,则不添加注解
:features.mockedType.getAnnotations()) // 否则添加 features.mockedType 上的所有注解
.implement(new ArrayList<Type>(features.interfaces)) // 实现指定的接口
.method(matcher) // 匹配 Mock 方法的方法匹配器
.intercept(dispatcher) // 拦截匹配的方法
.transform(withModifiers(SynchronizationState.PLAIN)) // 将方法修改为“普通”同步模式,而不是 Mockito 的“严格”同步模式
.attribute(
features.stripAnnotations
?MethodAttributeAppender.NoOp.INSTANCE // 如果 features.stripAnnotations 为 true,则不添加 MethodAttributeAppender 实例
:INCLUDING_RECEIVER) // 否则添加包含接收器的 MethodAttributeAppender 实例
.method(isHashCode()) // 重写 hashCode 方法
.intercept(hashCode) // 拦截 hashCode 方法
.method(isEquals()) // 重写 equals 方法
.intercept(equals) // 拦截 equals 方法
.serialVersionUid(42L) // 添加 serialVersionUID 属性
.defineField("mockitoInterceptor",MockMethodInterceptor.class,PRIVATE) // 定义一个名为 "mockitoInterceptor" 的私有 MockMethodInterceptor 属性
.implement(MockAccess.class) // 实现 MockAccess 接口
.intercept(FieldAccessor.ofBeanProperty()); // 拦截 MockAccess 接口的属性访问器方法
byteBuddy 利用构造模式帮我创建一个字节码
包含了一个 MockMethodInterceptor mockitoInterceptor 私有属性
mockitoInterceptor 这个名字是重点, 后面要考
上面的这个类 DispatcherDefaultingToRealMethod 是 MockMethodInterceptor 的内部类
MockMethodInterceptor$DispatcherDefaultingToRealMethod
此中又有一段代码
@FieldValue(“mockitoInterceptor”) MockMethodInterceptor interceptor
这个字段与 byte buff产生的字节码对象中的字段 mockitoInterceptor 对上了
public static class DispatcherDefaultingToRealMethod {
@SuppressWarnings("unused")
@RuntimeType
@BindingPriority(BindingPriority.DEFAULT * 2)
public static Object interceptSuperCallable(
@This Object mock,
@FieldValue("mockitoInterceptor") MockMethodInterceptor interceptor,
@Origin Method invokedMethod,
@AllArguments Object[] arguments,
@SuperCall(serializableProxy = true) Callable<?> superCall)
throws Throwable {
if (interceptor == null) {
return superCall.call();
}
return interceptor.doIntercept(
mock, invokedMethod, arguments, new RealMethod.FromCallable(superCall));
}
@SuppressWarnings("unused")
@RuntimeType
public static Object interceptAbstract(
@This Object mock,
@FieldValue("mockitoInterceptor") MockMethodInterceptor interceptor,
@StubValue Object stubValue,
@Origin Method invokedMethod,
@AllArguments Object[] arguments)
throws Throwable {
if (interceptor == null) {
return stubValue;
}
return interceptor.doIntercept(
mock, invokedMethod, arguments, RealMethod.IsIllegal.INSTANCE);
}
}
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)