首页 > 解决方案 > 当我想拦截一个方法时,我只能使用接口子类吗?

问题描述

我正在尝试使用 byte buddy 来拦截方法,但发现它仅在您子类型接口时才起作用。

下面的代码片段演示了这一点,您可以看到“hello”被传递到 3 种类型的相同“message”方法中,但只拦截了 Interface 子类型。

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 { ... }

如何拦截类的方法以及接口的方法?

谢谢

标签: javaaopbyte-buddy

解决方案


您正在定义仅在包中可见的包私有方法。您还使用了默认的类注入策略,它将创建一个新的类加载器来加载您注入的类。在运行时,两个包只有在它们具有相同的名称和类加载器时才相等,类似于类。

实际上,Byte Buddy 会为您覆盖这些方法,因为类创建不考虑目标类加载器。getDeclaredMethod您可以通过调用生成的类并调用它们来验证这一点。但是,即使它们具有相同的签名,Java 也不会实际调度此类外部包方法。要在同一个类加载器中定义类,可以使用ClassLoadingStrategy.Default.INJECTION. 但是请注意,不鼓励这种策略,因为它依赖于不安全的 API。在 Java 9+ 中,您可以ClassLoadingStrategy.UsingLookup改用什么是安全的。

当然,您也可以将您的方法更改为公开或受保护的。


推荐阅读