Javassist如何操作Java 字节码

一、开篇

说起 AOP 小伙伴们肯定很熟悉,无论是 JDK 动态代理或者是 CGLIB 等,其底层都是通过操作 Java 字节码来实现代理。常用的一些操作字节码的技术有 ASM、AspectJ、Javassist 等。

ASM 其设计和实现是尽可能小而且快,更专注于性能。它在指令的层面来操作,所以使用它需要对 JVM 的指令有所了解,门槛较高,CGLIB 就使用了 ASM 技术。
AspectJ 扩展了 Java 语言,定义了一系列 AOP 语法,在 JVM 中运行需要使用特定的编译器生成遵守 Java 字节码规范的 Class 文件,Spring AOP 使用了 AspectJ 。
Javassist 直接使用 Java 编码的形式操作字节码,简单易上手,性能高于反射,相比于 ASM 稍低。

二、Javassist 常用类

Javassist 抽象出一个 ClassPool 对象来操作 Java 类,可以通过 ClassPool.getDefault() 来获取默认的 ClassPool 。常用的对象:

CtClass:代表一个 Class 的实例,可以通过类的全限定名来获取 CtClass 对象,其中包含了对 Class 的各种操作。
ClassPool:通过 HashTable 保存了路径下的 CtClass 信息,key为类的全限定名称,value 为类名对应的 CtClass 对象。
CtMethod、CtField:抽象出类的方法和属性,可以用于定义或修改方法和字段。

三、Javassist 的使用

1、依赖

<dependency>
  <groupId>org.javassist</groupId>
  <artifactId>javassist</artifactId>
  <version>3.27.0-GA</version>
</dependency>

2、代码示例

// 获取默认类池
  ClassPool classPool = ClassPool.getDefault();
  // 1. 创建空类
  CtClass ctClass = classPool.makeClass("com.aysaml.demo.javassist.User");

  // 2. 创建 String 类型的 name 字段
  CtField field = new CtField(classPool.get("java.lang.String"), "name", ctClass);
  // 设置字段访问级别 private
  field.setModifiers(Modifier.PRIVATE);
  // 增加字段
  ctClass.addField(field);

  // 3. 增加 getter & setter 方法
  ctClass.addMethod(CtNewMethod.getter("getName", field));
  ctClass.addMethod(CtNewMethod.setter("setName", field));

  // 4. 增加无参构造方法:其中 $0 表示 this,$1 表示参数
  CtConstructor noArgsCons = new CtConstructor(new CtClass[] {}, ctClass);
  noArgsCons.setBody("{$0.name=\"mark\";}");
  ctClass.addConstructor(noArgsCons);

  // 5. 增加有参构造方法
  CtConstructor hasArgsCons =
    new CtConstructor(new CtClass[] {classPool.get("java.lang.String")}, ctClass);
  hasArgsCons.setBody("{$0.name=$1;}");
  ctClass.addConstructor(hasArgsCons);

  // 6. 创建方法
  CtMethod method = new CtMethod(CtClass.voidType, "printName", new CtClass[] {}, ctClass);
  method.setBody("{System.out.println($0.name);}");
  ctClass.addMethod(method);

  // 7. 生成类文件:可指定路径,默认为当前项目根目录
  ctClass.writeFile();

  // 8. 创建类实例
  Object person = ctClass.toClass().newInstance();

3、如何实现类似 AOP 的功能

由上可见,Javassist 对于编程化的操作字节码是很简单易懂的,我们以在方法的开头结尾打印信息为例:

public class Cat {

 /** 记录喵喵喵的次数 */
 private int num;

 public void miao() {
  this.num++;
 }
}

我们要在 miao( ) 方法的前增加声音输出:

public static void main(String[] args) throws NotFoundException, CannotCompileException {
  ClassPool classPool = ClassPool.getDefault();
  // 获取 Cat 类的 CtClass 对象
  CtClass catClass = classPool.get("com.aysaml.demo.javassist.Cat");
  // 获取 miao( ) 方法
  CtMethod method = catClass.getDeclaredMethod("miao");
  method.insertBefore("System.out.println(\"miao~\");");
  // 加载修改过的类,注意必须要保证调用前这个类没有被加载过
  catClass.toClass();
  //测试
  Cat cat = new Cat();
  cat.miao();
 }

注意到,在使用 catClass.toClass() 加载被修改过的类时,强调必须保证在调用前这个类没有被加载过,否则会报 attempted duplicate class definition for name 异常。

我们知道一个类是不能被一个类加载器加载两次的,所以为了解决这个问题,需要制定一个没有加载过该类的 Classloader,Javassist 提供了一个 ClassLoader ,如下:

public class Cat {

 /** 记录喵喵喵的次数 */
 private int num;

 public void miao() {
  System.out.println("调用了 miao 方法");
  this.num++;
 }

 public static void main(String[] args) throws Exception{
  ClassPool classPool = ClassPool.getDefault();
  // 获取 Cat 类的 CtClass 对象
  CtClass catClass = classPool.get("com.aysaml.demo.javassist.Cat");
  // 获取 miao( ) 方法
  CtMethod method = catClass.getDeclaredMethod("miao");
  method.insertBefore("System.out.println(\"miao~\");");
  // 重新设置一个 Classloader
  Loader classLoader = new Loader(classPool);
  Class clazz = classLoader.loadClass("com.aysaml.demo.javassist.Cat");
  // 调用修改过的类的方法
  clazz.getDeclaredMethod("miao").invoke(clazz.newInstance());
 }
}

执行结果为:

四、结语

关于 Javassist 暂时就说这么多了,更多使用方法参考官方 github wiki:

以上就是Javassist如何操作Java 字节码的详细内容,更多关于Javassist 操作Java 字节码的资料请关注我们其它相关文章!

(0)

相关推荐

  • 基于javassist进行动态编程过程解析

    今天在研究dubbo时,发现一个新的知识点,可以使用javassist包进行动态编程,hibernate也使用该包进行编程.晚上百度了很多资料,将它的特性以代码的形式展现出来. package com.zhi.demo; import java.lang.reflect.Field; import javassist.ClassPool; import javassist.CtClass; import javassist.CtConstructor; import javassist.CtFi

  • javassist使用指南

    Java 字节码以二进制的形式存储在 .class 文件中,每一个 .class 文件包含一个 Java 类或接口.Javaassist 就是一个用来 处理 Java 字节码的类库.它可以在一个已经编译好的类中添加新的方法,或者是修改已有的方法,并且不需要对字节码方面有深入的了解.同时也可以去生成一个新的类对象,通过完全手动的方式. 1. 使用 Javassist 创建一个 class 文件 首先需要引入jar包: <dependency> <groupId>org.javassi

  • Eclipse下Javassist正确使用方法代码解析

    这两天看到Hibernate的代理部分,第一反应是底层使用了反射,针对用户实体生成了代理类,后来反应过来了,反射没有任何可以产生新类的能力,也就顺理成章地找到了Javassist(下载地址). 在网上搜索到的大部分教程,都是针对Javassist的API进行一番讲解,但是最后,往往没有一个加载过程,而笔者模仿这些教程进行类的加载时,加载到的结果都是原来的类,并没有产生字节码被修改的内容. 在经过一番探索后,笔者发现,网上的大部分教程中的最后一步,保存字节码,使用的均是writeFile的无参数重

  • Javassist之一秒理解java动态编程

    概述 什么是动态编程?动态编程解决什么问题?Java中如何使用?什么原理?如何改进?(需要我们一起探索,由于自己也是比较菜,一般深入不到这个程度). 什么是动态编程 动态编程是相对于静态编程而言的,平时我们讨论比较多的就是静态编程语言,例如Java,与动态编程语言,例如JavaScript.那二者有什么明显的区别呢?简单的说就是在静态编程中,类型检查是在编译时完成的,而动态编程中类型检查是在运行时完成的.所谓动态编程就是绕过编译过程在运行时进行操作的技术,在Java中有如下几种方式: 反射 这个

  • Javassist如何操作Java 字节码

    一.开篇 说起 AOP 小伙伴们肯定很熟悉,无论是 JDK 动态代理或者是 CGLIB 等,其底层都是通过操作 Java 字节码来实现代理.常用的一些操作字节码的技术有 ASM.AspectJ.Javassist 等. ASM 其设计和实现是尽可能小而且快,更专注于性能.它在指令的层面来操作,所以使用它需要对 JVM 的指令有所了解,门槛较高,CGLIB 就使用了 ASM 技术. AspectJ 扩展了 Java 语言,定义了一系列 AOP 语法,在 JVM 中运行需要使用特定的编译器生成遵守

  • 详解Java字节码编程之非常好用的javassist

    一.Javassist入门 (一)Javassist是什么 Javassist是可以动态编辑Java字节码的类库.它可以在Java程序运行时定义一个新的类,并加载到JVM中:还可以在JVM加载时修改一个类文件.Javassist使用户不必关心字节码相关的规范也是可以编辑类文件的. (二)Javassist核心API 在Javassist中每个需要编辑的class都对应一个CtCLass实例,CtClass的含义是编译时的类(compile time class),这些类会存储在Class Poo

  • java字节码框架ASM操作字节码的方法浅析

    之前我们已经对ASM进行的详细的介绍,需要的朋友们可以点击这里:java字节码框架ASM的深入学习 JVM的类型签名对照表 Type Signature Java Type Z boolean B byte C char S short I int J long F float D double L fully-qualified-class ;fully-qualified-class [ type type[] ( arg-types ) ret-type method type 比如,ja

  • Java字节码增强技术知识点详解

    简单介绍下几种java字节码增强技术. ASM ASM是一个Java字节码操控框架,它能被用来动态生成类或者增强既有类的功能.ASM可以直接产生class文件,也可以在类被加载入Java虚拟机之前动态改变类行为.ASM从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类. 主页:https://asm.ow2.io/index.html ASM框架中的核心类有以下几个: ① ClassReader:该类用来解析编译过的class字节码文件. ② ClassWriter:

  • Java字节码的增强技术

    目录 Java字节码的增强技术 一.简单介绍下几种java字节码增强技术 1.ASM 2.Javassist 3.Byte Buddy 4.JVM-SANDBOX Java字节码的增强技术 一.简单介绍下几种java字节码增强技术 1.ASM ASM是一个Java字节码操控框架,它能被用来动态生成类或者增强既有类的功能.ASM可以直接产生class文件,也可以在类被加载入Java虚拟机之前动态改变类行为.ASM从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类. AS

  • Java字节码中jvm实例用法

    要想使Java运行,我们可以设计一个面向Java语言特性的虚拟机,并通过编译器将Java程序转换为它可以识别的指令序列,也称为Java字节码.由于Java字节码指令的操作码被固定为一个字节,所以它的名字就这样命名了.本篇要带来的是Java字节码中jvm的使用,主要分为两个操作视角,一起来看看具体内容吧. 1.虚拟机视角 在执行Java代码时,首先需要将编译后的class文件装入Java虚拟机.装入的Java类将存储在方法区(MethodArea)中.虚拟机会在实际运行时执行方法区内的代码.Jav

  • 值得收藏!教你如何在IDEA中快速查看Java字节码

    一.javap的参数 -help  --help  -?        输出此用法消息   -version                 版本信息   -v  -verbose             输出附加信息   -l                       输出行号和本地变量表   -public                  仅显示公共类和成员   -protected               显示受保护的/公共类和成员   -package              

  • 学会Java字节码指令,成为技术大佬

    目录 01.加载与存储指令 1)将局部变量表中的变量压入操作数栈中 2)将常量池中的常量压入操作数栈中 3)将栈顶的数据出栈并装入局部变量表中 02.算术指令 1)创建指令 2)字段访问指令 1)比较指令 2)条件跳转指令 3)比较条件转指令 4)多条件分支跳转指令 5)无条件跳转指令 Java 官方的虚拟机 Hotspot 是基于栈的,而不是基于寄存器的. 基于栈的优点是可移植性更好.指令更短.实现起来简单,但不能随机访问栈中的元素,完成相同功能所需要的指令数也比寄存器的要多,需要频繁的入栈和

  • 一篇文章带你从java字节码层理解i++和++i

    目录 程序目的 关键指令 i++示例源码 使用jclasslib查看i++字节码 ++i示例源码 参考 总结 程序目的 从java字节码层理解,为何i = i++后,结果是+1之前的数值.而i=++i后,结果是+1之后的值. 关键指令 iload_<n>:从局部变量表获取值,并压入操作数栈. istore_<n>:出栈,然后存储到局部变量表. i++示例源码 public class TestIPulsPlus { public static void main(String[]

  • IDEA神器一键查看Java字节码及其他类信息插件

    开始推荐 IDEA 字节码查看神器之前,先来回顾一下 Java 字节码是啥. 何为 Java 字节码? Java 虚拟机(JVM)是运行 Java 字节码的虚拟机.JVM 有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果. 什么是字节码?采用字节码的好处是什么? 在 Java 中,JVM 可以理解的代码就叫做字节码的文件),它不面向任何特定的处理器,只面向虚拟机.Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行

随机推荐