在运行时添加 Java 注解

发布于 2024-08-09 01:58:31 字数 332 浏览 3 评论 0原文

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

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

那么,这可能吗?或者您对解决这个问题的其他方法有什么建议吗?

Is it possible to add an annotation to an object (in my case in particular, a Method) at runtime?

For a bit more explanation: I have two modules, moduleA and moduleB. moduleB depends upon moduleA, which doesn't depend upon anything. (modA is my core datatypes and interfaces and such, modB is db/data layer) modB also depends on externalLibrary. In my case, modB is handing off a class from modA to externalLibrary, which needs certain methods to be annotated. The specific annotations are all part of externalLib and, as I said, modA doesn't depend on externalLib and I'd like to keep it that way.

So, is this possible, or do you have suggestions for other ways of looking at this problem?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

无语# 2024-08-16 01:58:31

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

特别是,请查看 AnnotationsAttribute 类,了解如何创建/设置注释和 有关字节码 API 的教程部分,了解如何操作类文件的一般指南。

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

It's possible via bytecode instrumentation library such as Javassist.

In particular, take a look at AnnotationsAttribute class for an example on how to create / set annotations and tutorial section on bytecode API for general guidelines on how to manipulate class files.

This is anything but simple and straightforward, though - I would NOT recommend this approach and suggest you consider Tom's answer instead unless you need to do this for a huge number of classes (or said classes aren't available to you until runtime and thus writing an adapter is impossible).

故笙诉离歌 2024-08-16 01:58:31

还可以使用 Java 反射 API 在运行时向 Java 类添加注释。本质上,必须重新创建类 java.lang.Class 中定义的内部 Annotation 映射(或者对于 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 版本并相应地更改实现。
  • 如果带注释的类被重新定义(例如在调试期间),将会丢失。
  • 未经过彻底测试;不确定是否有任何不良副作用 - 使用风险自负...

It is also possible to add an Annotation to a Java class at runtime using the Java reflection API. Essentially one must recreate the internal Annotation maps defined in the class java.lang.Class (or for Java 8 defined in the internal class java.lang.Class.AnnotationData). Naturally this approach is quite hacky and might break at any time for newer Java versions. But for quick and dirty testing/prototyping this approach can be useful at times.

Proove of concept example for 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);
            }
        });
    }
}

Usage example:

@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);
}

Output:

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

Limitations of this approach:

  • New versions of Java may break the code at any time.
  • The above example only works for Java 8 - making it work for older Java versions would require checking the Java version at runtime and changing the implementation accordingly.
  • If the annotated Class gets redefined (e.g. during debugging), the annotation will be lost.
  • Not thoroughly tested; not sure if there are any bad side effects - use at your own risk...
三月梨花 2024-08-16 01:58:31

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

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

它位于 JCenterMaven 中心

使用它:

@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 );

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

It is possible to create annotations at runtime via a Proxy. You can then add them to your Java objects via reflection as suggested in other answers (but you probably would be better off finding an alternative way to handle that, as messing with the existing types via reflection can be dangerous and hard to debug).

But it is not very easy... I wrote a library called, I hope appropriately, Javanna just to do this easily using a clean API.

It's in JCenter and Maven Central.

Using it:

@Retention( RetentionPolicy.RUNTIME )
@interface Simple {
    String value();
}

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

If any entry of the map does not match the annotation declared field(s) and type(s), an Exception is thrown. If any value that has no default value is missing, an Exception is thrown.

This makes it possible to assume every annotation instance that is created successfully is as safe to use as a compile-time annotation instance.

As a bonus, this lib can also parse annotation classes and return the values of the annotation as a Map:

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

This is convenient for creating mini-frameworks.

音栖息无 2024-08-16 01:58:31

无法在运行时添加注释,听起来您需要引入该模块的 适配器 B 用于包装模块 A 中的对象,公开所需的带注释的方法。

It's not possible to add an annotation at runtime, it sounds like you need to introduce an adapter that module B uses to wrap the object from module A exposing the required annotated methods.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文