源代码包含在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》
喜欢就点个👍吧