在Byte Buddy中动态设置@FieldProxy字段

时间:2019-05-11 14:37:27

标签: java byte-buddy

我必须实现一个拦截器,该拦截器可用于动态指定的字段,而与字段名称无关。

在这里评论答案 https://stackoverflow.com/a/35113359/11390192 我读过

  

您真的可以只在@This对象上使用反射。只要你   缓存Field实例,这与性能无关。

但是我怀疑下面的拦截器实现是有效的(如果我理解正确的注释)。

public static class DynamicFieldInterceptor {
    private final String fieldName;

    public DynamicFieldInterceptor(String fieldName) {
        this.fieldName = fieldName;
    }

    public void intercept(@This Object thiz) throws NoSuchFieldException, IllegalAccessException {
        Field field = thiz.getClass().getDeclaredField(fieldName);
        boolean oldAccessible = field.isAccessible();
        field.setAccessible(true);
        Long fieldValue = (Long)field.get(thiz);
        field.set(thiz, fieldValue + 1L);       // !< Instead of my business logic
        field.setAccessible(oldAccessible);
    }
}

我还尝试了以下想法:使用@FieldProxy参数上的不同注释为每个字段生成拦截器类。比将生成的类用作目标类的拦截器。

public interface Metaclass {
    void intercept(GetterAndSetter field);
}

public static class MetaclassInterceptor implements Metaclass{
    @Override
    public void intercept(GetterAndSetter field) {
        field.set((Long)field.get() + 1L);
    }
}

public static Class<?> annotateInterceptorClass(final String annotation)
        throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    return new ByteBuddy()
            .subclass(MetaclassInterceptor.class)
            .topLevelType()
            .name("ClassForIntercepting_" + annotation + "_Field")
            .modifiers(Visibility.PUBLIC, Ownership.STATIC)

            .defineMethod("intercept", void.class, Visibility.PUBLIC)
            .withParameter(GetterAndSetter.class, "intercept")
            .annotateParameter(AnnotationDescription.Builder.ofType(FieldProxy.class)
                    .define("value", annotation).build())

            .intercept(SuperMethodCall.INSTANCE)
            .make()
            .load(MetaclassInterceptor.class.getClassLoader())
            .getLoaded();
}

该类似乎生成良好。生成的类中的方法存在,并且使用预期的注释对参数进行注释。

但是,当我尝试使用生成的类作为拦截器时,却出现了异常。

Class<?> klass = new ByteBuddy()
            .subclass(Object.class)

            .defineProperty("index0", Long.class, false)
            .defineProperty("index1", Long.class, false)

            .defineMethod("doSomeActions", void.class, Visibility.PUBLIC)
            .intercept(
                    MethodDelegation
                            .withDefaultConfiguration()
                            .withBinders(FieldProxy.Binder.install(GetterAndSetter.class))

                            // Use dynamically generated interceptor, see abode
                            .to(annotateInterceptor("index0"))
                    .andThen(
                            MethodDelegation
                                    .withDefaultConfiguration()
                                    .withBinders(FieldProxy.Binder.install(GetterAndSetter.class))

                                    // Use dynamically generated interceptor, see abode
                                    .to(annotateInterceptor("index1"))
                    )

            )
            .make()
            .load(MetaclassInterceptor.class.getClassLoader())
            .getLoaded();

Exception in thread "main" java.lang.NoClassDefFoundError: LClassForIntercepting_index0_Field;
    at java.base/java.lang.Class.getDeclaredFields0(Native Method)
    at java.base/java.lang.Class.privateGetDeclaredFields(Class.java:3062)
    at java.base/java.lang.Class.getDeclaredField(Class.java:2410)
    at net.bytebuddy.implementation.LoadedTypeInitializer$ForStaticField.onLoad(LoadedTypeInitializer.java:120)
    at net.bytebuddy.implementation.LoadedTypeInitializer$Compound.onLoad(LoadedTypeInitializer.java:187)
    at net.bytebuddy.dynamic.TypeResolutionStrategy$Passive.initialize(TypeResolutionStrategy.java:102)
    at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:5662)
    at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:5651)
    at MainClass4.main(MainClass4.java:107)

即使我成功实现了拦截器的动态实现,我也肯定这不是完美的方法。我认为必须有可能以更简单的方式实现它。确实,如果未指定注释中的字段名称,则@FieldProxy批注可以从显式指定的名称和bean属性中获取字段,因此我认为这是将其映射到任何其他字段的技术机会。

1 个答案:

答案 0 :(得分:0)

使用load(MetaclassInterceptor.class.getClassLoader())加载类时,正在创建一个新的类加载器,除非重用它,否则其他加载器上的任何其他类都不可见。

您可以:

a)合并由DynamicType步骤创建的两个make,并将它们一起加载。这样,它们将最终位于同一类加载器中。

b)获取第一个生成的类的类加载器,并将其转换为InjectionClassLoader。您还需要指定ClassLoadingStrategy.WRAPPER.opened()并将其与InjectionClassLoader.Strategy.INSTANCE一起使用。请注意,这将允许任何引用您所生成类的实例的人在同一包中定义类。

c)使用ClassLoadingStrategy.Default.INJECTION在原始类加载器中定义类而不创建包装。并不是说这种策略依赖于Unsafe API。