美文网首页
ByteBuddy(二)— OnMethodEnter Advi

ByteBuddy(二)— OnMethodEnter Advi

作者: Wpixel | 来源:发表于2022-10-25 11:32 被阅读0次

    源代码包含在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接口。
    并实现接口的三个方法: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 = "<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》

    ----END----

    喜欢就点个👍吧

    相关文章

      网友评论

          本文标题:ByteBuddy(二)— OnMethodEnter Advi

          本文链接:https://www.haomeiwen.com/subject/rsemzrtx.html