操作 Java 字节码

栏目: Java · 发布时间: 6年前

内容简介:通常对于用 idea 的同学来说,class 文件是直接可以查看的,可以看到像 java 那样的代码。其实 class 文件是一种字节码文件,我们平时在 idea 所看到的,是 idea 自动反编译后的结果。如果把 class 文件用 sublime 打开,就会看到许多字节码,而不是 Java 代码了。像这样:Class文件是一组以 8 位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑排列在 Class 文件中,中间无任何分隔符。我们这里所说的操作 Java 字节码,就是操作修改 class 文件

通常对于用 idea 的同学来说,class 文件是直接可以查看的,可以看到像 java 那样的代码。其实 class 文件是一种字节码文件,我们平时在 idea 所看到的,是 idea 自动反编译后的结果。如果把 class 文件用 sublime 打开,就会看到许多字节码,而不是 Java 代码了。像这样:

cafe babe 0000 0034 0017 0100 1163 6e2f
6863 6873 7475 6469 6f2f 5573 6572 0700
0101 0010 6a61 7661 2f6c 616e 672f 4f62
6a65 6374 0700 0301 0004 6e61 6d65 0100
......

Class文件是一组以 8 位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑排列在 Class 文件中,中间无任何分隔符。

我们这里所说的操作 Java 字节码,就是操作修改 class 文件内容。

Why

同学们可能会有这样一个疑问,为什么要操作 Java 字节码,直接改 java 文件不是很好吗?

很多情况下是无法操作的 java 文件的,或者使用修改字节码的方式更方便:

  1. 在第三方依赖中加入一些检测数据
  2. AOP 操作,例如 Android 自动埋点统计
  3. Spring 框架的 AOP 操作使用 ASM 操作 Java 字节码

总的来说,可以更方便开发,也同时了解一些底层的原理。

Javassist

Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba(千叶 滋)所创建的。它已加入了开放源代码 JBoss 应用服务器项目,通过使用Javassist对字节码操作为 JBoss 实现动态”AOP”框架。

导包

compile group: 'org.javassist', name: 'javassist', version: '3.23.1-GA'

版本号可能不是最新的,想要最新的话查找 Maven 仓库获取最新的版本号即可。

类搜索路径

通过 ClassPool.getDefault() 获取的 ClassPool 使用 JVM 的类搜索路径。如果程序运行在 JBoss 或者 Tomcat 等 Web 服务器上,ClassPool 可能无法找到用户的类,因为 Web 服务器使用多个类加载器作为系统类加载器。在这种情况下,ClassPool 必须添加额外的类搜索路径。

下面的例子中,pool 代表一个 ClassPool 对象:

pool.insertClassPath(new ClassClassPath(this.getClass()));

上面的语句将 this 指向的类添加到 pool 的类加载路径中。你可以使用任意 Class 对象来代替 this.getClass(),从而将 Class 对象添加到类加载路径中。传参支持 ClassPath、URLClassPath、ByteArrayClassPath 类型。

编辑

// 创建 User 类
CtClass ctClass = classPool.makeClass("cn.hchstudio.User");
// 获取 String 类
CtClass CtString = classPool.get("java.lang.String");

通过 makeClass 和 get 方法可以分别创建、获取 CtClass,进而操作类。

CtField name = new CtField(CtString, "name", ctClass);
name.setModifiers(Modifier.PRIVATE);
ctClass.addField(name);

上面的语句是创建一个变量,new CtField 中分别传入类型、名称、ctClass。setModifiers 设置变量修饰符;addField 表示把变量加入到这个类中。

CtMethod setSex = CtMethod.make("public void setSex(java.lang.String sex){" +
                    "this.sex = sex;" +
                    "}", ctClass);
ctClass.addMethod(setSex);

Javassist 有一个简单除暴的新增方法方式,就是直接把要写的 java 代码变为字符串,之后 Javassist 便可自动完成代码校验,转为字节码的过程。

对于已存在的方法,可以使用 insertBefore、insertAfter 方法插入到方法函数之后或之后。

ctMethod.insertBefore("System.out.println(\"lalala\");");
ctMethod.insertAfter("System.out.println(\"lalala\");");

一个栗子

举一个栗子,这里通过 Javassist 生成一个 User 类,其中包括 name、sex 属性,并有其 set、get 方法。并且输出到 ./out/production/classes 目录下。

ClassPool classPool = ClassPool.getDefault();

try {
    CtClass ctClass = classPool.makeClass("cn.hchstudio.User");

    CtClass CtString = classPool.get("java.lang.String");

    CtField name = new CtField(CtString, "name", ctClass);
    name.setModifiers(Modifier.PRIVATE);
    ctClass.addField(name);
    CtField sex = new CtField(CtString, "sex", ctClass);
    sex.setModifiers(Modifier.PRIVATE);
    ctClass.addField(sex);

    CtMethod setName = new CtMethod(CtClass.voidType, "setName",
            new CtClass[]{CtString}, ctClass);
    setName.setModifiers(Modifier.PUBLIC);
    setName.setBody("name = $1;");
    ctClass.addMethod(setName);
    CtMethod getName = new CtMethod(CtString, "getName",
            new CtClass[]{}, ctClass);
    getName.setModifiers(Modifier.PUBLIC);
    getName.setBody("return name;");
    ctClass.addMethod(getName);
    CtMethod setSex = CtMethod.make("public void setSex(java.lang.String sex){" +
            "this.sex = sex;" +
            "}", ctClass);
    ctClass.addMethod(setSex);
    CtMethod getSex = new CtMethod(CtString, "getSex",
            new CtClass[]{}, ctClass);
    getSex.setModifiers(Modifier.PUBLIC);
    getSex.setBody("return sex;");
    ctClass.addMethod(getSex);

    ctClass.writeFile("./out/production/classes");
} catch (Exception e) {
    System.out.println(e.toString());
    e.printStackTrace();
}

ASM

ASM 也是一个操作 Java 字节码的框架,相比于 Javassist,它更加底层、轻量级、速度也快,不过在编写代码的时候可能容易出错,它需要我们直接写 Java 字节码。

Java jdk 自带了 ASM 的依赖,在 rt.jar!/jdk/internal/org/objectweb/asm 下。

Android 环境下则需要自己导入依赖,因为 Android 去掉了 rt.jar!/jdk 包。

编辑

ASM 编辑代码则比较复杂,需要对汇编有一定了解的同学才可以。

通常的方式是我们需要用 java 写出一个想要自动生成的类,然后查看他的 class 字节码

mv = cw.visitMethod(ACC_PUBLIC, "setName", "(Ljava/lang/String;)V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitFieldInsn(PUTFIELD, "cn/hchstudio/User", "name", "Ljava/lang/String;");
mv.visitInsn(RETURN);
mv.visitMaxs(2, 2);
mv.visitEnd();

这里改出一段示例代码,意思为新建一个 setName 方法,并给 name 属性赋值。

操作 Java 字节码


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

编程珠玑(续)(修订版)

编程珠玑(续)(修订版)

【美】Jon Bentley 乔恩•本特利 / 钱丽艳、刘田 / 人民邮电出版社 / 2015-2 / CNY 35.00

历史上最伟大的计算机科学著作之一 融深邃思想、实战技术与趣味轶事于一炉的奇书 带你真正领略计算机科学之美 多年以来,当程序员们推选出最心爱的计算机图书时,《编程珠玑》总是位于前列。正如自然界里珍珠出自细沙对牡蛎的磨砺,计算机科学大师Jon Bentley以其独有的洞察力和创造力,从磨砺程序员的实际问题中凝结出一篇篇不朽的编程“珠玑”,成为世界计算机界名刊《ACM通讯》历史上最受欢......一起来看看 《编程珠玑(续)(修订版)》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

随机密码生成器
随机密码生成器

多种字符组合密码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码