在运行时添加 Java 注释
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1635108/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Adding Java Annotations at Runtime
提问by Clayton
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.
多一点解释:我有两个模块,moduleA 和 moduleB。moduleB 依赖于 moduleA,它不依赖于任何东西。(modA 是我的核心数据类型和接口等,modB 是数据库/数据层) modB 也依赖于 externalLibrary。就我而言,modB 正在将一个类从 modA 移交给 externalLibrary,这需要对某些方法进行注释。具体的注释都是 externalLib 的一部分,正如我所说,modA 不依赖于 externalLib,我想保持这种状态。
So, is this possible, or do you have suggestions for other ways of looking at this problem?
那么,这可能吗,或者您对其他看待这个问题的方法有什么建议吗?
采纳答案by Tom
回答by ChssPly76
It's possible via bytecode instrumentation library such as Javassist.
可以通过字节码检测库(例如Javassist )来实现。
In particular, take a look at AnnotationsAttributeclass for an example on how to create / set annotations and tutorial section on bytecode APIfor general guidelines on how to manipulate class files.
特别是,查看AnnotationsAttribute类以获取有关如何创建/设置注释的示例,以及有关如何操作类文件的一般指南的字节码 API 教程部分。
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).
不过,这绝不是简单而直接的 - 我不会推荐这种方法,而是建议您考虑 Tom 的答案,除非您需要为大量类执行此操作(或者说这些类在运行时才可用,因此编写适配器是不可能的)。
回答by Balder
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.
还可以在运行时使用 Java 反射 API 向 Java 类添加注解。本质上,必须重新创建在类中定义的内部注解映射java.lang.Class
(或在内部类中定义的 Java 8 java.lang.Class.AnnotationData
)。自然地,这种方法非常笨拙,并且对于较新的 Java 版本可能随时会中断。但是对于快速和肮脏的测试/原型设计,这种方法有时很有用。
Proove of concept example for Java 8:
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...
- 新版本的 Java 可能随时会破坏代码。
- 上面的示例仅适用于 Java 8 - 使其适用于较旧的 Java 版本需要在运行时检查 Java 版本并相应地更改实现。
- 如果带注释的类被重新定义(例如在调试期间),注释将丢失。
- 未经彻底测试;不确定是否有任何不良副作用 -使用风险自负...
回答by Renato
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).
可以通过Proxy在运行时创建注释。然后,您可以按照其他答案中的建议,通过反射将它们添加到您的 Java 对象中(但您可能最好找到一种替代方法来处理该问题,因为通过反射弄乱现有类型可能很危险且难以调试)。
But it is not very easy... I wrote a library called, I hope appropriately, Javannajust to do this easily using a clean API.
但这并不是很容易......我写了一个名为Javaanna的库,我希望可以使用一个干净的 API 轻松地做到这一点。
It's in JCenterand 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 返回:
Map<String, Object> values = Javanna.getAnnotationValues( annotation );
This is convenient for creating mini-frameworks.
这对于创建迷你框架很方便。