Jmock 原理简单说明
2017-11-04 17:47
274 查看
在jmockit中,你可以使用
对于接口类型,需要这样调用:
这个倒没有什么古怪的。估计又是使用了java.reflect.Proxy。这个技巧在很多Java框架中用到,比如Spring
AOP对于接口类型的实现,就是通过Proxy来混入拦截器实现的。
但是,对于其他类型的调用,就比较奇怪了:
new出来的对象,如果没有赋值给新的变量,应该是随着GC回收。可就是在我的眼皮底下,mockInstance就这样被掉包了。
Spring AOP中,对于非接口类型,是通过CGLIB魔改字节码来实现拦截器注入的。所以我估计这个也是一样的道理。不过令人想不通的是,jmockit到底是什么时候进行移花接木的?没看到注入的地方啊……
只能通过看代码来揭秘了。先从MockUp的构造函数开始吧。
对于非接口类型,调用了
看到
众所周知,Java代码先是编译成class文件,然后由JVM加载运行的。围绕JVM这一中间层,各种有趣的技术应运而生。比如各种类加载器,可以动态地去加载同名的类的不同实现(不同的class文件)。还有各种魔改class文件的手段,在原来的实现中注入自己的代码,像ASM、javassist、GCLIB,等等。jmockit就是应用ASM来修改原来的class文件,用mocked的实现掉包原来的代码。因为MockUp的构造已经触发了“狸猫换太子”的幕后行为,所以这里就不用把new出来的东西赋值给具体变量了。
还有一个问题。我们虽然弄明白了jmockit的作案手法,可是还没有找到掉包现场呢!即使现在jmockit已经持有了被篡改后的字节码,可它又是怎么替换呢?
继续看下去,发现jmockit把修改后的字节码存在
关于动态Instrumentation,具体可以看下这篇文章:http://www.ibm.com/developerworks/cn/java/j-lo-jse61/
简单来说,通过这一机制可以实现监听JVM加载类的事件,并在此之前运行自己的挂钩方法。这么一来,掉包现场也找到了。
那jmockit怎么知道要监听哪些类呢?前面可以看到,需要Mock的类上,要添加Mocked注解。所以jmockit编写了一些跟主流测试框架集成的代码,在测试运行的时候获取带该注解的类。这样就知道要监听的目标了。
总结一下:jmockit先通过Mocked注解标记需要Mock掉的类。然后调用
MockUp来创建一个“fake”的实例,对某个方法指定自己的实现,而不是调用实际的方法。
对于接口类型,需要这样调用:
@Mocked private SomeInterface mockInstance; mockInstance = new MockUp<SomeInteface>() { ... }.getMockInstance();
这个倒没有什么古怪的。估计又是使用了java.reflect.Proxy。这个技巧在很多Java框架中用到,比如Spring
AOP对于接口类型的实现,就是通过Proxy来混入拦截器实现的。
但是,对于其他类型的调用,就比较奇怪了:
@Mocked private SomeProxy mockInstance; new MockUp<SomeProxy>() { @Mock public int doSth() { return 1; } }; mockInstance.doSth(); // return 1
new出来的对象,如果没有赋值给新的变量,应该是随着GC回收。可就是在我的眼皮底下,mockInstance就这样被掉包了。
Spring AOP中,对于非接口类型,是通过CGLIB魔改字节码来实现拦截器注入的。所以我估计这个也是一样的道理。不过令人想不通的是,jmockit到底是什么时候进行移花接木的?没看到注入的地方啊……
只能通过看代码来揭秘了。先从MockUp的构造函数开始吧。
// MockUp.java @Nonnull private Class<T> redefineClassOrImplementInterface(@Nonnull Class<T> classToMock) { if (classToMock.isInterface()) { return createInstanceOfMockedImplementationClass(classToMock, mockedType); } Class<T> realClass = classToMock; if (isAbstract(classToMock.getModifiers())) { classToMock = new ConcreteSubclass<T>(classToMock).generateClass(); } classesToRestore = redefineMethods(realClass, classToMock, mockedType); return classToMock; }
对于非接口类型,调用了
redefineMethods来定义一个仿版。顺着
redefineMethods找下去,终于发现了jmockit的“作案手法”。
// MockClassSetup.java @Nullable private byte[] modifyRealClass(@Nonnull Class<?> classToModify) { if (rcReader == null) { rcReader = createClassReaderForRealClass(classToModify); } MockupsModifier modifier = new MockupsModifier(rcReader, classToModify, mockUp, mockMethods); rcReader.accept(modifier, SKIP_FRAMES); return modifier.wasModified() ? modifier.toByteArray() : null; }
看到
byte[]的函数返回类型,估计就是在这里实现了字节码的转换,然后返回了新的被掉包的class文件了。沿着
MockupsModifier看下去,可以看到jmockit是用ASM来改动原来的实现(具体见
external.asm这个包,我就没有细看了)。
众所周知,Java代码先是编译成class文件,然后由JVM加载运行的。围绕JVM这一中间层,各种有趣的技术应运而生。比如各种类加载器,可以动态地去加载同名的类的不同实现(不同的class文件)。还有各种魔改class文件的手段,在原来的实现中注入自己的代码,像ASM、javassist、GCLIB,等等。jmockit就是应用ASM来修改原来的class文件,用mocked的实现掉包原来的代码。因为MockUp的构造已经触发了“狸猫换太子”的幕后行为,所以这里就不用把new出来的东西赋值给具体变量了。
还有一个问题。我们虽然弄明白了jmockit的作案手法,可是还没有找到掉包现场呢!即使现在jmockit已经持有了被篡改后的字节码,可它又是怎么替换呢?
继续看下去,发现jmockit把修改后的字节码存在
StartUp.java里面了。转过去会看到,jmockit这里用到了JDK6的一个新特性:动态Instrumentation。怪不得jmockit要求JDK版本知识在6以上。
关于动态Instrumentation,具体可以看下这篇文章:http://www.ibm.com/developerworks/cn/java/j-lo-jse61/
简单来说,通过这一机制可以实现监听JVM加载类的事件,并在此之前运行自己的挂钩方法。这么一来,掉包现场也找到了。
那jmockit怎么知道要监听哪些类呢?前面可以看到,需要Mock的类上,要添加Mocked注解。所以jmockit编写了一些跟主流测试框架集成的代码,在测试运行的时候获取带该注解的类。这样就知道要监听的目标了。
总结一下:jmockit先通过Mocked注解标记需要Mock掉的类。然后调用
new MockUp去创建修改后的class文件。在JVM运行的时候,通过JDK6之后的动态Instrumentation特性监听类加载事件,并在目标类加载之前移花接木,用魔改后的字节码换掉真货。虽然Java是门静态类型语言,不过幸亏有字节码和JVM作为中间层,使得mock实现起来相对容易。
相关文章推荐
- P2P原理及UDP穿透简单说明
- 再谈平移图像 简单却足以说明原理
- jQuery插件实现的方法和原理简单说明
- P2P原理及UDP穿透简单说明
- P2P原理及UDP穿透简单说明
- AjaxPro的原理简单说明
- 请描述Java中异常处理机制的简单原理和应用,并说明Error与Exception有什么区别?
- Java中异常处理机制的简单原理和应用,并说明Error与Exception有什么区别?
- JSF与primerfaces---JSF简单原理说明
- P2P原理及UDP穿透简单说明
- P2P原理及UDP穿透简单说明
- 卡尔曼滤波的原理简单说明
- 如何做一个处理命令行的应用程序,请大家帮我做一个简单的,说明一下,我想弄清其中 的原理!
- [算法] 阿尔法-贝塔剪枝原理简单说明
- 请描述Java中异常处理机制的简单原理和应用,并说明Error与Exception有什么区别?
- P2P原理及UDP穿透简单说明
- 区块链Btc98说明比特币的原理及运作机制 简单易懂
- P2P原理及UDP穿透简单说明
- P2P原理及UDP穿透简单说明
- u-boot relocate_code原理简单说明