実行時に Java アノテーションを追加する
-
06-07-2019 - |
質問
実行時にオブジェクト (特に私の場合はメソッド) にアノテーションを追加することは可能ですか?
もう少し詳しく説明すると、moduleA と moduleB という 2 つのモジュールがあります。moduleB は moduleA に依存しますが、 moduleA は何も依存しません。(modA はコアのデータ型とインターフェイスなど、modB はデータベース/データ層です) modB は externalLibrary にも依存します。私の場合、modB は modA から externalLibrary にクラスを引き渡しているため、特定のメソッドにアノテーションを付ける必要があります。特定のアノテーションはすべて externalLib の一部であり、前述したように、modA は externalLib に依存しないため、そのように保ちたいと考えています。
それで、これは可能ですか、それともこの問題を考察する別の方法についての提案はありますか?
解決
実行時に注釈を追加することはできません。アダプターモジュールBがモジュールAからオブジェクトをラップするために使用し、必要な注釈付きメソッドを公開します。
他のヒント
Javassist などのバイトコードインストルメンテーションライブラリ経由で可能です。
特に、注釈およびバイトコードAPIのチュートリアルセクションをご覧ください。
これは単純で簡単なものではありません-このアプローチはお勧めしません。膨大な数のクラスでこれを行う必要がない限り、代わりにトムの答えを検討することをお勧めしますしたがって、アダプターを記述することは不可能です。)
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バージョンを確認し、それに応じて実装を変更する必要があります。
- 注釈付きクラスが再定義を取得した場合(デバッグ中など) 、注釈は失われます。
- 完全にはテストされていません。悪い副作用があるかどうかわからない-ご自身の責任で使用 ...
実行時にアノテーションを作成することができます。 プロキシ. 。その後、他の回答で提案されているように、リフレクションを介してそれらを Java オブジェクトに追加できます (ただし、リフレクションを介して既存の型をいじるのは危険でデバッグが難しい可能性があるため、おそらくそれを処理する別の方法を見つけた方がよいでしょう)。
しかし、それはそれほど簡単ではありません...というライブラリを書きました。適切であればいいのですが、 ジャバンナ クリーンな API を使用してこれを簡単に行うためです。
入った Jセンター そして メイブン・セントラル.
それを使用して:
@Retention( RetentionPolicy.RUNTIME )
@interface Simple {
String value();
}
Simple simple = Javanna.createAnnotation( Simple.class,
new HashMap<String, Object>() {{
put( "value", "the-simple-one" );
}} );
マップのエントリがアノテーションで宣言されたフィールドおよびタイプと一致しない場合、例外がスローされます。デフォルト値のない値が存在しない場合は、例外がスローされます。
これにより、正常に作成されたすべてのアノテーション インスタンスはコンパイル時のアノテーション インスタンスと同じくらい安全に使用できると想定できます。
おまけに、このライブラリは注釈クラスを解析して、注釈の値をマップとして返すこともできます。
Map<String, Object> values = Javanna.getAnnotationValues( annotation );
ミニフレームワークを作成する場合に便利です。