当我想拦截一个方法时,我只能用接口子类化吗?
Can I only subclass with Interfaces when I want to intercept a method?
我正在尝试使用 byte buddy 来拦截方法,但发现它似乎只在您对接口进行子类型时才有效。
下面的代码片段演示了这一点,您可以看到 "hello" 被传递到 3 种类型的相同 "message" 方法,但只有接口子类型被拦截。
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.attribute.AnnotationRetention;
import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.Morph;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.This;
import net.bytebuddy.matcher.ElementMatchers;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
class Sandbox {
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
testIt(StaticClass.class).message("hello");
testIt(AbstractStaticClass.class).message("hello");
testIt(Interface.class).message("hello");
}
private static <T> T testIt(Class<T> aClass) throws IllegalAccessException, InstantiationException {
return new ByteBuddy().with(AnnotationRetention.ENABLED)
.subclass(aClass)
.method(
ElementMatchers.isAnnotatedWith(AnAnnotation.class)
).intercept(
MethodDelegation.withDefaultConfiguration()
.withBinders(Morph.Binder.install(Invoker.class))
.to(new Interceptor()))
.make()
.load(Sandbox.class.getClassLoader())
.getLoaded()
.newInstance();
}
public interface Invoker {
Object invoke(Object[] args);
}
public static class Interceptor {
@RuntimeType
public Object intercept(@This Object proxy, @Origin Method method, @Morph(defaultMethod = true) Invoker invoker, @AllArguments Object[] args) {
return invoker.invoke(new Object[]{"there"});
}
}
public static class StaticClass {
@AnAnnotation
String message(String message) {
System.out.println(getClass().getName() + ": message: " + message);
return message;
}
}
public static abstract class AbstractStaticClass {
@AnAnnotation
String message(String message) {
System.out.println(getClass().getName() + ": message: " + message);
return message;
}
}
public interface Interface {
@AnAnnotation
default String message(String message) {
System.out.println(getClass().getName() + ": message: " + message);
return message;
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
private @interface AnAnnotation {}
}
我还发现,如果我将 类 更改为相互继承,如下所示,那么对于 AbstractStaticClass 和 StaticClass,该方法根本不会执行。
public static class StaticClass extends AbstractStaticClass { ... }
public static abstract class AbstractStaticClass implements Interface { ... }
public interface Interface { ... }
如何拦截 类 的方法以及接口?
谢谢
您正在定义仅在包内可见的包私有方法。您还使用了默认的 class 注入策略,它将创建一个新的 class 加载程序来加载您注入的 class。在运行时,两个包只有在它们具有相同的名称和 class 加载程序时才相等,类似于 classes.
实际上,Byte Buddy 会为您覆盖这些方法,因为 class 创建不考虑目标 class 加载程序。您可以通过在生成的 class 上调用 getDeclaredMethod
并调用它们来验证这一点。但是,Java 不会实际分派此类包外方法,即使它们具有相同的签名。要在同一个 class 加载程序中定义 classes,您可以使用 ClassLoadingStrategy.Default.INJECTION
。但是请注意,不鼓励使用此策略,因为它依赖于不安全 API。在 Java 9+ 中,您可以使用 ClassLoadingStrategy.UsingLookup
而不是安全的。
您当然也可以将您的方法更改为 public 或受保护。
我正在尝试使用 byte buddy 来拦截方法,但发现它似乎只在您对接口进行子类型时才有效。
下面的代码片段演示了这一点,您可以看到 "hello" 被传递到 3 种类型的相同 "message" 方法,但只有接口子类型被拦截。
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.attribute.AnnotationRetention;
import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.Morph;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.This;
import net.bytebuddy.matcher.ElementMatchers;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
class Sandbox {
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
testIt(StaticClass.class).message("hello");
testIt(AbstractStaticClass.class).message("hello");
testIt(Interface.class).message("hello");
}
private static <T> T testIt(Class<T> aClass) throws IllegalAccessException, InstantiationException {
return new ByteBuddy().with(AnnotationRetention.ENABLED)
.subclass(aClass)
.method(
ElementMatchers.isAnnotatedWith(AnAnnotation.class)
).intercept(
MethodDelegation.withDefaultConfiguration()
.withBinders(Morph.Binder.install(Invoker.class))
.to(new Interceptor()))
.make()
.load(Sandbox.class.getClassLoader())
.getLoaded()
.newInstance();
}
public interface Invoker {
Object invoke(Object[] args);
}
public static class Interceptor {
@RuntimeType
public Object intercept(@This Object proxy, @Origin Method method, @Morph(defaultMethod = true) Invoker invoker, @AllArguments Object[] args) {
return invoker.invoke(new Object[]{"there"});
}
}
public static class StaticClass {
@AnAnnotation
String message(String message) {
System.out.println(getClass().getName() + ": message: " + message);
return message;
}
}
public static abstract class AbstractStaticClass {
@AnAnnotation
String message(String message) {
System.out.println(getClass().getName() + ": message: " + message);
return message;
}
}
public interface Interface {
@AnAnnotation
default String message(String message) {
System.out.println(getClass().getName() + ": message: " + message);
return message;
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
private @interface AnAnnotation {}
}
我还发现,如果我将 类 更改为相互继承,如下所示,那么对于 AbstractStaticClass 和 StaticClass,该方法根本不会执行。
public static class StaticClass extends AbstractStaticClass { ... }
public static abstract class AbstractStaticClass implements Interface { ... }
public interface Interface { ... }
如何拦截 类 的方法以及接口?
谢谢
您正在定义仅在包内可见的包私有方法。您还使用了默认的 class 注入策略,它将创建一个新的 class 加载程序来加载您注入的 class。在运行时,两个包只有在它们具有相同的名称和 class 加载程序时才相等,类似于 classes.
实际上,Byte Buddy 会为您覆盖这些方法,因为 class 创建不考虑目标 class 加载程序。您可以通过在生成的 class 上调用 getDeclaredMethod
并调用它们来验证这一点。但是,Java 不会实际分派此类包外方法,即使它们具有相同的签名。要在同一个 class 加载程序中定义 classes,您可以使用 ClassLoadingStrategy.Default.INJECTION
。但是请注意,不鼓励使用此策略,因为它依赖于不安全 API。在 Java 9+ 中,您可以使用 ClassLoadingStrategy.UsingLookup
而不是安全的。
您当然也可以将您的方法更改为 public 或受保护。