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
),
Target
、Source
间根据方法名、方法参数列表、返回值
去进行匹配。
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 |
|
@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系列(一、二、… 十四)