Ist es möglich, einem Objekt (in meinem Fall insbesondere einer Methode) zur Laufzeit eine Anmerkung hinzuzufügen?
Für ein bisschen mehr Erklärung: Ich habe zwei Module, Modul A und Modul B. Modul B hängt von Modul A ab, das von nichts abhängt. (modA ist meine Kerndatentypen und -schnittstellen und so ist modB die Datenbank / Datenschicht) modB hängt auch von externalLibrary ab. In meinem Fall übergibt modB eine Klasse von modA an externalLibrary, für die bestimmte Methoden mit Anmerkungen versehen werden müssen. Die spezifischen Anmerkungen sind alle Teil von externalLib, und wie gesagt, modA hängt nicht von externalLib ab, und ich möchte, dass dies auch so bleibt.
Ist dies also möglich oder haben Sie Vorschläge für andere Sichtweisen auf dieses Problem?
quelle
Antworten:
Es ist nicht möglich, zur Laufzeit eine Annotation hinzuzufügen. Es scheint, als müssten Sie einen Adapter einführen , mit dem Modul B das Objekt aus Modul A umschließt und die erforderlichen annotierten Methoden verfügbar macht.
quelle
Dies ist über eine Bytecode-Instrumentierungsbibliothek wie Javassist möglich .
In der AnnotationsAttribute- Klasse finden Sie ein Beispiel zum Erstellen / Festlegen von Annotationen und einen Tutorial-Abschnitt zur Bytecode-API mit allgemeinen Richtlinien zum Bearbeiten von Klassendateien.
Dies ist jedoch alles andere als einfach und unkompliziert. Ich würde diesen Ansatz NICHT empfehlen und vorschlagen, stattdessen Toms Antwort in Betracht zu ziehen, es sei denn, Sie müssen dies für eine große Anzahl von Klassen tun (oder diese Klassen stehen Ihnen erst zur Laufzeit und damit zum Schreiben zur Verfügung ein Adapter ist nicht möglich).
quelle
Es ist auch möglich, einer Java-Klasse zur Laufzeit mithilfe der Java-Reflection-API eine Anmerkung hinzuzufügen. Im Wesentlichen müssen die in der Klasse definierten internen Annotation Maps
java.lang.Class
(oder für in der internen Klasse definiertes Java 8java.lang.Class.AnnotationData
) neu erstellt werden. Natürlich ist dieser Ansatz ziemlich hackig und kann bei neueren Java-Versionen jederzeit unterbrochen werden. Für schnelle und schmutzige Tests / Prototypen kann dieser Ansatz jedoch manchmal nützlich sein.Beispiel für ein Konzept für 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); } }); } }
Anwendungsbeispiel:
@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); }
Ausgabe:
TestClass annotation before:null TestClass annotation after:@RuntimeAnnotations$TestAnnotation(value=some String)
Einschränkungen dieses Ansatzes:
quelle
Es ist möglich, Anmerkungen zur Laufzeit über einen Proxy zu erstellen . Sie können sie dann über Reflektion zu Ihren Java-Objekten hinzufügen, wie in anderen Antworten vorgeschlagen (aber Sie sollten wahrscheinlich besser einen alternativen Weg finden, um damit umzugehen, da das Herumspielen mit den vorhandenen Typen über Reflektion gefährlich und schwer zu debuggen sein kann).
Aber es ist nicht sehr einfach ... Ich habe eine Bibliothek namens Javanna geschrieben , um dies einfach mit einer sauberen API zu tun.
Es ist in JCenter und Maven Central .
Es benutzen:
@Retention( RetentionPolicy.RUNTIME ) @interface Simple { String value(); } Simple simple = Javanna.createAnnotation( Simple.class, new HashMap<String, Object>() {{ put( "value", "the-simple-one" ); }} );
Wenn ein Eintrag in der Karte nicht mit den mit Anmerkungen deklarierten Feldern und Typen übereinstimmt, wird eine Ausnahme ausgelöst. Wenn ein Wert fehlt, der keinen Standardwert hat, wird eine Ausnahme ausgelöst.
Auf diese Weise kann davon ausgegangen werden, dass jede erfolgreich erstellte Annotationsinstanz genauso sicher ist wie eine Annotationsinstanz zur Kompilierungszeit.
Als Bonus kann diese Bibliothek auch Annotationsklassen analysieren und die Werte der Annotation als Map zurückgeben:
Dies ist praktisch für die Erstellung von Mini-Frameworks.
quelle