抱歉,你查看的文章不存在

JMockit原理剖析

上篇文章描述了Jmockit的最基本的也是最方便也是最神奇的mock神技单元测试JMockit使用

本篇大概就其原理好好说道。

背景知识

Instrumentation知识算是java5之后一个激动人心的功能。

对于部分年纪较大的开发者大概会见过在程序的jvm参数配置如下

-javaagent:D:\resin-pro-3.1.12\lib\aspectjweaver-1.7.0.jar

-javaagent:D:\resin-pro-3.1.12\lib\spring-instrument-3.1.2.RELEASE.jar

多年前笔者也曾经接触过在jvm启动参数中配置javaagent 关于aspectjweaver为何需要配置javaagent也是一直心有疑虑

到了javase6之后java又增加了新的动态代理(这也是当前java界比较流行的自动探针apm技术的原理,不需要修改代码完成监控)

从java5的技术称之为premain 而java6的新技术称之为agentmain

java中main函数想必所有的开发人员都相当熟悉,关于premain和agentmain也顾名思义可以理解

分析

首先查看jmockit的manifest文件

Manifest-Version: 1.0
Premain-Class: mockit.internal.startup.Startup
Archiver-Version: Plexus Archiver
Built-By: PC11
Agent-Class: mockit.internal.startup.Startup
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Created-By: Apache Maven 3.2.1
Build-Jdk: 1.8.0_20

从代码中可以分析其支持两种Instrumentation

指定启动class为mockit.internal.startup.Startup

代码

基本上我们都是通过直接创建MockUp的子类来实现相关的Mock操作的。

因此先分析一下

protected MockUp()
{
   validateMockingAllowed();
 
   MockUp<?> previousMockUp = findPreviouslyMockedClassIfMockUpAlreadyApplied();
 
   if (previousMockUp != null) {
      mockedType = previousMockUp.mockedType;
      mockedClass = previousMockUp.mockedClass;
      return;
   }
 
   mockedType = validateTypeToMock();
 
   if (mockedType instanceof Class<?>) {
      @SuppressWarnings("unchecked") Class<T> classToMock = (Class<T>) mockedType;
      mockedClass = redefineClassOrImplementInterface(classToMock);
   }
   else if (mockedType instanceof ParameterizedType) {
      ParameterizedType parameterizedType = (ParameterizedType) mockedType;
      @SuppressWarnings("unchecked") Class<T> classToMock = (Class<T>) parameterizedType.getRawType();
      mockedClass = redefineClassOrImplementInterface(classToMock);
   }
   else {
      Type[] typesToMock = ((TypeVariable<?>) mockedType).getBounds();
 
      if (typesToMock.length > 1) {
         mockedClass = new MockedImplementationClass<T>(this).createImplementation(typesToMock);
      }
      else {
         mockedClass = new CaptureOfMockedUpImplementations(this, typesToMock[0]).apply();
      }
   }
}

可以看到对应Mock类型支持Class ParameterizedType(泛型类比如List)

我们从最基本的Class开始分析

private Class<T> redefineClassOrImplementInterface(@NotNull Class<T> classToMock)
{
   if (classToMock.isInterface()) {
      return createInstanceOfMockedImplementationClass(classToMock, mockedType);
   }
 
   Class<T> realClass = classToMock;
 
   if (isAbstract(classToMock.getModifiers()) && classToMock.getClassLoader() != null) {
      classToMock = generateConcreteSubclass(classToMock);
   }
 
   classesToRestore = redefineMethods(realClass, classToMock, mockedType);
   return classToMock;

很明显其区分了interface和普通类型(对于class还处理了是否是抽象类)

对于List来说实质上是一个interface 我们继续向下看

public Class<T> createImplementation(@NotNull Class<T> interfaceToBeMocked, @Nullable Type typeToMock)
{
   createImplementation(interfaceToBeMocked);
   byte[] generatedBytecode = implementationClass == null ? null : implementationClass.getGeneratedBytecode();
 
   MockClassSetup mockClassSetup = new MockClassSetup(generatedClass, typeToMock, mockUpInstance, generatedBytecode);
   mockClassSetup.redefineMethodsInGeneratedClass();
 
   return generatedClass;
}
 
Class<T> createImplementation(@NotNull Class<T> interfaceToBeMocked)
{
   if (isPublic(interfaceToBeMocked.getModifiers())) {
      generateImplementationForPublicInterface(interfaceToBeMocked);
   }
   else {
      //noinspection unchecked
      generatedClass = (Class<T>) Proxy.getProxyClass(interfaceToBeMocked.getClassLoader(), interfaceToBeMocked);
   }
 
   return generatedClass;
}
private void generateImplementationForPublicInterface(@NotNull Class<T> interfaceToBeMocked)
{
   implementationClass = new ImplementationClass<T>(interfaceToBeMocked) {
      @NotNull @Override
      protected ClassVisitor createMethodBodyGenerator(@NotNull ClassReader typeReader)
      {
         return new InterfaceImplementationGenerator(typeReader, generatedClassName);
      }
   };
 
   generatedClass = implementationClass.generateClass();
}

从上述代码可以看出基本上使用了InterfaceImplementationGenerator作为接口实现类的生成。

package mockit.internal.mockups;
 
import mockit.external.asm.*;
import mockit.internal.classGeneration.*;
import static mockit.external.asm.Opcodes.*;
 
import org.jetbrains.annotations.*;
 
public final class InterfaceImplementationGenerator extends BaseImplementationGenerator
{
   public InterfaceImplementationGenerator(@NotNull ClassReader classReader, @NotNull String implementationClassName)
   {
      super(classReader, implementationClassName);
   }
 
   @Override
   protected void generateMethodBody(
      int access, @NotNull String name, @NotNull String desc, @Nullable String signature, @Nullable String[] exceptions)
   {
      mw = cw.visitMethod(ACC_PUBLIC, name, desc, signature, exceptions);
      generateEmptyImplementation(desc);
   }
}

关注到ClassWriter类的存在(asm)但是并没有引用asm的jar啊

从包的结构来看 应该是jmockito在打包的时候将asm的源码打包进去到了external(此处顺带吐槽一下百世的XingNg,将CXF打包进去)

关于asm各位有兴趣可以到网上自己学习相关(要求一些字节码和操作数相关知识,api较为底层)

查看上述generateEmptyImplementation 方法

@Override
protected void generateMethodBody(
   int access, @NotNull String name, @NotNull String desc, @Nullable String signature, @Nullable String[] exceptions)
{
   mw = cw.visitMethod(ACC_PUBLIC, name, desc, signature, exceptions);
   generateEmptyImplementation(desc);
}
protected final void generateEmptyImplementation(@NotNull String desc)
{
   Type returnType = Type.getReturnType(desc);
   pushDefaultValueForType(returnType);
   mw.visitInsn(returnType.getOpcode(IRETURN));
   mw.visitMaxs(1, 0);
}
private void pushDefaultValueForType(@NotNull Type type)
{
   switch (type.getSort()) {
      case Type.VOID: break;
      case Type.BOOLEAN:
      case Type.CHAR:
      case Type.BYTE:
      case Type.SHORT:
      case Type.INT:    mw.visitInsn(ICONST_0); break;
      case Type.LONG:   mw.visitInsn(LCONST_0); break;
      case Type.FLOAT:  mw.visitInsn(FCONST_0); break;
      case Type.DOUBLE: mw.visitInsn(DCONST_0); break;
      case Type.ARRAY:  generateCreationOfEmptyArray(type); break;
      default:          mw.visitInsn(ACONST_NULL);
   }
}

这边涉及到一些asm的底层api asm基本通过访问者模式来实现大量的通过底层常量池来完成 比如boolean char byte short int均返回了ICONST_0对象直接返回了ACONST_NULL 当然还需要计算栈空间大小visitMax

那么具体实现是如何被替换进了接口实现呢?

@NotNull
public Class<T> createImplementation(@NotNull Class<T> interfaceToBeMocked, @Nullable Type typeToMock)
{
   createImplementation(interfaceToBeMocked);
   byte[] generatedBytecode = implementationClass == null ? null : implementationClass.getGeneratedBytecode();
 
   MockClassSetup mockClassSetup = new MockClassSetup(generatedClass, typeToMock, mockUpInstance, generatedBytecode);
   mockClassSetup.redefineMethodsInGeneratedClass();
 
   return generatedClass;
}

很明显此处mockUpInstance是从上文中通过MockUp的子类的实例

通过MockClassSetup完成了类的重定义

public void redefineMethodsInGeneratedClass()
{
   byte[] modifiedClassFile = modifyRealClass(realClass);
   validateThatAllMockMethodsWereApplied();
 
   if (modifiedClassFile != null) {
      applyClassModifications(realClass, modifiedClassFile);
   }
}

我们来粗略查看一下MockClassSetup的实现

private MockClassSetup(
   @NotNull Class<?> realClass, @NotNull Class<?> classToMock, @Nullable Type mockedType, @NotNull MockUp<?> mockUp,
   @Nullable byte[] realClassCode)
{
   this.realClass = classToMock;
   mockMethods = new MockMethods(realClass, mockedType);
   this.mockUp = mockUp;
   forStartupMock = Startup.initializing;
   rcReader = realClassCode == null ? null : new ClassReader(realClassCode);
 
   Class<?> mockUpClass = mockUp.getClass();
   new MockMethodCollector(mockMethods).collectMockMethods(mockUpClass);
 
   mockMethods.registerMockStates(mockUp, forStartupMock);
 
   if (forStartupMock) {
      TestRun.getMockClasses().addMock(mockMethods.getMockClassInternalName(), mockUp);
   }
   else {
      TestRun.getMockClasses().addMock(mockUp);
   }
}

最重要的细节出现了mockMethods 这个应当是我们目前最重要的关键词。当对应的method呗Mock注解修饰会直接覆盖既有的实现

MockMethods实现如下

MockMethods(@NotNull Class<?> realClass, @Nullable Type mockedType)
{
   this.realClass = realClass;
 
   if (mockedType == null || realClass == mockedType) {
      mockedTypeIsAClass = true;
   }
   else {
      Class<?> mockedClass = Utilities.getClassType(mockedType);
      mockedTypeIsAClass = !mockedClass.isInterface();
   }
 
   reentrantRealClass = mockedTypeIsAClass && MockingBridge.instanceOfClassThatParticipatesInClassLoading(realClass);
   methods = new ArrayList<MockMethod>();
   typeParametersToTypeArguments = new GenericTypeReflection(realClass, mockedType);
   mockClassInternalName = "";
}

将对应的方法放入methods属性中

那么找到对应放入methods的地方

return new MethodVisitor()
{
   @Override
   @Nullable public AnnotationVisitor visitAnnotation(String desc, boolean visible)
   {
      if ("Lmockit/Mock;".equals(desc)) {
         MockMethods.MockMethod mockMethod =
            mockMethods.addMethod(collectingFromSuperClass, access, methodName, methodDesc);
 
         if (mockMethod != null) {
            return new MockAnnotationVisitor(mockMethod);
         }
      }
 
      return null;
   }

必须是mockit.Mock注解修饰的方法会被放入到methods list中。那么在下文中使用modifyRealClass将会替换对应的方法

@Nullable
private byte[] modifyRealClass(@NotNull Class<?> classToModify)
{
   if (rcReader == null) {
      if (!HOTSPOT_VM && classToModify == System.class) {
         return null;
      }
 
      rcReader = createClassReaderForRealClass(classToModify);
   }
 
   MockupsModifier modifier = new MockupsModifier(rcReader, classToModify, mockUp, mockMethods);
   rcReader.accept(modifier, SKIP_FRAMES);
 
   return modifier.wasModified() ? modifier.toByteArray() : null;
}

MockupsModifier可以望文生义,确实在此处重新覆盖了相关实现。这样获得新的接口的实例中就会存有在MockUp中同签名(不包括static)的实现了。

如上是一个简单的关于接口利用asm实现mock过程的分析

同样的道理当使用具体实现类(非接口)此时由于对应的bytecode已经被修改 后面通过任意方式new或者其他方式都可以完成字节码的替换(也就完成了aop的功能)

目前主流的无打桩apm基本通过类似原理来实现 比如 透视宝

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

编辑于

后端之路

0 篇文章0 人订阅

相关文章

扫码关注云+社区

领取腾讯云代金券