Jmock 原理简单说明
在jmockit中,你可以使用MockUp來創建一個“fake”的實例,對某個方法指定自己的實現,而不是調用實際的方法。
對于接口類型,需要這樣調用:
private SomeInterface mockInstance;mockInstance = new MockUp<SomeInteface>() {... }.getMockInstance();這個倒沒有什么古怪的。估計又是使用了java.reflect.Proxy。這個技巧在很多Java框架中用到,比如Spring AOP對于接口類型的實現,就是通過Proxy來混入攔截器實現的。
但是,對于其他類型的調用,就比較奇怪了:
private SomeProxy mockInstance;new MockUp<SomeProxy>() {public int doSth() {return 1;} };mockInstance.doSth(); // return 1new出來的對象,如果沒有賦值給新的變量,應該是隨著GC回收。可就是在我的眼皮底下,mockInstance就這樣被掉包了。
Spring AOP中,對于非接口類型,是通過CGLIB魔改字節碼來實現攔截器注入的。所以我估計這個也是一樣的道理。不過令人想不通的是,jmockit到底是什么時候進行移花接木的?沒看到注入的地方啊……
只能通過看代碼來揭秘了。先從MockUp的構造函數開始吧。
// MockUp.java 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 private byte[] modifyRealClass( 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實現起來相對容易。
總結
以上是生活随笔為你收集整理的Jmock 原理简单说明的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: win10系统定时自动切换深色模式
- 下一篇: 用JavaScript实现图片剪切效果