【问题标题】:Adding Java Annotations at Runtime在运行时添加 Java 注释
【发布时间】:2010-12-10 17:50:59
【问题描述】:

是否可以在运行时向对象(在我的例子中特别是方法)添加注释?

更多解释:我有两个模块,模块A 和模块B。 moduleB 依赖于 moduleA,它不依赖于任何东西。 (modA 是我的核心数据类型和接口等,modB 是数据库/数据层) modB 也依赖于 externalLibrary。在我的例子中,modB 将一个类从 modA 移交给 externalLibrary,这需要对某些方法进行注释。具体注释都是 externalLib 的一部分,正如我所说,modA 不依赖于 externalLib,我想保持这种方式。

那么,这可能吗,或者您对其他方式来看待这个问题有什么建议?

【问题讨论】:

标签: java annotations runtime


【解决方案1】:

可以通过字节码检测库,例如Javassist

特别是,请查看AnnotationsAttribute 类以获取有关如何创建/设置注释的示例,并查看tutorial section on bytecode API 以获取有关如何操作类文件的一般指南。

不过,这绝不是简单明了的方法-我不推荐这种方法,而是建议您考虑 Tom 的答案,除非您需要为大量类执行此操作(或者说类在运行时才可用因此编写适配器是不可能的)。

【讨论】:

    【解决方案2】:

    还可以在运行时使用 Java 反射 API 向 Java 类添加注解。本质上,必须重新创建在类java.lang.Class 中定义的内部注释映射(或者对于在内部类java.lang.Class.AnnotationData 中定义的Java 8)。自然地,这种方法是相当老套的,并且可能在任何时候因为较新的 Java 版本而中断。但是对于快速而肮脏的测试/原型设计,这种方法有时会很有用。

    Java 8 的概念证明示例:

    public final class RuntimeAnnotations {
    
        private static final Constructor<?> AnnotationInvocationHandler_constructor;
        private static final Constructor<?> AnnotationData_constructor;
        private static final Method Class_annotationData;
        private static final Field Class_classRedefinedCount;
        private static final Field AnnotationData_annotations;
        private static final Field AnnotationData_declaredAnotations;
        private static final Method Atomic_casAnnotationData;
        private static final Class<?> Atomic_class;
    
        static{
            // static initialization of necessary reflection Objects
            try {
                Class<?> AnnotationInvocationHandler_class = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
                AnnotationInvocationHandler_constructor = AnnotationInvocationHandler_class.getDeclaredConstructor(new Class[]{Class.class, Map.class});
                AnnotationInvocationHandler_constructor.setAccessible(true);
    
                Atomic_class = Class.forName("java.lang.Class$Atomic");
                Class<?> AnnotationData_class = Class.forName("java.lang.Class$AnnotationData");
    
                AnnotationData_constructor = AnnotationData_class.getDeclaredConstructor(new Class[]{Map.class, Map.class, int.class});
                AnnotationData_constructor.setAccessible(true);
                Class_annotationData = Class.class.getDeclaredMethod("annotationData");
                Class_annotationData.setAccessible(true);
    
                Class_classRedefinedCount= Class.class.getDeclaredField("classRedefinedCount");
                Class_classRedefinedCount.setAccessible(true);
    
                AnnotationData_annotations = AnnotationData_class.getDeclaredField("annotations");
                AnnotationData_annotations.setAccessible(true);
                AnnotationData_declaredAnotations = AnnotationData_class.getDeclaredField("declaredAnnotations");
                AnnotationData_declaredAnotations.setAccessible(true);
    
                Atomic_casAnnotationData = Atomic_class.getDeclaredMethod("casAnnotationData", Class.class, AnnotationData_class, AnnotationData_class);
                Atomic_casAnnotationData.setAccessible(true);
    
            } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | NoSuchFieldException e) {
                throw new IllegalStateException(e);
            }
        }
    
        public static <T extends Annotation> void putAnnotation(Class<?> c, Class<T> annotationClass, Map<String, Object> valuesMap){
            putAnnotation(c, annotationClass, annotationForMap(annotationClass, valuesMap));
        }
    
        public static <T extends Annotation> void putAnnotation(Class<?> c, Class<T> annotationClass, T annotation){
            try {
                while (true) { // retry loop
                    int classRedefinedCount = Class_classRedefinedCount.getInt(c);
                    Object /*AnnotationData*/ annotationData = Class_annotationData.invoke(c);
                    // null or stale annotationData -> optimistically create new instance
                    Object newAnnotationData = createAnnotationData(c, annotationData, annotationClass, annotation, classRedefinedCount);
                    // try to install it
                    if ((boolean) Atomic_casAnnotationData.invoke(Atomic_class, c, annotationData, newAnnotationData)) {
                        // successfully installed new AnnotationData
                        break;
                    }
                }
            } catch(IllegalArgumentException | IllegalAccessException | InvocationTargetException | InstantiationException e){
                throw new IllegalStateException(e);
            }
    
        }
    
        @SuppressWarnings("unchecked")
        private static <T extends Annotation> Object /*AnnotationData*/ createAnnotationData(Class<?> c, Object /*AnnotationData*/ annotationData, Class<T> annotationClass, T annotation, int classRedefinedCount) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
            Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) AnnotationData_annotations.get(annotationData);
            Map<Class<? extends Annotation>, Annotation> declaredAnnotations= (Map<Class<? extends Annotation>, Annotation>) AnnotationData_declaredAnotations.get(annotationData);
    
            Map<Class<? extends Annotation>, Annotation> newDeclaredAnnotations = new LinkedHashMap<>(annotations);
            newDeclaredAnnotations.put(annotationClass, annotation);
            Map<Class<? extends Annotation>, Annotation> newAnnotations ;
            if (declaredAnnotations == annotations) {
                newAnnotations = newDeclaredAnnotations;
            } else{
                newAnnotations = new LinkedHashMap<>(annotations);
                newAnnotations.put(annotationClass, annotation);
            }
            return AnnotationData_constructor.newInstance(newAnnotations, newDeclaredAnnotations, classRedefinedCount);
        }
    
        @SuppressWarnings("unchecked")
        public static <T extends Annotation> T annotationForMap(final Class<T> annotationClass, final Map<String, Object> valuesMap){
            return (T)AccessController.doPrivileged(new PrivilegedAction<Annotation>(){
                public Annotation run(){
                    InvocationHandler handler;
                    try {
                        handler = (InvocationHandler) AnnotationInvocationHandler_constructor.newInstance(annotationClass,new HashMap<>(valuesMap));
                    } catch (InstantiationException | IllegalAccessException
                            | IllegalArgumentException | InvocationTargetException e) {
                        throw new IllegalStateException(e);
                    }
                    return (Annotation)Proxy.newProxyInstance(annotationClass.getClassLoader(), new Class[] { annotationClass }, handler);
                }
            });
        }
    }
    

    使用示例:

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface TestAnnotation {
        String value();
    }
    
    public static class TestClass{}
    
    public static void main(String[] args) {
        TestAnnotation annotation = TestClass.class.getAnnotation(TestAnnotation.class);
        System.out.println("TestClass annotation before:" + annotation);
    
        Map<String, Object> valuesMap = new HashMap<>();
        valuesMap.put("value", "some String");
        RuntimeAnnotations.putAnnotation(TestClass.class, TestAnnotation.class, valuesMap);
    
        annotation = TestClass.class.getAnnotation(TestAnnotation.class);
        System.out.println("TestClass annotation after:" + annotation);
    }
    

    输出:

    TestClass annotation before:null
    TestClass annotation after:@RuntimeAnnotations$TestAnnotation(value=some String)
    

    这种方法的局限性:

    • Java 的新版本可能随时破坏代码。
    • 上述示例仅适用于 Java 8 - 使其适用于较旧的 Java 版本需要在运行时检查 Java 版本并相应地更改实现。
    • 如果注解的 Class 获得 redefined(例如在调试期间),注解将丢失。
    • 未经彻底测试;不确定是否有任何不良副作用 - 使用风险自负...

    【讨论】:

    【解决方案3】:

    无法在运行时添加注解,听起来您需要引入一个adapter,模块 B 使用该adapter 来包装来自模块 A 的对象,从而公开所需的注解方法。

    【讨论】:

    • 我支持这个。但是我可能会考虑对原文进行注释,我认为这里没有什么大问题。我们通常这样做,以 JPA 实体为例,您将其传递给远程 EJB 组件以存储在 DB 中。你也可以使用它来填充你的 UI。
    • 汤姆:啊,当然。也许有继承:从模块 A 扩展类,覆盖有问题的方法,然后对其进行注释?
    • 醋:这对我来说可能是最简单的解决方案。我试图将我的“数据模型”与“数据实现”分开,但老实说,我没有看到需要插入不同的数据实现的时候。
    • 所以,就简单点吧。尽管如此,稍后在适当的时候对您的适配器进行编码会很方便。正如您所说,您可能会为此考虑继承,因此处理超类型即可。
    • 我决定对此采取混合方法。现在,我刚刚注释了原始方法(将依赖项添加到 modA),我认为我总是可以稍后拉出注释并使用适配器。谢谢各位!
    【解决方案4】:

    可以通过Proxy 在运行时创建注释。然后,您可以按照其他答案中的建议通过反射将它们添加到您的 Java 对象中(但您可能最好找到一种替代方法来处理它,因为通过反射弄乱现有类型可能很危险且难以调试)。

    但这并不容易......我写了一个库,我希望适当地使用Javanna 只是为了使用干净的 API 轻松地做到这一点。

    它位于JCenterMaven Central

    使用它:

    @Retention( RetentionPolicy.RUNTIME )
    @interface Simple {
        String value();
    }
    
    Simple simple = Javanna.createAnnotation( Simple.class, 
        new HashMap<String, Object>() {{
            put( "value", "the-simple-one" );
        }} );
    

    如果映射的任何条目与注释声明的字段和类型不匹配,则会引发异常。如果缺少任何没有默认值的值,则会抛出异常。

    这使得假设每个成功创建的注解实例都可以像编译时注解实例一样安全使用。

    作为奖励,这个库还可以解析注解类并将注解的值作为 Map 返回:

    Map<String, Object> values = Javanna.getAnnotationValues( annotation );
    

    这对于创建迷你框架很方便。

    【讨论】:

      猜你喜欢
      • 2010-09-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-06-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多