背景:
系统集成测试,需要写mock(单元测试有spock等mock工具)。
于是基于spring的aop实现了一个mock框架,可以用于集成测试。
但是只能对spring管理的bean进行mock。
对于有些工具类,是没办法mock的,比如一些加解密,签名验签,都是用静态方法实现的。
于是寻找一些解决方案,希望尽可能的减少侵入性,实现对静态方法、非spring对象的mock。
思路是在类加载之前,修改其字节码,实现aop。
于是选择了基于javassist的实现。
核心代码如下
MyMethodProxy.java
package org.jsirenia.javassist;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.jsirenia.reflect.ClassUtil;
import org.jsirenia.string.JRender;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.LoaderClassPath;
import javassist.NotFoundException;
/**
* 结合PackageUtil,可以设置指定包下的指定类的指定方法的 方法拦截。支持环绕通知。
*/
public class MyMethodProxy {
private static final Map contextMap = new ConcurrentHashMap<>();
public static Object invoke(String uid,String className, String methodName,String parameterTypeNames,Object self,Object[] args) throws Throwable{
Class> selfClass = Class.forName(className);
String[] parameterTypeArray = parameterTypeNames.split(",");
Class>[] parameterTypes = new Class[parameterTypeArray.length];
for(int i=0;i body = new ArrayList<>();
body.add("{");
if (isStatic) {
body.add("return ($r)${methodProxyName}.invoke(${uid},${className},${methodName},${parameterTypes},null,$args);");
} else {
body.add("return ($r)${methodProxyName}.invoke(${uid},${className},${methodName},${parameterTypes},$0,$args);");
}
body.add("}");
String bodyString = String.join("\n", body);
Map variables = new HashMap<>();
variables.put("methodProxyName", methodProxyName);
variables.put("uid", wrapQuota(uid));
variables.put("className", wrapQuota(className));
variables.put("methodName", wrapQuota(methodName));
variables.put("parameterTypes", wrapQuota(parameterTypes));
bodyString = new JRender("${","}").withVariables(variables ).render(bodyString);
method.setBody(bodyString);
}
ct.writeFile();
ct.toClass();
contextMap.put(uid, invoker);
};
private static String wrapQuota(String text){
return "\""+text+"\"";
}
}
MyMethodFilter.java
package org.jsirenia.javassist;
import javassist.CtMethod;
public interface MyMethodFilter {
boolean filter(CtMethod method);
}
MethodInvoker.java
package org.jsirenia.javassist;
import java.lang.reflect.Method;
public interface MethodInvoker {
Object invoke(Object self, Method thisMethod,Method proceed, Object[] args) throws Throwable;
}
如果是web项目,在ContextLoadListener中,对需要修改的类,调用MyMethodProxy的proxy方法。
效果:
类的字节码被修改,它的方法(包括静态方法)在执行时,会执行MethodInvoker的invoke方法。
可以在invoke方法中进行想要的操作,可以调用原来的逻辑、可以替换掉原来执行逻辑,反正就是
一个环绕通知的效果。
好处是可以对静态方法、私有方法进行拦截,也可以修改MyMethodProxy的实现,对继承的方法也进行
拦截。