概述
字节码相关技术,作为Java程序员来说,是个既熟悉又陌生的词,熟悉的是几乎我们无时无刻不再使用着字节码相关技术,陌生则是因为对于开发人员尤其是业务开发来说,实在是没有机会或需求要用到字节码相关的技术手段。
在Java中一般是用javac命令编译源代码为字节码文件,一个.java文件从编译到运行的示例如图所示:
那么从广义上来讲,所有让代码具备原本不具有的功能,那种类似魔法效果的技术,都统称为字节码增强。
之前写了几篇文章介绍了各种字节码相关工具,为此我们对不同工具的用途与使用时机进行一次全面的梳理。
搞懂Java三种代理模式:静态代理、动态代理和cglib代理
深入探析切面编程:解密 AspectJ 与 Spring AOP 的 AOP 魔法
什么是 Java Agent,以及如何使用它们进行性能分析?
编译过程分类
从字节码生命周期看,除了字节码class文件外,还包括从源代码生成字节码的过程,以及字节码进入运行时后classloader加载前,以及加载后的运行。
-
加载后:AspectJ、Spring AOP 。
部分技术会在多个阶段都存在。下面对这些我们再归纳下:
-
apt
如图所示,Java 源代码的编译过程可分为三个步骤:
- 将源文件解析为抽象语法树;
- 调用已注册的注解处理器;
- 生成字节码。
如果在第 2 步调用注解处理器过程中生成了新的源文件,那么编译器将重复第 1、2 步,解析并且处理新生成的源文件。每次重复我们称之为一轮(Round)。也就是说,第一轮解析、处理的是输入至编译器中的已有源文件。如果注解处理器生成了新的源文件,则开始第二轮、第三轮,解析并且处理这些新生成的源文件。当注解处理器不再生成新的源文件,编译进入最后一轮,并最终进入生成字节码的第 3 步。
-
aspectj
ApectJ主要采用的是编译期织入,在这个期间使用AspectJ的acj编译器(类似javac)把aspect类编译成class字节码后,在java目标类编译时织入,即先编译aspect类再编译目标类。 -
asm、JavaAssist、CGLib、ByteBuddy
从狭义来讲,字节码增强讲的就是对已经是字节码的class文件进行操作,那么主要有两种工具,一个是ASM,另一个是Javassit.
- ASM是纯粹的对字节码按照java的规范就行字节码理解范畴内的进行修改操作,可以说门槛很高;
- Javassit则可以理解为是一个提供了对字节码操作API的框架,来简化字节码操作的门槛,让字节码操作像面向对象编程一样简单;因此,ASM要比Javassit性能要好
- 在ASM基础上又衍生出了CGLib/ByteBuddy等技术,虽然功能没有ASM强悍,但使用相对简单了很多。
-
类加载技术
Classloader在加载类的时候会进行校验,对于已经加载过的类是不允许重复加载的。那如果字节码增强技术只能用在加载之前的范围,那可以使用的范围就特别有限了,好在Java提供了Instrument技术,也称agent技术,让这些字节码工具能够在类加载后也能进行修改。
JVM TI(Java Virtual Machine Tool Interface)可以支持类的热加载。JVM TI是一组用于与 Java 虚拟机交互的原生接口,它提供了强大的工具支持,可以用于监控、分析和操作 Java 应用程序。通过JVM TI,我们可以在运行时动态修改已加载类的字节码,从而实现类的热加载效果。
总结
他们的关系如下:
优缺点如下:
| 字节码工具 | 描述 | 优点 | 缺点 |
|---|---|---|---|
| Java-proxy | 在运行时生成一个代理类,这个代理类实现了指定的接口,并在其中添加了额外的逻辑,如方法调用前后的处理。 | - 简单易用 - 原生支持 | - 仅能代理接口或继承类 - 动态代理类需实现接口 |
| ASM | 一种通用Java字节码操作和分析框架。它可以用于修改现有的class文件或动态生成class文件。 | - 强大的字节码操作能力 - 高性能 | - 学习曲线较陡 - 代码较复杂 |
| AspectJ | 功能强大的切面编程框架,支持在编译时和运行时织入切面逻辑。它通过Java语言的扩展来支持切面的声明式定义,引入了新的关键字和语法元素。 | - 强大的 AOP 支持 - 高度集成 | - 学习曲线较陡 - 需要特定编译器或处理器 |
| JavaAssist | 用于在运行时操作字节码的 Java 库,以 Java 代码的方式来操作字节码,而不需要直接操作复杂的字节码指令。这使得动态代码生成和修改变得更加容易和可维护 | - 提供高级别的 API - 简化字节码操作 - 相对较简单 | - 性能相对较低 |
| CGLib | 是一个开源的第三方库,用于在Java运行时生成字节码并创建代理类。与Java-proxy不同,CGLIB代理可以代理普通类,即使它们没有实现任何接口。CGLIB使用ASM库来生成字节码,并通过继承的方式创建代理类,因此也被称为子类代理。 | - 可代理普通类 - 高性能 | - 无法代理 final 类 - 生成代理类较大 - 已不维护 |
| ByteBuddy | 它提供了一个简洁且强大的 API,使开发人员能够在不修改源代码的情况下,实现对类的定制和增强。无论是创建动态代理、生成新的类、修改方法体,还是在方法调用前后插入自定义逻辑,ByteBuddy 都能胜任。 | - 简洁的 API - 高性能 - 功能全面 - 支持运行时生成和修改类 | - 学习成本较高 |