基于javassist实现aop

背景:
系统集成测试,需要写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的实现,对继承的方法也进行
拦截。

你可能感兴趣的:(java)