zoukankan      html  css  js  c++  java
  • Javassist基本用法汇总

    最近项目需要对基础架构做增强,需要基于字节码在不侵入原有代码的情况下实现, 故把javassist的基本用法过了一遍。这篇博客就是把主要讲讲为什么要用javassist以及javassist的基本用法。

    1.为什么要使用javassist(上手成本低)

    基于字节码增强的框架有两个ASM和javassit,下面是两个框架的特点以及对比

    Javassist & ASM 对比

    1.Javassist源代码级API比ASM中实际的字节码操作更容易使用
    2.Javassist在复杂的字节码级操作上提供了更高级别的抽象层。Javassist源代码级API只需要很少的字节码知识,甚至不需要任何实际字节码知识,因此实现起来更容易、更快。
    3.Javassist使用反射机制,这使得它比运行时使用Classworking技术的ASM慢。
    总的来说ASM比Javassist快得多,并且提供了更好的性能。Javassist使用Java源代码的简化版本,然后将其编译成字节码。这使得Javassist非常容易使用,但是它也将字节码的使用限制在Javassist源代码的限制之内。
    总之,如果有人需要更简单的方法来动态操作或创建Java类,那么应该使用Javassist API 。如果需要注重性能地方,应该使用ASM库。

    2. javassist基本用法(基于3.28.0-GA的版本) 

    Javassist 是一个开源的分析、编辑和创建Java字节码的类库. 其主要优点在于简单快速. 直接使用 java 编码的形式, 而不需要了解虚拟机指令, 就能动态改变类的结构, 或者动态生成类.

    Javassist中最为重要的是ClassPool, CtClass, CtMethod以及CtField这几个类.

    ClassPool: 一个基于Hashtable实现的CtClass对象容器, 其中键是类名称, 值是表示该类的CtClass对象
    CtClass: CtClass表示类, 一个CtClass(编译时类)对象可以处理一个class文件, 这些CtClass对象可以从ClassPool获得
    CtMethods: 表示类中的方法
    CtFields: 表示类中的字段 

    2.1 ClassPool对象

    2.1.1 ClassPool的创建

    // 获取ClassPool对象, 使用系统默认类路径
    ClassPool pool = new ClassPool(true);
    // 效果与 new ClassPool(true) 一致
    ClassPool pool1 = ClassPool.getDefault();

    为减少ClassPool可能导致的内存消耗. 可以从ClassPool中删除不必要的CtClass对象. 或者每次创建新的ClassPool对象.

    // 从ClassPool中删除CtClass对象
    ctClass.detach();
    // 也可以每次创建一个新的ClassPool, 而不是ClassPool.getDefault(), 避免内存溢出
    ClassPool pool2 = new ClassPool(true);

    2.1.2 classpath

    通过 ClassPool.getDefault()获取的ClassPool使用JVM的classpath.在Tomcat等Web服务器运行时, 服务器会使用多个类加载器作为系统类加载器, 这可能导致ClassPool可能无法找到用户的类. 这时, ClassPool须添加额外的classpath才能搜索到用户的类.

    // 将classpath插入到指定classpath之前
    pool.insertClassPath(new ClassClassPath(this.getClass()));
    // 将classpath添加到指定classpath之后
    pool.appendClassPath(new ClassClassPath(this.getClass()));
    // 将一个目录作为classpath
    pool.insertClassPath("/xxx/lib");

    2.2 CtClass对象

    2.2.1 获取CtClass

    // 通过类名获取 CtClass, 未找到会抛出异常
    CtClass ctClass = pool.get("com.kawa.ssist.JustRun");
    // 通过类名获取 CtClass, 未找到返回 null, 不会抛出异常
    CtClass ctClass1 = pool.getOrNull("com.kawa.ssist.JustRun"); 

    2.2.2 创建CtClass

    // 复制一个类
    CtClass ctClass2 = pool.getAndRename("com.kawa.ssist.JustRun", "com.kawa.ssist.JustRunq");
    // 创建一个新类
    CtClass ctClass3 = pool.makeClass("com.kawa.ssist.JustRuna");
    // 通过class文件创建一个新类
    CtClass ctClass4 = pool.makeClass(new FileInputStream(new File("/home/un/test/JustRun.class")));

    2.2.3 CtClass基础信息

    // 类名
    String simpleName = ctClass.getSimpleName();
    // 类全名
    String name = ctClass.getName();
    // 包名
    String packageName = ctClass.getPackageName();
    // 接口
    CtClass[] interfaces = ctClass.getInterfaces();
    // 继承类
    CtClass superclass = ctClass.getSuperclass();
    // 获取类方法
    CtMethod ctMethod = ctClass.getDeclaredMethod("getName()", new CtClass[] {pool.get(String.class.getName()), pool.get(String.class.getName())});
    // 获取类字段
    CtField ctField = ctClass.getField("name");
    // 判断数组类型
    ctClass.isArray();
    // 判断原生类型
    ctClass.isPrimitive();
    // 判断接口类型
    ctClass.isInterface();
    // 判断枚举类型
    ctClass.isEnum();
    // 判断注解类型
    ctClass.isAnnotation();
    // 冻结一个类,使其不可修改
    ctClass.freeze () 
    // 判断一个类是否已被冻结
    ctClass.isFrozen()
    // 删除类不必要的属性,以减少内存占用。调用该方法后,许多方法无法将无法正常使用,慎用
    ctClass.prune() 
    //解冻一个类,使其可以被修改。如果事先知道一个类会被defrost, 则禁止调用prune方法
    ctClass.defrost() 

    2.2.4 CtClass类操作

    // 添加接口
    ctClass.addInterface(...);
    // 添加构造器
    ctClass.addConstructor(...);
    // 添加字段
    ctClass.addField(...);
    // 添加方法
    ctClass.addMethod(...);

    2.2.5 CtClass类编译

    // 获取字节码文件 需要注意的是一旦调用该方法,则无法继续修改已经被加载的class
    Class clazz = ctClass.toClass();
    // 类的字节码文件
    ClassFile classFile = ctClass.getClassFile();
    // 编译成字节码文件, 使用当前线程上下文类加载器加载类, 如果类已存在或者编译失败将抛出异常
    byte[] bytes = ctClass.toBytecode();

    2.3 CtMethod对象

    2.3.1 获取CtMethod属性

    CtClass ctClass5 = pool.get(TestService.class.getName());
    CtMethod ctMethod = ctClass5.getDeclaredMethod("selectOrder");
    // 方法名
    String methodName = ctMethod.getName();
    // 返回类型
    CtClass returnType = ctMethod.getReturnType();
    // 方法参数, 通过此种方式得到方法参数列表
    // 格式: com.kawa.TestService.getOrder(java.lang.String,java.util.List)
    ctMethod.getLongName();
    // 方法签名 格式: (Ljava/lang/String;Ljava/util/List;Lcom/test/Order;)Ljava/lang/Integer;
    ctMethod.getSignature();
    
    // 获取方法参数名称, 可以通过这种方式得到方法真实参数名称
    List<String> argKeys = new ArrayList<>();
    MethodInfo methodInfo = ctMethod.getMethodInfo();
    CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
    LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
    int len = ctMethod.getParameterTypes().length;
    // 非静态的成员函数的第一个参数是this
    int pos = Modifier.isStatic(ctMethod.getModifiers()) ? 0 : 1;
    for (int i = pos; i < len; i++) {
        argKeys.add(attr.variableName(i));
    }

    2.3.2 CtMethod方法体修改

    // 在方法体前插入代码块
    ctMethod.insertBefore("");
    // 在方法体后插入代码块
    ctMethod.insertAfter("");
    // 在某行 字节码 后插入代码块
    ctMethod.insertAt(10, "");
    // 添加参数
    ctMethod.addParameter(CtClass);
    // 设置方法名
    ctMethod.setName("newName");
    // 设置方法体 $0=this / $1,$2,$3... 代表方法参数
    ctMethod.setBody("{$0.name = $1;}");
    //创建一个新的方法
    ctMethod.make("kawa",CtClass);

    2.3.3 异常块 addCatch()

    在方法中加入try catch块, 需要注意的是, 必须在插入的代码中, 加入return值$e代表异常信息.插入的代码片段必须以throw或return语句结束

    CtMethod m = ...;
    CtClass etype = ClassPool.getDefault().get("java.io.IOException");
    m.addCatch("{ System.out.println($e); throw $e; }", etype);
    // 等同于添加如下代码: 
    try {
        // the original method body
    } catch (java.io.IOException e) {
        System.out.println(e);
        throw e;
    }

    2.4 特殊标识

    $0

    方法调用的目标对象. 它不等于this, 它代表了调用者. 如果方法是静态的, 则$0为null.

    $1, $2 ..

    方法的参数 m.insertBefore("{ System.out.println($1); System.out.println($2); }");

    $$

    是所有方法参数的简写, 主要用在方法调用上. 例如:
    
    // 原方法
    move(String a,String b)
    move($$)
    move($1,$2)
    // 如果新增一个方法, 方法含有move的所有参数, 则可以这些写: 
    move($$, context)
    move($1, $2, context)

    $args

    $args 指的是方法所有参数的数组,类似Object[],如果参数中含有基本类型,则会转成其包装类型。需要注意的时候,$args[0]对应的是$1,而不是$0,$0!=$args[0],$0=this

    $cflow

    $cflow意思为控制流(control flow),是一个只读的变量,值为一个方法调用的深度。例
    
    //原方法
    int fact(int n) {
        if (n <= 1)
            return n;
        else
            return n * fact(n - 1);
    }
    //javassist调用
    CtMethod cm = ...;
    //这里代表使用了cflow
    cm.useCflow("fact");
    //这里用了cflow,说明当深度为0的时候,就是开始当第一次调用fact的方法的时候,打印方法的第一个参数
    cm.insertBefore("if ($cflow(fact) == 0)"
                  + "    System.out.println("fact " + $1);");

    $_ 与 $r

    $_是方法调用的结果;$r是返回结果的类型, 用于强制类型转换
    
    Object result = ... ;
    $_ = ($r)result; 

    $w

    基本类型的包装类 Integer i = ($w)5;

    $class

    一个 java.lang.Class 对象, 表示当前正在修改的类

    $sig

    类型为 java.lang.Class 的参数类型数组

    $type

    一个 java.lang.Class 对象, 表示返回值类型

    $proceed

    调用表达式中方法的名称

    参考博客:https://zhuanlan.zhihu.com/p/349661837

    参考文档:https://www.javassist.org/tutorial/tutorial.html 

  • 相关阅读:
    opencv_图像的色彩空間cvtColor(HSV、HSL、HSB )及相关色彩学
    ASCII码字符对照表
    机器学习_logistic回归和梯度下降
    mysql 安装异常:this application requires .NET Framework(尚未安装.NET Framework 4.5 原因是:指定)
    面向对象的基本原则----
    面试题:“你能不能谈谈,java GC是在什么时候,对什么东西,做了什么事情?”
    String 解析--创建对象存储分析
    <cache> does not allow attribute "maxBytesLocalDisk
    Your 30-day trial of MyEclipse has expired 解决方案
    svn: E175002: java.lang.RuntimeException: Could not generate DH keypair svn: E175002: OPTIONS request failed on '/svn/ERPHR/HR_633'
  • 原文地址:https://www.cnblogs.com/hlkawa/p/15383289.html
Copyright © 2011-2022 走看看