源代码包含在byteBuddy IDEA项目中。
这是项目结构

有三个类:拦截器、插件包、程序包
拦截器
包含Advice代码LogInterceptor.java
插件包
包含提供拦截逻辑的插件程序。
在这个例子中InterceptorPlugin.java是插件程序。
Maven构建过程使用插件程序生成插入代码。
程序包
包含函数代码DataProducer.java和一个java程序Main1.java,用于执行检测代码,可用于测试和查看检测结果。
target文件夹
存储maven构建过程的结果,包括Java类文件、插入代码和jar包。
该项目添加了Advice代码以创建DataProducer.class的方法。
Advice代码应用@OnMethodEnter注解。
这种类型的Advice将在Java方法执行开始时注入Advice代码:
源码文件:DataProducer.java
业务代码
public void create(){
System.out.println("create data");
}
maven编译后的文件:target/DataProducer.class
添加非业务代码
public void create(){
LogInterceptor.logger.info("Method start");
System.out.println("create data");
}
插件程序将转换Java字节码(Java类文件),而不是Java源代码。因此,在检测过程之后,Java源代码保持不变,但Java类文件的内容将发生变化,类文件包含Advice代码和功能代码。上面的解释使用Java源代码来显示插入指令的代码,以便于理解。
运行
该项目执行maven build来生成代码。
要启动maven构建,请使用mvn clean package -X命令执行项目
maven构建过程完成后,执行Main1.java:
public static void main(String[] args) {
new DataProducer().create();
}
20:13:20.331 [main] INFO com.wpixel.bytebuddy.chapter1.LogInterceptor - Method start
create DataProducer
Process finished with exit code 0
Maven构建过程
这些流程的主要内置流程:
1.Maven清除项目target文件夹。
2.Maven编译器将java源代码保存在项目中,并将Java类文件存储在项目target文件夹中。
3.通过调用InterceptorPlugin.java,Maven执行ByteBuddy指令处理。
4.插件程序可以在项目target文件夹中逐个保存所有Java类文件。
5.对于每个Java类文件,插件程序调用matches方法来查找用于拦截的函数代码。
6.如果找到,则匹配结果变为真,并且插件程序继续执行应用程序方法。如果未找到,则插件程序重复步骤5的过程。
7.如果不适用,InterceptorPlugin.java不会将Advice代码应用于功能代码。
8.InterceptorPlugin.java以字节码(java类文件)格式创建插入指令的代码,并将代码存储在项目target文件夹中。
9.Maven重复步骤4至步骤8,直到InterceptorPlugin.java检查项目中的所有类文件。
10.Maven创建了一个jar文件,它打包了所有Java类文件,包括插入的代码
这是pom.xml中的ByteBuddy配置:
<plugin>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-maven-plugin</artifactId>
<version>${bytebuddy.version}</version>
<executions>
<execution>
<goals>
<goal>transform</goal>
</goals>
</execution>
</executions>
<configuration>
<transformations>
<transformation>
<plugin>com.wpixel.bytebuddy.chapter1.InterceptorPlugin</plugin>
</transformation>
</transformations>
</configuration>
</plugin>
该插件需要bytebuddy maven插件。
要启用检测过程,<goal>标记必须具有transform值。
因此,ByteBuddfy检测过程的另一个名称是转换。
<transformation>标记必须具有插件程序的全路径类名。
该项目使用com.wpixel.bytebuddy.chapter1.InterceptorPlugin插件。项目可以有一个或多个插件程序。
拦截器插件
InterceptorPlugin.java是执行匹配逻辑和拦截逻辑的插件程序。
InterceptorPlugin.java实现了net.bytebuddy.build.Plugin接口。
并实现接口的三个方法:matches、apply、close
public class InterceptorPlugin implements Plugin{
@Override
public boolean matches(TypeDescription target){
//codeisomitted
}
@Override
public Builder<?> apply(Builder<?> builder,
TypeDescriptiontype Description,
ClassFileLocator classFileLocator){
/*codeisomitted*/
}
@Override
public void close() throws IOException{
/*codeisomitted*/
}
}
注:matches匹配Java类, apply匹配Java类里的内容(方法,字段等)
matches方法
插件程序将检查项目target文件夹中的所有Java类文件。
然后调用matches方法来执行第一级匹配逻辑。
1. public boolean matches(TypeDescription target){
2. System.out.println("Inspecting" + target.getName());
3. if(target.getName().equals(DataProducer.class.getName())){
4. System.out.println("Found target code : " + target.getName());
5. return true;
6. } else {
7. System.out.println("Inspected code" + target.getName() + "is not the target code");
8. return false;
9. }
10. }
第3行匹配逻辑希望找到DataProducer类文件。
逻辑将当前java类的名称与DataProducer进行比较。
当类名匹配时,该方法返回true,否则该方法打印Inspected code xxxx is not the target code,返回false。只有当matches方法返回true时,程序才会继续执行。
apply方法
apply方法提供拦截逻辑。
拦截逻辑是根据第二级匹配逻辑的状态将Advice代码添加到功能代码中。
第二级匹配逻辑进一步过滤matches方法中找到的Java类文件。
第二级匹配主要是要找到的Java类文件的方法名、字段名、注释和其他java元素。
在ByteBuddy中,大多数匹配逻辑都是使用net.bytebuddy.matcher.ElementMatchers创建的。
这是InterceptorPlugin.java的apply方法的实现
public Builder<?> apply(Builder<?> builder, TypeDescription typeDescription, ClassFileLocator classFileLocator){
return builder.visit(Advice.
to(LogInterceptor.class).
on(ElementMatchers.named("create")));
}
第一个参数builder是net.bytebuddy.dynamic.DynamicType.Builder类型。
该方法使用builder的visit方法添加Advice代码。
方法将采用Java字节码格式创建Advice代码,然后将字节码加入到功能代码的字节码中。
在visit方法括号内,使用Advice构造配置。
Advice.to方法指定LogInterceptor.class作为参数。
LogInterceptor.class是提供Advice代码的Java类。
然后用on方法指定第二级匹配逻辑。
on方法使用ElementMatchers来指定匹配条件。
该标准使用命名方法来匹配DataProducer.class中的create方法。
bytebuddy只会在第二级匹配条件返回true时嵌入代码。
命名方法根据Java元素的确切名称查找匹配。
Java元素可以是方法、字段、泛型类型等。
close方法
close方法是Plugin接口的派生方法之一。
在检查了项目target文件夹中的所有Java类文件后,将只调用close方法一次。
因此,close方法适合用于关闭在匹配和应用方法中创建的任何资源。
InterceptorPlugin.java的close方法的实现:
public void close() throws IOException{
System.out.println("InterceptorPlugin close method");
}
该方法会在屏幕上打印一条消息"InterceptorPlugin close method"。
插件程序生成的所有跟踪都可以通过Console查看
Advice Code
使用方法注入Advice代码。
这是LogInterceptor.java中Advice代码的实现
public class LogInterceptor{
public static Logger logger = Logger.getLogger(LogInterceptor.class.getName());
@OnMethodEnter
public static void start() {
logger.info("Method start");
}
}
在LogInterceptor.java中,start方法是提供注入逻辑的方法。
start方法非常简单,该方法只创建一个日志消息,表示已启动检测方法。
@OnMethodEnter注释必须在此方法上进行注释,以便ByteBuddy知道提供注入逻辑的代码片段。此外注入的方法必须是静态的否则检测过程将无效。
因此,ByteBuddy通过此过程将函数代码转换为指令代码:
(1) 使用@OnMethodEnter注释查找静态方法:
@OnMethodEnter
public static void start() {
logger.info("Method start");
}
(2) 将方法内容转换为对Java执行有效的字节码。
在这种情况下,这将是:
From
logger.log("Method start")
To
LogInterceptor.logger.info("Method start");
(3) 之后,ByteBuddy将生成的字节码添加到DataProducer.class里的create方法中,因此,插入代码的结果包含非功能代码和功能代码
public void create(){
LogInterceptor.logger.log(Level.INFO, "Method start");
System.out.println("create data");
}
请注意ByteBuddy只处理Java类格式的Java字节码。
这意味着DataProducer.java保持不变,但DataProducer.class将发生变化。
Java编程和Java字节码编码之间的区别
java编程不同于Java字节码编程,因为java字节码编程按照java类语法进行编程,而Java编程使用Java语言语法。
例如,java编程允许对实例值进行这种类的初始化变量:
public class DataProducer{
private String data = "<noData>";
}
然而,在Java字节码中,这是无效的。
JVM不会为此引发运行时异常,但代码根本无效,这意味着数据实例变量将保持空值。要在Java字节码中正确初始化变量的值,必须通过构造函数进行初始化:
public class DataProducer{
private String data;
public DataProducer(){
data = "<noData>";
}
}
因此,在学习butebuddy中生成开发代码时,必须按照Java字节码语法开发java代码。
尽管如此,大多数Java编程语法仍然使用,因为ByteBuddy将相应地对其进行翻译。
bytebuddy书籍《Java Interceptor Development with ByteBuddy: Fundamental》
喜欢就点个👍吧