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配置:

<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----

喜欢就点个👍吧

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容