Su的技术博客

  • 首页
  • Java
  • MySQL
  • DDD
  • 事故复盘
  • 架构方案
  • Other
  • 工具
  • 打赏
  • 关于
  1. 首页
  2. Java
  3. 正文
                           

【八戒】JAVA字节码增强解密(上)

2023-02-19 59点热度 0人点赞 0条评论

前言

字节码增强:指的是在Java字节码生成之后,对其进行修改,增强其功能,这种方式相当于对应用程序的二进制文件进行修改。

为什么要进行字节码增强呢?字节码增强可以用在什么地方呢?如何对字节码进行增强?增强的手段都有哪些?一起来看看吧


做为JAVA开发,你应该无数次的听过一句话:一次编译,到处运行。没错,这是JAVA的优势,但你有没有想过这究竟是为什么呢?Why???

是因为有JVM虚拟机,以及格式固定的Class字节码文件!要运行于不同的机器,只需针对不同的硬件开发不同的JVM即可。

而本次主题字节码增强的核心则在于:因为有JVM规范的存在,只要最终可以生成符合规范的字节码就可以在JVM上运行。

而什么是字节码增强呢?字节码增强指的是在Java字节码生成之后,对其进行修改,增强其功能,这种方式相当于对应用程序的二进制文件进行修改。

为什么要进行字节码增强呢?字节码增强可以用在什么地方呢?....... 凡是了解过字节码增强技术的人都知道它的好,我们在这里也就不一一细说了,这也不是本次分解的重点;接下来我们一起来看看如何对字节码进行增强,增强的手段都有哪些。

【JVM和字节码的基本情况】

首先你需要知道JVM执行的过程,如下图:(图片来自网络)

图片

其次你也需要知道字节码文件都是长什么样的?是用什么样的结构来描述一个类和内部相关的信息?你可以用文本编辑器打开.class字节码文件,你会发现其实它是十六进制的,并且是按指定格式指定规范进行描述和解析的。你也可以使用javap -verbose命令查看可视信息(会输出整个class的完整结构,包括常量池、字段表、方法表等),更容易读懂。如下图:(图片来自网络)

图片

主要包含了三大块信息:

Code区:就是编写代码编译后的JVM指令集,字节码增强主要操作该区域

LineNumberTable:行号表,是行号对Code区指令集的关联,知道哪一行需执行哪些指令

LocalVeriableTable:本地变量表,就是局部变量的定义,它的实际存储结构也是和类的字段表是类似的

【字节码增强的技术及各自优缺点】

到了这里,你已了解了JVM和字节码的基本情况,接下来我们一起来看看字节码增强的技术都有哪些,他们各自的优缺点都是怎么样的呢?同时还奉上了简单的示例。

1、ASM字节码增强类库

ASM是一个轻量级的Java字节码操作框架,它是在虚拟机指令层面进行字节码操作,在加载和操作类文件时不需要将整个类的结构读取到内存,就可以以流式的方法来处理字节码文件。

优点:由于使用流式的加载和处理机制,因此消耗的内存较小,并且是几个字节码增强工具中速度最快的一种增强技术。

缺点:当然是编程难度较大,因为需要熟悉JVM的各种指令集,上手比较困难,不过好在ASM提供了工具以查看常规代码对应的ASM编码 - ASM ByteCode Outline(idea plugin)。

应用建议:虽然要懂得JVM的各种指令集,编程难度会较大,但比较看重性能的场景大多还是会优先选用ASM进行编程。

ASM字节码增强类库工作流程:(图片来自网络)

图片

ASM Core API主要包括的工具类

ClassReader:用于读取已经编译好的.class文件。

ClassWriter:用于重新构建编译后的类,如修改类名、属性以及方法,也可以生成新类的字节码文件。

各种Visitor类:如FieldVisitor、MethodVisitor、AnnotationVisitor分别对字节码中的属性、方法、注解进行处理。

注:另外还有一个TreeAPI,它的解析方式是以Dom的方式进行解析和操作

图片

需求:给Teacher类中的say方法进行增强,在前后输出start和end。典型的AOP

第一步:当然是得先把ASM的jar包引进项目。但如果你的项目使用了Spring则可以直接使用,因为Spring底层就有一套被重命名的ASM框架;

第二步:编写一个Teacher类,是被增强的目标类,代码如下

图片

第三步:编写ClassVisitor类 – TeacherClassVisitor,用于进行类和方法的增强

图片

第四步:编写字节码增强入口类(继承ClassLoader),调用+测试

图片

运行结果如下:

图片

编码建议:

a、先编好源码->再使用命令将字节码转为可读的结构->再按命令写ASM代码(命令:javap –c clazzName)

b、善于运用工具,不是所有的开发都是大神:ASMfier(帮你生成代码)

图片

2、CGLib动态代理

额,你可能会有点蒙逼,这里为什么要说动态代理?但如果你看过它的底层,你就会知道:其实CGlib动态代理是基于ASM实现(Spring也是如此)。

CGLib核心类:

Enhancer类:用于创建代理类的工具类,可以设置目标类以及代理回调对像。

MethodInterceptor接口:主要是实现代理的回调方法,用于拦截实现自已的业务功能。

优点:将ASM复杂的技术进行了包装,开放简单易用的工具类。因此他使用简单方便,整体速度也不慢,综合情况比较良好。

缺点:毕竟是经过了包装,所以字节码增强的灵活性有所降低,不能非常灵活的去实现代码增强,而且通常是配合反射来实现。

应用建议:简单且高效,虽然有反射但整体性能影响并不会太大,如果不追求灵活性、或者功能简单的话可以使用CGLib实现。

3、Javassist字节码增强类库

Javassist 是一个开源的分析、编辑和创建Java字节码的类库,它是基于源代码层面的字节码工具类,性能比ASM稍差。其中最为重要的几个工具类是 ClassPool,CtClass ,CtMethod 以及 CtField 这几个类。

优点:主要在于简单,直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类,而且Javassist的API和JAVA的反射API颇为相似,很容易上手。

缺点:速度是这几个当中稍差的,并且不支持少量的代码书写方式如:

不支持数组的初始化,如 String[]{"1","2"} ,除非只有数组的容量为1

不支持内部类和匿名类

应用建议:Javassist的慢是在操作字节码时慢,并不是执行字节码慢,因此如果不需要频繁操作字节码还是推荐使用的。

Javassist核心的API:

ClassPool类:是CtClass对象集合的容器,可以读取类文件来构造CtClass对象,并且保存CtClass对象以便以后使用。

注:但如果CtClass对象创建过多则可能会导致内存溢出,需要有意识的进行CtClass的释放。

CtClass类:代表Class文件对象,可以用于编辑和管理该Class

CtMethod类:是方法的对象,用于操作和管理该方法

CtField类:是字段属性的对象,用于操作和管理该字段属性。

Javassist各API类需要关注的方法:

【ClassPool需要关注的方法】:

getDefault : 返回默认的ClassPool 对象,是单例模式的,一般通过该方法创建我们的ClassPool对象;

appendClassPath | insertClassPath : 将一个ClassPath加到类搜索路径的末尾位置或插入到起始位置。通常通过该方法写入额外的类搜索路径,以解决多个类加载器环境中找不到类的问题;

makeClass | get | getCtClass :获取一个指定类名称的CtClass对象,用于后续的编辑

toClass : 将修改后的CtClass加载至当前线程的类加载器中,并返回一个Class类对象用于后续操作。需要注意的是一旦调用该方法,则无法继续修改已经被加载的这个class;

【CtClass需要关注的方法】:

getName | setName:获取或设置类名称;

setSuperclass | setInterfaces:设置父类(继承),设置实现的接口;

getDeclaredMethods | getMethod:获取定义的方法或者取指定的方法;

getDeclaredFields | getField:获取定义的属性字段或者取指定的属性字段;

addMethod | addField:添加方法或属性字段;

detach : 将该当前class从ClassPool中删除,可以在类处理结束后调用该方法,确保不会内存溢出;

writeFile : 根据CtClass生成 .class 文件;

toClass : 通过类加载器加载该CtClass。

【CtMethod中的一些重要方法】:

getName | setName:获取或设置方法名;

getReturnType | getModifiers:获取接口返回类型或者修饰符;

hasAnnotation | getAnnotations:检查或获取注解;

insertBefore : 在方法的起始位置插入代码;

insterAfter : 在方法的所有 return 语句前插入代码以确保语句能够被执行;

insertAt : 在指定的行位置插入代码;

setBody : 设置方法的主体代码内容,当方法被abstract修饰时,该修饰符被移除;make : 创建一个新的方法。

【CtField中的一些重要方法】:

getName | setName:获取或设置字段名

getModifiers | setModifiers:获取或设置字段的修饰符

hasAnnotation | getAnnotations:检查或获取注解

make : 创建一个新的字段属性。

Javassist应用示例:(还是以前面的需求为例)

图片

输出的结果将和上面的示例结果一致。


到此你已经基本学会了字节码增强技术,可以根据实际的需求选择合适的框架技术来对字节码文件进行增强了;但要如何在实际项目中使用,如如何使用增强后的字节码文件生效,还请看下回分解。

 

标签: 八戒 字节码 JVM 编译 Java
最后更新:2023-02-19

coder

分享干货文章,学习先进经验。

打赏 点赞
< 上一篇
下一篇 >
最新 热点 推荐
最新 热点 推荐
殷浩详解DDD 第四讲:领域层设计规范 既生@Resource,何生@Autowired? Go整洁架构实践 接口优化的常见方案实战总结 QQ音乐高可用架构体系 构建一个布隆过滤器 —— Building a Bloom filter
殷浩详解DDD 第四讲:领域层设计规范Redis为什么这么快?构建一个布隆过滤器 —— Building a Bloom filterQQ音乐高可用架构体系接口优化的常见方案实战总结Go整洁架构实践
笔记 | 5种网络IO模型 一次误删除MySQL主库的恢复操作 猪八戒网DevOps之Java组件安全检测 笔记08 | 搜狗面试题:IO多路复用之select、poll、epoll的区别 历经 16 年猪八戒网如何成功实现双活流量架构 殷浩详解DDD 第四讲:领域层设计规范

@Autowired (1) @Resource (1) API网关 (1) ddd (6) DP (1) ElasticSearch (1) eureka (7) go (1) HTTP (1) IDEA (1) iOS (1) Java (8) JSR (1) QQ音乐 (1) repository (1) Spring (1) SQL优化 (1) 代理 (1) 依赖注入 (1) 同城双活 (1) 垃圾回收 (1) 定时任务 (1) 容灾 (1) 布隆过滤器 (1) 异地双活 (1) 接口优化 (1) 故障转移 (1) 数据库 (2) 整洁架构 (1) 文件网关 (1) 方案 (2) 服务续约 (1) 注册中心 (7) 流水账 (1) 流量 (1) 第五 (1) 线上案例 (1) 线上问题 (2) 缓存 (1) 缓存击穿 (1) 编译 (3) 网络 (3) 聊聊 (1) 订单 (1) 设计规范 (1) 详解 (1) 连接池 (1) 限流 (1) 领域驱动设计 (4) 高可用 (1)

COPYRIGHT © 2014-2023 verysu.com . ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

粤ICP备15033072号-2