使用ByteBuddy拦截未实现接口方法无法获取签名与参数问题咨询
ByteBuddy 接口方法拦截问题解决方案
问题原因解答
1. 未实现接口方法能否获取方法签名?
可以获取,你当前拿不到签名是因为匹配到的Intercept C方法仅注入了@DefaultMethod和@SuperMethod,这两个参数对于未实现的接口抽象方法来说都是null,且该方法没有添加@Origin注解注入被调用的Method对象,所以看不到方法信息。
2. 为什么没有触发Intercept D回调?
ByteBuddy的MethodDelegation默认会优先匹配参数数量更少、类型匹配度更高的重载方法,你没有指定绑定优先级的前提下,无额外入参的Intercept C优先级高于需要匹配入参的Intercept D,所以会被优先选中。
正确实现方案
你需要的代理能力可以通过以下调整实现:
第一步:调整代理类生成逻辑
新增存储属性的字段,调整方法匹配规则,优化委托配置:
@Test public void testMethod() throws InstantiationException, IllegalAccessException, NoSuchFieldException { final Class<?> proxy = new ByteBuddy().subclass(Object.class) .implement(Person.class) // 新增私有字段存储自定义属性,实现set/get的状态存储 .defineField("attributes", Map.class, Visibility.PRIVATE) // 构造方法初始化属性存储Map .constructor(ElementMatchers.any()) .intercept(SuperMethodCall.INSTANCE.andThen( FieldAccessor.ofField("attributes").setsValue(new HashMap<>()) )) // 拦截所有非构造、非finalizer的方法 .method(ElementMatchers.not(ElementMatchers.isConstructor()) .and(ElementMatchers.not(ElementMatchers.named("finalize")))) // 委托给拦截器,开启方法绑定缓存提升性能 .intercept(MethodDelegation.to(Delegator.class) .withCacheForMethodDelegation()) .make() .load(TestClass.class.getClassLoader()) .getLoaded(); final Person theInstance = (Person) proxy.newInstance(); theInstance.setFirstName("ABC"); System.out.println(theInstance.getFirstName()); // 输出ABC }
第二步:编写统一通用拦截器
不需要写多个重载方法避免绑定混乱,单个方法即可拿到所有需要的上下文:
public static class Delegator { // 指定最高绑定优先级,确保不会匹配到其他方法 @BindingPriority(Integer.MAX_VALUE) @RuntimeType public static Object intercept( // 代理实例 @This Object proxy, // 被调用的方法签名 @Origin Method method, // 方法入参数组 @AllArguments Object[] args, // 父类方法调用入口,不存在则为null @SuperCall(nullIfImpossible = true) Callable<?> superCall, // 接口默认方法调用入口,不存在则为null @DefaultCall(nullIfImpossible = true) Callable<?> defaultCall, // 代理类的属性存储字段 @FieldValue("attributes") Map<String, Object> attributes ) throws Throwable { // 1. 执行自定义前置逻辑 System.out.printf("拦截到方法:%s,入参:%s%n", method.getName(), Arrays.toString(args)); // 2. 优先调用原方法逻辑(如果存在) if (superCall != null) { return superCall.call(); } if (defaultCall != null) { return defaultCall.call(); } // 3. 未实现的抽象方法自定义处理 String methodName = method.getName(); if (methodName.startsWith("set") && args.length == 1) { String attrName = Introspector.decapitalize(methodName.substring(3)); attributes.put(attrName, args[0]); return null; } else if (methodName.startsWith("get")) { String attrName = Introspector.decapitalize(methodName.substring(3)); return attributes.get(attrName); } // 其他方法自定义返回值 return null; } }
注意事项
- 不要为拦截器编写多个同名重载方法,很容易导致绑定结果不符合预期,优先用单通用方法加nullable参数处理不同场景
- 调用原方法优先用
@SuperCall/@DefaultCall的Callable,不要用反射调用Method对象,性能差距非常大 - 如果需要给不同方法配置不同拦截逻辑,可以通过方法匹配时分多个intercept配置段实现
内容的提问来源于stack exchange,提问作者Novoj
相关产品推荐
相关产品推荐

