赞
踩
为了搞懂 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 一些问题, 说的云里雾里, 最后还是源码靠谱的说
# 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
又是谁帮我们把 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);
@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 类。这段代码使用注释解释了一系列步骤,包括:
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); } }
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。