javassist 入门以及dubbo中的使用案例

news/2024/10/30 9:25:56/

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();}}}

这个方法是创建代理实例

  1. 注入public static java.lang.reflect.Method[] methods; 存放被代理对象的方法
  2. 注入InvocationHandler handler 属性;
  3. 注入沟通方法 一个是无参数,一个是 InvocationHandler handler参数的
  4. 增强每一个方法,存放到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.
}

http://www.ppmy.cn/news/312133.html

相关文章

x宝评论抓取

#某宝评论接口sign参数逆向 1.接口速览 多次请求发现&#xff0c;t为时间戳&#xff0c;sign为加密参数&#xff0c;盲猜和data、t有关&#xff0c;sign为32位&#xff0c;盲猜是字符串的32位的MD5 2.搜索js代码 这里为搜索的是appKey&#xff0c;就找到了sign&#xff0c;然…

知识变现海哥:值得反复思考的20句知识变现精华

哈喽&#xff0c;大家好&#xff0c;我是海哥&#xff0c;知识付费变现创业教练&#xff0c;教育公司培训总监&#xff0c;从事知识付费变现咨询10年&#xff0c;已助力3000人实现知识付费变现。 拉开人与人差距的有时不是转业和努力&#xff0c;而是一开始的认知和思维。 从…

16 进制转 10 进制

#include <stdio.h> //16进制转10进制 int main() {int a;printf("请输入16进制数:");scanf("%x",&a);//%x代表16进制printf("转换为10进制:");printf("%d",a);//%d带代表整形输出return 0; } &#xff05;d整型&#xff0c…

10进制数转16进制

已知&#xff1a;十进制数123被转换为十六进制数7B。这个转换过程如下&#xff1a;将123除以16&#xff0c;余数为11&#xff08;十六进制的B&#xff09;商为7.继续将7除以16&#xff0c;余数为7&#xff0c;商为0.因此7B就是123的十六进制数。 //10进制数转16进制System.out…

16进制转10进制

问题描述   从键盘输入一个不超过8位的正的十六进制数字符串&#xff0c;将它转换为正的十进制数后输出。   注&#xff1a;十六进制数中的10~15分别用大写的英文字母A、B、C、D、E、F表示。 样例输入 FFFF 样例输出 65535 求解 NO.1 思路&#xff1a;16进制转2进制&#…

windows10卸载Xshell6 报错-1603安装时出现致命错误(亲测有效)

1、报错如下图&#xff1a; 2、解决办法 1、在命令行窗口输入regedit打开注册表编辑器 2、找到 HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control 3、将 RegistrySizeLimit&#xff08;REG_DWORD类型的&#xff09; 的值改为 FFFFFFFF &#xff08;10进制就是 4294967295…

10进制转16进制

问题描述   十六进制数是在程序设计时经常要使用到的一种整数的表示方式。它有0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F共16个符号&#xff0c;分别表示十进制数的0至15。十六进制的计数方法是满16进1&#xff0c;所以十进制数16在十六进制中是10&#xff0c;而十进制的17在十六进制…

Win7安装CAD出现错误1606 最简单的解决方法

关于很多使用win7系统的网友在安装CAD的时候&#xff0c;都会遇到1606错误&#xff0c;网上的一些解决方法很不正规&#xff0c;或者没有办法解决方案&#xff0c;本站通过技术员验证&#xff0c;收集到了一个比较正确的解决方案。6个步骤简单帮你解决 1、新建记事本。 2、将以…