在运行时添加 Java 注释

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

更详细的说明: 我有两个模块,模块 A 和模块 B。模块 B 依赖于模块 A,而模块 A 不依赖于任何东西。(modA 是我的核心数据类型和接口,因此,modB 是 db/数据层) modB 也依赖外部库。在我的例子中,modB 将一个类从 modA 传递给 exteralLibrary,后者需要注释某些方法。具体的注释都是外部实例库的一部分,正如我所说的,modA 不依赖于外部实例库,我希望保持这种状态。

那么,这是可能的,或者你有其他的方法来看待这个问题的建议吗?

70242 次浏览

在运行时添加注释是不可能的,听起来您需要引入一个 适配器,模块 B 用它从模块 A 中包装对象,并公开所需的注释方法。

可以通过字节码检测库(如 贾维斯特)实现。

特别是,查看 注释属性类的示例,了解如何创建/设置注释,以及 关于字节码 API 的教程部分的一般指导原则,了解如何操作类文件。

但是,这并不简单和直接——我不会推荐这种方法,并建议您考虑 Tom 的答案,除非您需要为大量的类这样做(或者所说的类在运行时之前不可用,因此不可能编写适配器)。

还可以使用 Java 反射 API 在运行时向 Java 类添加注释。实际上,必须重新创建在类 java.lang.Class中定义的内部注释映射(或者对于在内部类 java.lang.Class.AnnotationData中定义的 Java8)。当然,这种方法相当古怪,对于较新的 Java 版本,可能随时会中断。但是对于快速而粗糙的测试/原型设计,这种方法有时是有用的。

Java8的概念验证示例:

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 的新版本可能在任何时候破坏代码。
  • 上面的例子只适用于 Java8——要使它适用于旧的 Java 版本,需要在运行时检查 Java 版本并相应地更改实现。
  • 如果带注释的类得到 重新定义(例如在调试期间) ,注释将丢失。
  • 未经彻底测试,不确定是否有任何不良副作用-使用风险自负..。

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

但是这并不容易... ... 我编写了一个名为 贾瓦纳的库,我希望能够恰当地使用一个干净的 API 轻松地完成这项工作。

JCenter玛文中心

使用它:

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


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

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

这样就可以假设成功创建的每个注释实例都可以安全地用作编译时注释实例。

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

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

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