上篇文章描述了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基本通过类似原理来实现 比如 透视宝