JMockit原理剖析


声明:本文转载自https://my.oschina.net/qixiaobo025/blog/1541813,转载目的在于传递更多信息,仅供学习交流之用。如有侵权行为,请联系我,我会及时删除。

上篇文章描述了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<Int>)

我们从最基本的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基本通过类似原理来实现 比如 透视宝

本文发表于2017年09月22日 16:37
(c)注:本文转载自https://my.oschina.net/qixiaobo025/blog/1541813,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 2176 讨论 0 喜欢 0

抢先体验

扫码体验
趣味小程序
文字表情生成器

闪念胶囊

你要过得好哇,这样我才能恨你啊,你要是过得不好,我都不知道该恨你还是拥抱你啊。

直抵黄龙府,与诸君痛饮尔。

那时陪伴我的人啊,你们如今在何方。

不出意外的话,我们再也不会见了,祝你前程似锦。

这世界真好,吃野东西也要留出这条命来看看

快捷链接
网站地图
提交友链
Copyright © 2016 - 2021 Cion.
All Rights Reserved.
京ICP备2021004668号-1