ByteBuddy(二)— OnMethodEnter Advice

源代码包含在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配置:


    net.bytebuddy
    byte-buddy-maven-plugin
    ${bytebuddy.version}
    
      
        
          transform
        
      
    
    
      
                 
          com.wpixel.bytebuddy.chapter1.InterceptorPlugin
        
      
    

该插件需要bytebuddy maven插件。
要启用检测过程,标记必须具有transform值。
因此,ByteBuddfy检测过程的另一个名称是转换。
<transformation>标记必须具有插件程序的全路径类名
该项目使用com.wpixel.bytebuddy.chapter1.InterceptorPlugin插件。项目可以有一个或多个插件程序。

拦截器插件

InterceptorPlugin.java是执行匹配逻辑和拦截逻辑的插件程序。
InterceptorPlugin.java实现了net.bytebuddy.build.Plugin接口。
并实现接口的三个方法:matchesapplyclose

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")));
}

第一个参数buildernet.bytebuddy.dynamic.DynamicType.Builder类型。
该方法使用buildervisit方法添加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.javaclose方法的实现:

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 = "";
}

然而,在Java字节码中,这是无效的。
JVM不会为此引发运行时异常,但代码根本无效,这意味着数据实例变量将保持空值。要在Java字节码中正确初始化变量的值,必须通过构造函数进行初始化:

public class DataProducer{
    private String data;
    public DataProducer(){
        data = "";
    }
}

因此,在学习butebuddy中生成开发代码时,必须按照Java字节码语法开发java代码。
尽管如此,大多数Java编程语法仍然使用,因为ByteBuddy将相应地对其进行翻译。


bytebuddy书籍《Java Interceptor Development with ByteBuddy: Fundamental》

喜欢就点个吧

你可能感兴趣的:(ByteBuddy(二)— OnMethodEnter Advice)