当前位置: 首页 > news >正文

Byte-Buddy系列 - 第2讲 方法拦截与委托实现

目录

    • 一、拦截方法及其优先级
    • 二、方法委托调用
    • 三、方法委托调用支持的注解

一、拦截方法及其优先级

class Foo {
  public String bar() { return null; }
  public String foo() { return null; }
  public String foo(Object o) { return null; }
}
 
Foo dynamicFoo = new ByteBuddy()
  .subclass(Foo.class)
  .method(isDeclaredBy(Foo.class)).intercept(FixedValue.value("One!"))
  .method(named("foo")).intercept(FixedValue.value("Two!"))
  .method(named("foo").and(takesArguments(1))).intercept(FixedValue.value("Three!"))
  .make()
  .load(getClass().getClassLoader())
  .getLoaded()
  .newInstance();

在上面的例子中,我们为重写方法定义了三个不同的规则:

  • 第一条规则涉及Foo类中定义的任何方法,即示例类中的所有三个方法。
  • 第二个规则匹配两个名为foo的方法,foo是前一个选择的子集。
  • 最后一个规则只匹配foo(Object)方法,这是对前一个选择的进一步简化。

但是考虑到这种选择重叠,Byte Buddy如何决定将哪个规则应用于哪个方法呢?
Byte Buddy以堆栈形式组织重写方法的规则。这意味着每当你注册一个新规则来覆盖一个方法时,都会被推到这个堆栈的顶部,并且总是首先应用(Last Win),直到添加了一个新的规则,这个规则的优先级更高。

对于上面的例子,这意味着:

  • bar()方法首先对named("foo").and(takesArguments(1))进行匹配,然后对named("foo")进行匹配,其中两次匹配尝试都是否定的。最后,isDeclaredBy(Foo.class匹配器允许重写bar()方法以返回"One!"
  • 类似地,foo()方法首先与named("foo").and(takesArguments(1))进行匹配,其中缺少的参数导致匹配不成功。在此之后,named("foo")匹配器确定一个正匹配,以便重写foo()方法以返回"Two!"
  • foo(Object)立即被named("foo").and(takesArguments(1))匹配器匹配,这样重写的实现返回"Three!"

由于这种组织方式,您应该总是最后注册更具体的方法匹配器。否则,之后注册的任何不太具体的方法匹配器可能会阻止应用之前定义的规则。注意,ByteBuddy配置允许定义一个ignoreMethod属性。与此方法匹配器成功匹配的方法永远不会被覆盖。默认情况下,Byte Buddy不覆盖任何合成方法。

二、方法委托调用

class Source {
  public String hello(String name) { return null; }
}
 
class Target {
  public static String hello(String name) {
    return "Hello " + name + "!";
  }
}
 
String helloWorld = new ByteBuddy()
  .subclass(Source.class)
  .method(named("hello")).intercept(MethodDelegation.to(Target.class))
  .make()
  .load(getClass().getClassLoader())
  .getLoaded()
  .newInstance()
  .hello("World");

如上代码将Source.hello方法委托为Target.hello方法(Target中的方法需定义为static),
TargetSource间根据方法名、方法参数列表、返回值去进行匹配。


Target中的方法名也可以不一样,则会自动根据方法参数列表、返回值,如下Target定义效果相同:

class Target {
  public static String intercept(String name) {
    return "Hello " + name + "!";
  }
}

对于如下Target定义,更具体的参数类型(相较于Object)会优先生效,使用如下Target定义去代理Source,最终生效的是String intercept(String name)方法:

class Target {
  public static String intercept(String name) { return "Hello " + name + "!"; }
  public static String intercept(int i) { return Integer.toString(i); }
  public static String intercept(Object o) { return o.toString(); }
}

三、方法委托调用支持的注解

如上方法委托方式中,支持注解汇总如下表:

注解说明
@Argument(int)按源方法参数列表顺序提取并设置参数(从0开始),
例如:void foo(@Argument(0) Object o1, @Argument(1) Object o2)
@AllArguments从源方法中提取全部参数,
例如:void foo(@AllArgument Object[] params)
@This注入当前动态生成的对象实例,
允许在拦截器中调用当前实例的方法,
使用 @This 注解的一个典型原因是访问实例的字段。
@Super注入原来被覆盖的父类对象实例,
允许在拦截器中调用父类的方法,
可用于调用被重写的实现。
@SuperCall标注Callable、Runnable参数上,用于调用原实现,
例如:void foo(@SuperCall Callable<List<String> callable)
其中Callable的模版参数List<String>表示原方法返回值类型。
@DefaultCall类似@SuperCall,用于调用interface中的default方法实现。
@Default类似@Super,用于调用interface中的default方法实现。
@RuntimeType宽松的类型匹配(避免严格的类型匹配),
标注在方法上表示放宽返回类型检查,
标注在参数上表示放宽此参数类型检查。
@FieldValue将当前动态生成对象实例的field(属性值)注入到指定参数
@Origin
  • @Origin 注解的参数必须用于以下类型之一:MethodConstructorExecutableClassMethodHandleMethodTypeStringint
  • 根据参数的类型,它会被分配一个对原始方法或构造函数的 MethodConstructor 引用,或者一个对动态创建的 Class 的引用。在使用 Java 8 时,还可以通过在拦截器中使用 Executable 类型来接收方法或构造函数引用。
  • 如果注解参数是 String 类型,则该参数会被分配 MethodtoString 方法返回的值。一般来说,我们建议尽可能使用这些 String 值作为方法标识符,并不鼓励使用 Method 对象,因为它们的查找会引入显著的运行时开销。
  • 为了避免这种开销,@Origin 注解还提供了一个属性用于缓存这些实例以供重用。请注意,MethodHandleMethodType 存储在类的常量池中,因此使用这些常量的类至少必须是 Java 7 版本。
  • 为了避免使用反射在另一个对象上反射性地调用拦截的方法,我们进一步建议使用 @Pipe 注解。
  • 当在 int 类型的参数上使用 @Origin 注解时,它会被分配被拦截方法的修饰符。

@SuperCall示例:

class MemoryDatabase {
  public List<String> load(String info) {
    return Arrays.asList(info + ": foo", info + ": bar");
  }
}
 
class LoggerInterceptor {
  public static List<String> log(@SuperCall Callable<List<String>> zuper)
      throws Exception {
    System.out.println("Calling database");
    try {
      return zuper.call();
    } finally {
      System.out.println("Returned from database");
    }
  }
}
 
MemoryDatabase loggingDatabase = new ByteBuddy()
  .subclass(MemoryDatabase.class)
  .method(named("load")).intercept(MethodDelegation.to(LoggerInterceptor.class))
  .make()
  .load(getClass().getClassLoader())
  .getLoaded()
  .newInstance();

@Super示例:

class ChangingLoggerInterceptor {
  public static List<String> log(String info, @Super MemoryDatabase zuper) {
    System.out.println("Calling database");
    try {
      return zuper.load(info + " (logged access)");
    } finally {
      System.out.println("Returned from database");
    }
  }
}

@RuntimeType示例:

class Loop {
  public String loop(String value) { return value; }
  public int loop(int value) { return value; }
}

//使用此Interceptor会同时匹配Loop中的2个方法
class Interceptor {
  @RuntimeType
  public static Object intercept(@RuntimeType Object value) {
    System.out.println("Invoked method with: " + value);
    return value;
  }
}

@FieldValue示例:

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.implementation.bind.annotation.FieldValue;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatchers;

public class FieldValueExample {
    public static void main(String[] args) throws Exception {
        Class<?> dynamicType = new ByteBuddy()
            .subclass(Person.class)
            .method(ElementMatchers.named("printName"))
            .intercept(MethodDelegation.to(PersonInterceptor.class))
            .make()
            .load(FieldValueExample.class.getClassLoader())
            .getLoaded();

        Person person = (Person) dynamicType.getDeclaredConstructor().newInstance();
        person.setName("John Doe");
        person.printName();
    }

    public static class Person {
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public void printName() {
            System.out.println("Name: " + name);
        }
    }

    public static class PersonInterceptor {
        public static void intercept(@FieldValue("name") String name) {
            System.out.println("Intercepted name: " + name);
        }
    }
}

参考:
https://bytebuddy.net/#
Byte Buddy 教程 - https://notes.diguage.com/byte-buddy-tutorial/
Byte Buddy官方教程 - 中文翻译专栏
https://github.com/raphw/byte-buddy
https://www.baeldung.com/byte-buddy
简书Wpixel - ByteBuddy系列(一、二、… 十四)

相关文章:

  • 1ll C++
  • STM32F407使用ESP8266连接阿里云并上传数据
  • MySQL【8.0.41版】安装详细教程--无需手动配置环境
  • 热Key问题及其解决方案:Redis高并发场景下的性能优化
  • 因子分析学习介绍,及其高阶应用以及学术上创新方向,鲁棒因子分析建模
  • [ACM_3] n组数据 | getchar() | getline(cin,s)
  • 代码重构学习
  • 塔能物联运维:成功打破物联网设备之间的互联互通难题
  • 蓝桥杯备赛学习笔记:高频考点与真题预测(C++/Java/python版)
  • Java 中序列化和反序列化
  • C语言【模仿strcpy】
  • kkFileView的安装和使用
  • fabric test-network启动
  • RAI Toolbox详解
  • 同一个网段下内网机器通过转发网络到堡垒机来访问外网的方式
  • Java进阶版线程池(超详细 )
  • TorchServe部署模型-index_to_name.json
  • 硕日新能SRNE Solar 荣获 TÜV NORD 目击实验室认可资质!
  • FRP驱动本地摄像头实现远程图传
  • js异步机制
  • 农业网站建设/线上销售渠道有哪些
  • wordpress仪表盘访问不了/搜索引擎营销与seo优化
  • 化学试剂网站建设/杭州市优化服务
  • 品牌型网站设计/企业seo的措施有哪些
  • 湖北省建设厅官方网站电话/百度搜索app
  • 网站推送/优化神马排名软件