javassite 入门
- 概述
- 原理
- 简单的demo
- 记录方法执行的时间
- 带参数和返回值
- javassite 占位符
- dubbo中的使用
- 代理工厂 JavassistProxyFactory
- 代理类 org.apache.dubbo.common.bytecode.Proxy
- org.apache.dubbo.rpc.proxy.InvokerInvocationHandler
- 创建类的工具类 ClassGenerator
概述
学习javassite 不得不提的javaagent 技术,字节码插桩 如果不太了解的可以参考 Javaagent入门这一篇文章 自己有一个简单的大致了解
Javassist是一个开源的分析、编辑和创建Java字节码的类库。其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成
Javassist 应用场景:
监控、动态代理
dubbo、myBatis、Spring 都有不同程度的应用。
javaagen 是操作字节码,对jvm了解的要求非常高,因此就有了对字节码操作的代码库,比如asm和javassite这样的框架。
原理
简单的demo
pom依赖
<dependency><groupId>org.javassist</groupId><artifactId>javassist</artifactId><version>3.29.2-GA</version></dependency>
我们修改下面的字节码内容
public class UserService {public void saveUser(){System.out.println("保存用户");}
}
开始修改:
@Testpublic void test1() throws NotFoundException, CannotCompileException {ClassPool classPool = ClassPool.getDefault();CtClass ctClass = classPool.get("com.wfg.javassite.UserService");CtMethod saveUser = ctClass.getDeclaredMethod("saveUser");saveUser.insertAfter("System.out.println(System.currentTimeMillis());");//把修改后的类装入jvmctClass.toClass();new UserService().saveUser();}
记录方法执行的时间
@Testpublic void test2() throws NotFoundException, CannotCompileException {ClassPool classPool = ClassPool.getDefault();CtClass ctClass = classPool.get("com.wfg.javassite.UserService");CtMethod saveUser = ctClass.getDeclaredMethod("saveUser");saveUser.insertBefore("long start = System.currentTimeMillis();");saveUser.insertAfter("long end = System.currentTimeMillis();" +"System.out.println(end - start);");//把修改后的类装入jvmctClass.toClass();new UserService().saveUser();}
从错误信息中可以看出来试 没有start 变量导致的,
ps: javassite的语法插入语句默认 使用{} 括号括着,因此start 和 end 不在同一个代码块中
因此我们需要把整体的插入:使用一个代理的方法
saveUser(){
saveUser$agent();
}
这样的方式处理
public void test2() throws NotFoundException, CannotCompileException {ClassPool classPool = ClassPool.getDefault();CtClass ctClass = classPool.get("com.wfg.javassite.UserService");CtMethod saveUser = ctClass.getDeclaredMethod("saveUser");//这种写法不符合语法,存在代码
// saveUser.insertBefore("long start = System.currentTimeMillis();");
// saveUser.insertAfter("long end = System.currentTimeMillis();" +
// "System.out.println(end - start);");CtMethod newMethod = CtNewMethod.copy(saveUser, ctClass, null);newMethod.setName(saveUser.getName()+"$agent");ctClass.addMethod(newMethod);saveUser.setBody("{long start = System.currentTimeMillis();\n" +" saveUser$agent();\n" +" long end = System.currentTimeMillis();\n" +" System.out.println(end-start);}");//把修改后的类装入jvmctClass.toClass();new UserService().saveUser();}
带参数和返回值
@Testpublic void test3() throws Exception {ClassPool classPool = ClassPool.getDefault();CtClass ctClass = classPool.get("com.wfg.javassite.UserService");CtMethod save = ctClass.getDeclaredMethod("save");CtMethod newMethod = CtNewMethod.copy(save, ctClass, null);newMethod.setName(save.getName()+"$agent");ctClass.addMethod(newMethod);save.setBody("{long start = System.currentTimeMillis();\n" +" save$agent($$);\n" +" long end = System.currentTimeMillis();\n" +" System.out.println(end-start);" +" return $3 ;" +"}");//把修改后的类装入jvmctClass.toClass();User user = new UserService().save("张三", 12, new User());System.out.println(user.toString());}
javassite 占位符
符号 | 描述 |
$0, $1, $2, ... | $0表示this,其他的表示实际的参数 |
$args | 参数数组. 相当于new Object[]{$1,$2,....},其中的基本类型会被转为包装类型 |
$$ | 所有的参数,如m($$)相当于m($1,$2...),如果m无参数则m($$)相当于m() |
$cflow(...) | 表示一个指定的递归调用的深度 |
$r | 用于类型装换,表示返回值的类型. |
$w | 将基础类型转换为一个包装类型.如Integer a=($w)5;表示将5转换为Integer。如果不是基本类型则什么都不做。 |
$_ | 返回值,如果方法为void,则返回值为null; 值在方法返回前获得, 如果希望发生异常是有返回值(默认值,如nul),需要将insertAfter方法的第二个参数asFinally设置为true |
$sig | 方法参数的类型数组,数组的顺序为参数的顺序 |
$type | 返回类型的class, 如返回Integer则$type相当于java.lang.Integer.class, 注意其与$r的区别 |
$class | 方法所在的类的class |
dubbo中的使用
代理工厂 JavassistProxyFactory
org.apache.dubbo.rpc.proxy.javassist.JavassistProxyFactory
这个是dubbo代理工厂的其中之一
@Override@SuppressWarnings("unchecked")public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {try {//创建代理类 并且实例化return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));} catch (Throwable fromJavassist) {// try fall back to JDK proxy factory//如果失败使用jdk代理兜底try {T proxy = jdkProxyFactory.getProxy(invoker, interfaces);logger.error(PROXY_FAILED, "", "", "Failed to generate proxy by Javassist failed. Fallback to use JDK proxy success. " +"Interfaces: " + Arrays.toString(interfaces), fromJavassist);return proxy;} catch (Throwable fromJdk) {logger.error(PROXY_FAILED, "", "", "Failed to generate proxy by Javassist failed. Fallback to use JDK proxy is also failed. " +"Interfaces: " + Arrays.toString(interfaces) + " Javassist Error.", fromJavassist);logger.error(PROXY_FAILED, "", "", "Failed to generate proxy by Javassist failed. Fallback to use JDK proxy is also failed. " +"Interfaces: " + Arrays.toString(interfaces) + " JDK Error.", fromJdk);throw fromJavassist;}}}
(T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
代理类 org.apache.dubbo.common.bytecode.Proxy
这个类是参数判断和缓存功能
public static Proxy getProxy(Class<?>... ics) {}
private static Class<?> buildProxyClass(ClassLoader cl, Class<?>[] ics, ProtectionDomain domain) {ClassGenerator ccp = null;try {// 类的创建工具类,ccp = ClassGenerator.newInstance(cl);//保存方法描述Set<String> worked = new HashSet<>();List<Method> methods = new ArrayList<>();//判断不是 public 包名是否相同 作为基准String pkg = ics[0].getPackage().getName();Class<?> neighbor = ics[0];for (Class<?> ic : ics) {String npkg = ic.getPackage().getName();if (!Modifier.isPublic(ic.getModifiers())) { //判断不是 public 并且包名不同if (!pkg.equals(npkg)) {throw new IllegalArgumentException("non-public interfaces from different packages");}}ccp.addInterface(ic);for (Method method : ic.getMethods()) {//* int do(int arg1) => "do(I)I"// * void do(String arg1,boolean arg2) => "do(Ljava/lang/String;Z)V"String desc = ReflectUtils.getDesc(method);if (worked.contains(desc) || Modifier.isStatic(method.getModifiers())) {continue;}worked.add(desc);int ix = methods.size();Class<?> rt = method.getReturnType();Class<?>[] pts = method.getParameterTypes();StringBuilder code = new StringBuilder("Object[] args = new Object[").append(pts.length).append("];");for (int j = 0; j < pts.length; j++) {code.append(" args[").append(j).append("] = ($w)$").append(j + 1).append(';');}code.append(" Object ret = handler.invoke(this, methods[").append(ix).append("], args);");if (!Void.TYPE.equals(rt)) {code.append(" return ").append(asArgument(rt, "ret")).append(';');}methods.add(method);ccp.addMethod(method.getName(), method.getModifiers(), rt, pts, method.getExceptionTypes(), code.toString());}}// create ProxyInstance class.String pcn = neighbor.getName() + "DubboProxy" + PROXY_CLASS_COUNTER.getAndIncrement();ccp.setClassName(pcn);ccp.addField("public static java.lang.reflect.Method[] methods;");ccp.addField("private " + InvocationHandler.class.getName() + " handler;");ccp.addConstructor(Modifier.PUBLIC, new Class<?>[]{InvocationHandler.class}, new Class<?>[0], "handler=$1;");ccp.addDefaultConstructor();Class<?> clazz = ccp.toClass(neighbor, cl, domain);clazz.getField("methods").set(null, methods.toArray(new Method[0]));return clazz;} catch (RuntimeException e) {throw e;} catch (Exception e) {throw new RuntimeException(e.getMessage(), e);} finally {// release ClassGeneratorif (ccp != null) {ccp.release();}}}
这个方法是创建代理实例
- 注入public static java.lang.reflect.Method[] methods; 存放被代理对象的方法
- 注入InvocationHandler handler 属性;
- 注入沟通方法 一个是无参数,一个是 InvocationHandler handler参数的
- 增强每一个方法,存放到methods中
org.apache.dubbo.rpc.proxy.InvokerInvocationHandler
通过上面的分析我们知道,代理类真正的是 InvokerInvocationHandler 的invoke方法
创建类的工具类 ClassGenerator
package com.wfg.javassite.mydubbo;import javassist.*;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.ProtectionDomain;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;/*** @author wufagang* @description* @date 2023年06月10日 12:56*/
public class ClassGenerator {private static final AtomicLong CLASS_NAME_COUNTER = new AtomicLong(0);private static final String SIMPLE_NAME_TAG = "<init>";private static final Map<ClassLoader, ClassPool> POOL_MAP = new ConcurrentHashMap<>(); //ClassLoader - ClassPoolprivate ClassPool mPool;private CtClass mCtc;private String mClassName;private String mSuperClass;private Set<String> mInterfaces;private List<String> mFields;private List<String> mConstructors;private List<String> mMethods;private ClassLoader mClassLoader;private Map<String, Method> mCopyMethods; // <method desc,method instance>private Map<String, Constructor<?>> mCopyConstructors; // <constructor desc,constructor instance>private boolean mDefaultConstructor = false;private ClassGenerator() {}private ClassGenerator(ClassLoader classLoader, ClassPool pool) {mClassLoader = classLoader;mPool = pool;}public static ClassGenerator newInstance() {return new ClassGenerator(Thread.currentThread().getContextClassLoader(), getClassPool(Thread.currentThread().getContextClassLoader()));}public static ClassGenerator newInstance(ClassLoader loader) {return new ClassGenerator(loader, getClassPool(loader));}public static boolean isDynamicClass(Class<?> cl) {return ClassGenerator.DC.class.isAssignableFrom(cl);}public static ClassPool getClassPool(ClassLoader loader) {if (loader == null) {return ClassPool.getDefault();}ClassPool pool = POOL_MAP.get(loader);if (pool == null) {pool = new ClassPool(true);pool.insertClassPath(new LoaderClassPath(loader));POOL_MAP.put(loader, pool);}return pool;}private static String modifier(int mod) {StringBuilder modifier = new StringBuilder();if (Modifier.isPublic(mod)) {modifier.append("public");} else if (Modifier.isProtected(mod)) {modifier.append("protected");} else if (Modifier.isPrivate(mod)) {modifier.append("private");}if (Modifier.isStatic(mod)) {modifier.append(" static");}if (Modifier.isVolatile(mod)) {modifier.append(" volatile");}return modifier.toString();}public String getClassName() {return mClassName;}public ClassGenerator setClassName(String name) {mClassName = name;return this;}public ClassGenerator addInterface(String cn) {if (mInterfaces == null) {mInterfaces = new HashSet<>();}mInterfaces.add(cn);return this;}public ClassGenerator addInterface(Class<?> cl) {return addInterface(cl.getName());}public ClassGenerator setSuperClass(String cn) {mSuperClass = cn;return this;}public ClassGenerator setSuperClass(Class<?> cl) {mSuperClass = cl.getName();return this;}// 写好对字符串属性 private Object aa;public ClassGenerator addField(String code) {if (mFields == null) {mFields = new ArrayList<>();}mFields.add(code);return this;}//没有赋值public ClassGenerator addField(String name, int mod, Class<?> type) {return addField(name, mod, type, null);}// 处理完整的 带有赋值的属性public ClassGenerator addField(String name, int mod, Class<?> type, String def) {StringBuilder sb = new StringBuilder();sb.append(modifier(mod)).append(' ').append(ReflectUtils.getName(type)).append(' ');sb.append(name);if (StringUtils.isNotEmpty(def)) {sb.append('=');sb.append(def);}sb.append(';');return addField(sb.toString());}//完整的方法体public ClassGenerator addMethod(String code) {if (mMethods == null) {mMethods = new ArrayList<>();}mMethods.add(code);return this;}public ClassGenerator addMethod(String name, int mod, Class<?> rt, Class<?>[] pts, String body) {return addMethod(name, mod, rt, pts, null, body);}/**** @author wufagang* @date 2023/6/10 13:22* @param name* @param mod* @param rt 返回值* @param pts 参数* @param ets 异常类型* @param body 方法体* @return com.wfg.javassite.mydubbo.ClassGenerator*/public ClassGenerator addMethod(String name, int mod, Class<?> rt, Class<?>[] pts, Class<?>[] ets,String body) {StringBuilder sb = new StringBuilder();sb.append(modifier(mod)).append(' ').append(ReflectUtils.getName(rt)).append(' ').append(name);sb.append('(');if (ArrayUtils.isNotEmpty(pts)) {for (int i = 0; i < pts.length; i++) {if (i > 0) {sb.append(',');}sb.append(ReflectUtils.getName(pts[i]));sb.append(" arg").append(i);}}sb.append(')');if (ArrayUtils.isNotEmpty(ets)) {sb.append(" throws ");for (int i = 0; i < ets.length; i++) {if (i > 0) {sb.append(',');}sb.append(ReflectUtils.getName(ets[i]));}}sb.append('{').append(body).append('}');return addMethod(sb.toString());}public ClassGenerator addMethod(Method m) {addMethod(m.getName(), m);return this;}public ClassGenerator addMethod(String name, Method m) {String desc = name + ReflectUtils.getDescWithoutMethodName(m);addMethod(':' + desc);if (mCopyMethods == null) {mCopyMethods = new ConcurrentHashMap<>(8);}mCopyMethods.put(desc, m);return this;}public ClassGenerator addConstructor(String code) {if (mConstructors == null) {mConstructors = new LinkedList<>();}mConstructors.add(code);return this;}public ClassGenerator addConstructor(int mod, Class<?>[] pts, String body) {return addConstructor(mod, pts, null, body);}public ClassGenerator addConstructor(int mod, Class<?>[] pts, Class<?>[] ets, String body) {StringBuilder sb = new StringBuilder();sb.append(modifier(mod)).append(' ').append(SIMPLE_NAME_TAG);sb.append('(');for (int i = 0; i < pts.length; i++) {if (i > 0) {sb.append(',');}sb.append(ReflectUtils.getName(pts[i]));sb.append(" arg").append(i);}sb.append(')');if (ArrayUtils.isNotEmpty(ets)) {sb.append(" throws ");for (int i = 0; i < ets.length; i++) {if (i > 0) {sb.append(',');}sb.append(ReflectUtils.getName(ets[i]));}}sb.append('{').append(body).append('}');return addConstructor(sb.toString());}public ClassGenerator addConstructor(Constructor<?> c) {String desc = ReflectUtils.getDesc(c);addConstructor(":" + desc);if (mCopyConstructors == null) {mCopyConstructors = new ConcurrentHashMap<>(4);}mCopyConstructors.put(desc, c);return this;}public ClassGenerator addDefaultConstructor() {mDefaultConstructor = true;return this;}public ClassPool getClassPool() {return mPool;}/*** @param neighbor A class belonging to the same package that this* class belongs to. It is used to load the class.*/public Class<?> toClass(Class<?> neighbor) {return toClass(neighbor,mClassLoader,getClass().getProtectionDomain());}public Class<?> toClass(Class<?> neighborClass, ClassLoader loader, ProtectionDomain pd) {if (mCtc != null) {mCtc.detach();}long id = CLASS_NAME_COUNTER.getAndIncrement();try {CtClass ctcs = mSuperClass == null ? null : mPool.get(mSuperClass);if (mClassName == null) {mClassName = (mSuperClass == null || javassist.Modifier.isPublic(ctcs.getModifiers())? ClassGenerator.class.getName() : mSuperClass + "$sc") + id;}mCtc = mPool.makeClass(mClassName);if (mSuperClass != null) {mCtc.setSuperclass(ctcs);}mCtc.addInterface(mPool.get(DC.class.getName())); // add dynamic class tag.if (mInterfaces != null) {for (String cl : mInterfaces) {mCtc.addInterface(mPool.get(cl));}}if (mFields != null) {for (String code : mFields) {mCtc.addField(CtField.make(code, mCtc));}}if (mMethods != null) {for (String code : mMethods) {if (code.charAt(0) == ':') {mCtc.addMethod(CtNewMethod.copy(getCtMethod(mCopyMethods.get(code.substring(1))),code.substring(1, code.indexOf('(')), mCtc, null));} else {mCtc.addMethod(CtNewMethod.make(code, mCtc));}}}if (mDefaultConstructor) {mCtc.addConstructor(CtNewConstructor.defaultConstructor(mCtc));}if (mConstructors != null) {for (String code : mConstructors) {if (code.charAt(0) == ':') {mCtc.addConstructor(CtNewConstructor.copy(getCtConstructor(mCopyConstructors.get(code.substring(1))), mCtc, null));} else {String[] sn = mCtc.getSimpleName().split("\\$+"); // inner class name include $.mCtc.addConstructor(CtNewConstructor.make(code.replaceFirst(SIMPLE_NAME_TAG, sn[sn.length - 1]), mCtc));}}}try {return mPool.toClass(mCtc, neighborClass, loader, pd);} catch (Throwable t) {if (!(t instanceof CannotCompileException)) {return mPool.toClass(mCtc, loader, pd);}throw t;}} catch (RuntimeException e) {throw e;} catch (NotFoundException | CannotCompileException e) {throw new RuntimeException(e.getMessage(), e);}}public void release() {if (mCtc != null) {mCtc.detach();}if (mInterfaces != null) {mInterfaces.clear();}if (mFields != null) {mFields.clear();}if (mMethods != null) {mMethods.clear();}if (mConstructors != null) {mConstructors.clear();}if (mCopyMethods != null) {mCopyMethods.clear();}if (mCopyConstructors != null) {mCopyConstructors.clear();}}private CtClass getCtClass(Class<?> c) throws NotFoundException {return mPool.get(c.getName());}private CtMethod getCtMethod(Method m) throws NotFoundException {return getCtClass(m.getDeclaringClass()).getMethod(m.getName(), ReflectUtils.getDescWithoutMethodName(m));}private CtConstructor getCtConstructor(Constructor<?> c) throws NotFoundException {return getCtClass(c.getDeclaringClass()).getConstructor(ReflectUtils.getDesc(c));}public static interface DC {} // dynamic class tag interface.
}