因为觉得powermock的运用cglib来修改字节码挺好玩的,所以这几天自己研究了下,还是那样,基于TDD来编写powermock,写这篇文章的目的是为了厘清自己的思路
首先powermock的核心就是when和thenReturn这俩东西,when的时候,其实就是打了个桩:
public static <T> OngoingStubbing<T> when(T methodCall) {
//return Mockito.when(methodCall);
return new OngoingStubbing<T>(methodCall);
}
这个桩子OngoingStubbing,在thenReturn时也会用到:
public class OngoingStubbing<T> {
//answer
// add ans
// add return
//
private final InvocationContainerImpl0 invocationContainerImpl = new InvocationContainerImpl0();
private T method;
public OngoingStubbing (T method) {
this.method = method;
}
public OngoingStubbing<T> thenReturn(T var1) {
invocationContainerImpl.addAnswer(new Return0(var1));
return this;
}
}
可以看到里面有个invocationContainerImpl,这个变量是用来干什么的呢? 其实就是为了处理thenReturn时的返回变量的,通过addAnswer方法,既然是这样,那么这些answer是在什么时候取出来的呢? 当然是在后面实际调用方法时,会取到这个Answer,所以是怎么取到的?
也是通过invocationContainerImpl的方法findAnswersForStubbing,那么findAnswersForStubbing是在什么时候被调用的呢,就是通过修改字节码来使他在callback时会被调用到,callback的实现,在powermock分为七种,我们在这里用的MethodInteceptor,一个简单的实现是:
package mockito;
import org.mockito.cglib.proxy.MethodInterceptor;
import org.mockito.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class MethodInterceptorImpl implements MethodInterceptor {
public Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable {
return "2";
}
}
自己写的MockCreator用来使用这个字节码增强:
package mockito;
import org.mockito.cglib.proxy.Enhancer;
public class MockCreator {
public static <T> T createMock(Class<T> type) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(type);
// 设置回调方法 - 回调方法实现为 FixedValue (固定值) .
enhancer.setCallback(new MethodInterceptorImpl());
// 创建 SampleClass 的代理子类实例
T proxy = (T) enhancer.create();
return proxy;
}
}
现在的一个比较简略的实现就是,我们在测试方法运行之前,把需要mock的东西mock住:
import mockito.MethodInterceptorImpl;
import mockito.PowerMockito;
import mockito.StaticClass;
import org.junit.Before;
import org.junit.Test;
import org.mockito.cglib.proxy.Enhancer;
import org.mockito.cglib.proxy.FixedValue;
import static mockito.MockCreator.createMock;
import static mockito.PowerMockito.*;
public class MockTest {
StaticClass staticmethod;
//把stati to mock method
// return 时把
//nongq inte zuolesha
@Before
public void setUp() throws Exception{
staticmethod = createMock(StaticClass.class);
}
@Test
public void test() {
mockStatic(StaticClass.class);
StaticClass sc = new StaticClass();
when(sc.nmsl()).thenReturn("2");
System.out.println(staticmethod.nmsl());
}
}
但是这样的一个没有实现的点,就是thenReturn时的value没法传进MethodImpl去,所以现在最难的一步就是,如何把thenReturn("2")的"2"传进callback里面,我的办法就是pullongingstub
public class MethodInterceptorImpl
implements MethodInterceptor {
Object getReturnValue() {
OngoingStubbing<String> stringOngoingStubbing = OngoingStubbing.pullongoingStub();
Return0 return0 = (Return0)stringOngoingStubbing.findAnswersForStubbing();
return return0.value;
}
public Object intercept(Object obj, Method methods, Object[] args, MethodProxy proxy) throws Throwable {
return getReturnValue();
}
}
为了简单,我把pullonging设置成了静态方法,并且对invocationhandler也改成了静态变量:
public class OngoingStubbing<T> {
//answer
// add ans
// add return
//findAnswersForStubbing
public static InvocationContainerImpl0 invocationContainerImpl = new InvocationContainerImpl0();
private T method;
public static OngoingStubbing<String> pullongoingStub() {
OngoingStubbing<String> stringOngoingStubbing = new OngoingStubbing<String>(invocationContainerImpl);
return stringOngoingStubbing;
}
这样,就能找到对应的answer了,这个answer就是thenReturn时放进去的:
public OngoingStubbing<T> thenReturn(T var1) {
invocationContainerImpl.addAnswer(new Return0(var1));
return this;
}
运行可得到结果:
不过上面的缺点就是,只能when then调用一次,只能返回第一个Answer这显然是不太好的,所以要想办法用bindmatcher,来对每个方法,返回对应的Answer,首先就要明白powermock是怎么match的,首先会存储一个matcherStack,里面存储的类似 any()和方法的键值对
通过 argumentMatcherStorage.pullLocalizedMatchers();方法,获取已经有的matchers,然后通过setPotential,来设置可能的mather为当前已有的mathcers
有一行,比较重要,就是
OngoingStubbingImpl<T> ongoingStubbing = new OngoingStubbingImpl(this.invocationContainer);
这个就是和上面我的静态invocationHandler的处理类似,但这个是放在mockHandler定义了这个invocation,这样说来,如果一个静态成员不该出现,那么把它放在中间处理器(mockHandler)里面作为成员变量,并作为new xxx()的入参,这算是一种解决办法。
至于
ThreadSafeMockingProgress.mockingProgress().reportOngoingStubbing(ongoingStubbing);
这一行,应该是为了线程安全,做的处理,
扩展callbackilter,可以用来多次增强并且在指定环境使用指定的方法,可参考:
实战CGLib系列之proxy篇(二):回调过滤CallbackFilter_java风云的博客-CSDN博客
我们看powermock源码的话,其实thenReturn就是addAnswer,
public OngoingStubbing<T> thenReturn(T var1) {
invocationContainerImpl.addAnswer(new Return0(var1));
return this;
}
所以其实就是我们要把invocationImpl传进MethodInteceptorImpl里面,那么powermock是通过mockHandlerImpl处理的:
现在就是不知道invocationImpl是啥时候传进去mockHandlerImpl的
应该是when的时候,我们会把invocationImpl注册进mockHandler里面,不是,when的时候会生成一个ongoingstub,然后这个stub给后面使用
而且,第一次when then是,powermock会实际调用这个方法,这个时候会往stub里面放进一个桩(通过when then放进去的),等第二次调用时,就不会了,因为已经有了stub,那么通过这个stub来answer,返回之前打桩的值。
还有一个比较麻烦的点就是,如何写出七种不同的inteceptor
spy
好像是给原来的方法包裹了一层拦截器(或者代理),调用的时候实际调用的是代理,然后代理调用拦截器,再通过InterceptedInvocation的callRealMethod来调用实际方法:
public Object callRealMethod() throws Throwable {
if (!this.realMethod.isInvokable()) {
throw Reporter.cannotCallAbstractRealMethod();
} else {
return this.realMethod.invoke();
}
}
而实际调用的是call方法:
public static class FromCallable extends RealMethod.FromBehavior implements RealMethod {
public FromCallable(final Callable<?> callable) {
super(new RealMethodBehavior() {
public Object call() throws Throwable {
return callable.call();
}
});
}
}
spy和mock用的都是同一套逻辑,不过spy会执行call方法,但是mock不会走进call方法里面,spy的调用如下
mock的调用栈如下,算了,mock没有调用栈...
ByteCodeFramework里面有@preparefortest注解的逻辑
在mockStatic的时候遇到了比较多的问题:
首先要读懂bytebuddy相关代码
比较核心的应该是
DynamicType,它有很多内嵌类和接口,比如Builder,而MethodDefinition继承自Builder,ReceiverTypeDefinition又继承自MethodDefinition,单独的接口ImplementationDefinition里面有个intercept方法,如下:
DynamicType.Builder.MethodDefinition.ReceiverTypeDefinition<U> intercept(Implementation var1);
AbstractBase继承自ImplementationDefinition,然后MethodMatchAdapter继承自AbstractBase,并且覆写了上面的方法:
public DynamicType.Builder.MethodDefinition.ReceiverTypeDefinition<U> intercept(Implementation implementation) {
return this.materialize(new ForImplementation(implementation));
}
不太知道materialize是干啥的
ByteBuddy使用入坑_tinysakurac的博客-CSDN博客
这个是bytebuddy的用法,关注Delegation用法