操作 Java 字节码

代码星冰乐

专注成就未来

首页 归档 关于

操作 Java 字节码

Oct 26, 2018 | ChanghuiN | Java | 阅读
文章目录
  1. 1. Class 文件是什么
  2. 2. Why
  3. 3. Javassist
    1. 3.1. 导包
    2. 3.2. 类搜索路径
    3. 3.3. 编辑
    4. 3.4. 一个栗子
  4. 4. ASM
    1. 4.1. 编辑

作 者:ChanghuiN
原文链接:https://www.hchstudio.cn/article/2018/e8df/
版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。


由于版权原因,请阅读原文 --> 操作 Java 字节码

关注我们

作 者:ChanghuiN
原文链接:https://www.hchstudio.cn/article/2018/e8df/
版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。

作 者:ChanghuiN
原文链接:https://www.hchstudio.cn/article/2018/e8df/
版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。

本博客主要介绍通过 Javassist、ASM 操作 Java 字节码。

Class 文件是什么

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

1
2
3
4
5
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”框架。

导包

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

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

类搜索路径

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

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

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

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

编辑

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

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

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

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

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

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

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

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

一个栗子

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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 字节码

1
2
3
4
5
6
7
8
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 属性赋值。

关注我们

作 者:ChanghuiN
原文链接:https://www.hchstudio.cn/article/2018/e8df/
版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。

分享
Java
Nginx 不停机升级 及 gzip 压缩优化安全闲扯
微信关注我们
分类
  • Android8
  • Go4
  • Java59
  • Kafka,Java1
  • Kotlin2
  • Linux1
  • MapReduce1
  • Python2
  • Raft1
  • Redis1
  • ThreadPoolExecutor1
  • go1
  • 工具1
  • 总结8
  • 旅游日记1
标签
Nginx ChanghuiN haifeiWu Android Java 设计模式 hexo Kotlin 算法 MySQL 源码解析 Python Redis golang web Kafka 配置中心 总结 性能优化 旅游日记 Shell Go 问题排查 译文 Docker Spring Boot 工具 学习笔记 WebFlux 性能测试 go 散列表 源码 netty Raft
最近文章
  • Kafka的日志复制机制
  • 从20到21
  • go 并发编程
  • 【译】了解Linux CPU负载-您何时应该担心?
  • Zookeeper 与分布式锁
  • 基于Redis的分布式锁到底安全吗?
  • 【译】Raft 学生指南
  • ThreadPoolExecutor 的简单梳理
  • MapReduce 的简单实现
  • 使用 Map 实现策略模式
福利专区
    免费SSL证书
      阿里云红包
        腾讯云专属福利
        Copyright © 2021 代码星冰乐. Powered by ChanghuiN. 版权所有 晋ICP备15001365号
        特别感谢: 云服务器服务商 、 CDN 服务商