在运行时添加Java批注


76

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

进一步说明:我有两个模块,模块A和模块B。moduleB依赖于moduleA,而它不依赖任何东西。(modA是我的核心数据类型和接口,而modB是数据库/数据层)modB也取决于externalLibrary。以我为例,modB将一个类从modA传递给externalLibrary,这需要注释某些方法。特定的注释都是externalLib的一部分,正如我所说的,modA不依赖externalLib,我想保持这种方式。

那么,这是否可能,或者您对解决此问题的其他方式有建议吗?


选中此选项可能对您有所帮助stackoverflow.com/a/14276270/4741746至少我们可以修改它
sushant gosavi

Answers:


21

不可能在运行时添加注释,这听起来像您需要引入一个适配器,模块B使用该适配器包装来自模块A的对象,以暴露所需的带注释的方法。


1
我第二。但是我可能会考虑对原始文档进行注释,在这里看不到什么大问题。通常,我们以JPA实体为例,将其传递给远程EJB组件以存储在数据库中。并且您使用相同的来填充您的UI。
阿德·安萨里

汤姆:当然可以。也许具有继承性:从模块A扩展类,重写所讨论的方法,然后对其进行注释?
克莱顿

醋:对我来说,这可能是最简单的解决方案。我试图将“数据模型”与“数据实现”分开,但老实说,我没有看到需要插入其他数据实现的时间。
克莱顿

因此,简单吧。尽管如此,稍后在适当的时候对适配器进行编码将很方便。正如您所说的那样,您可能会考虑继承,因此处理超类型将做到这一点。
阿迪尔·安萨里

1
我决定对此采取混合方法。现在,我只是注释了原始方法(将依赖项添加到modA中),以确保以后可以随时提取注释并使用适配器。谢谢大家!
克莱顿

42

可以通过字节码检测库(例如Javassist)来实现

特别是,请查看AnnotationsAttribute类,以获取有关如何创建/设置注释的示例,以及有关字节码API的教程部分,以获取有关如何操作类文件的一般准则。

不过,这并不是简单而直接的任何事情-我不建议您采用这种方法,而是建议您考虑Tom的答案,除非您需要对大量类进行此操作(或者说,这些类直到运行时才对您可用,因此编写适配器是不可能的)。


26

还可以使用Java反射API在运行时向Java类添加注释。本质上,必须重新创建在该类中定义的内部Annotation映射java.lang.Class(或对于在内部类中定义的Java 8 java.lang.Class.AnnotationData)。自然,这种方法很容易破解,对于较新的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版本并相应地更改实现。
  • 如果重新定义了带注释的类(例如在调试过程中),则注释将丢失。
  • 未经彻底测试;不确定是否有不良副作用-使用风险自负...

干得好,我真的很感激,使其与Java 1.7工作,也许这张图是有帮助的:grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/...
gouessej

关于如何使其适用于字段的任何建议?
heez

2
@heez我已经在字段和方法上完成了这项工作。您可以看到AnnotationUtil.java
Dean Xu

6

可以在运行时通过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" );
    }} );

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

这样就可以假定成功创建的每个注释实例与编译时注释实例一样安全。

另外,此lib还可以解析注释类,并将注释的值作为Map返回:

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

这对于创建微型框架很方便。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.