2009-10-28 19 views
60

Kann ich einem Objekt (in meinem Fall insbesondere einer Methode) zur Laufzeit eine Annotation hinzufügen?Java-Annotationen zur Laufzeit hinzufügen

Für ein bisschen mehr Erklärung: Ich habe zwei Module, ModulA und ModulB. ModulB hängt von Modul A ab, das von nichts abhängt. (modA ist mein Kern Datentypen und Schnittstellen und so, modB ist db/Datenschicht) modB hängt auch von externalLibrary. In meinem Fall übergibt modB eine Klasse von modA an externalLibrary, die bestimmte Methoden benötigt, um Anmerkungen zu erhalten. Die spezifischen Anmerkungen sind alle Teil von externalLib und, wie gesagt, modA hängt nicht von externalLib ab und ich möchte es so behalten.

Also, ist das möglich, oder haben Sie Vorschläge für andere Möglichkeiten, dieses Problem zu betrachten?

+0

Überprüfen Sie diese eine kann Ihnen helfen http://Stackoverflow.com/a/14276270/4741746 zumindest können wir es ändern –

Antwort

21

Es ist nicht möglich, eine Annotation zur Laufzeit hinzuzufügen. Es scheint, als müssten Sie ein adapter einführen, das Modul B verwendet, um das Objekt aus Modul A zu wickeln und die erforderlichen annotierten Methoden verfügbar zu machen.

+1

Ich zweite dies. Aber ich könnte überlegen, das Original zu kommentieren, ich sehe hier kein großes Problem. Wir machen das normalerweise, nehmen Sie den Fall von JPA Entities, die Sie an eine Remote-EJB-Komponente übergeben, um sie in der DB zu speichern. Und Sie verwenden dasselbe, um Ihre Benutzeroberfläche zu füllen. –

+0

Tom: Ah, natürlich. Vielleicht mit Vererbung: Erweitern Sie die Klasse von Modul A, überschreiben Sie die betreffende Methode und kommentieren Sie sie dann? – Clayton

+0

Essig: das ist wahrscheinlich die einfachste Lösung für mich. Ich habe versucht, mein "Datenmodell" getrennt von meiner "Datenimplementierung" zu halten, aber ich sehe ehrlich gesagt keine Zeit, in der ich eine andere Datenimplementierung anschließen müsste. – Clayton

38

Dies ist über eine Bytecode-Instrumentierungsbibliothek wie Javassist möglich.

Sehen Sie sich insbesondere die Klasse AnnotationsAttribute für ein Beispiel zum Erstellen/Festlegen von Anmerkungen und tutorial section on bytecode API für allgemeine Richtlinien zum Bearbeiten von Klassendateien an.

Das ist alles andere als einfach und unkompliziert - ich würde diesen Ansatz NICHT empfehlen und vorschlagen, dass Sie Toms Antwort berücksichtigen, es sei denn, Sie müssen dies für eine große Anzahl von Klassen tun (oder diese Klassen stehen Ihnen erst zur Verfügung) Laufzeit und damit das Schreiben eines Adapters ist unmöglich).

12

Es ist auch möglich, eine Annotation zu einer Java-Klasse zur Laufzeit mithilfe der Java-Reflektions-API hinzuzufügen. Im Wesentlichen muss man die internen Annotationsmaps neu erstellen, die in der Klasse java.lang.Class (oder für Java 8 definiert in der internen Klasse java.lang.Class.AnnotationData) definiert sind. Natürlich ist dieser Ansatz ziemlich hacky und könnte jederzeit für neuere Java-Versionen brechen. Aber für schnelles und schmutziges Testen/Prototyping kann dieser Ansatz manchmal nützlich sein.

proove von Konzept Beispiel 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); 
} 

Ausgang:

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

Einschränkungen dieses Ansatzes:

  • Neue Java-Versionen können den Code jederzeit beschädigen.
  • Das obige Beispiel funktioniert nur für Java 8 - damit es für ältere Java-Versionen funktioniert, müsste die Java-Version zur Laufzeit überprüft und die Implementierung entsprechend geändert werden.
  • Wenn die annotierte Klasse redefined erhält (z. B. während des Debuggens), wird die Anmerkung verloren gehen.
  • Nicht gründlich getestet; wenn es irgendwelche schlechten Nebenwirkungen nicht sicher - Nutzung auf eigene Gefahr ...
+0

Gute Arbeit, ich würde wirklich schätzen, es mit Java 1.7 zu arbeiten, vielleicht ist diese Karte hilfreich: http://grepcode.com/file/repository .grepcode.com/java/root/jdk/openjdk/7u40-b43/java/lang/Class.java # Class.0annotations – gouessej

+1

Das ist genial: D Danke für die tolle Arbeit –

+0

Irgendwelche Vorschläge, wie man das zum Laufen bringt Felder? – heez

1

Es ist möglich, Anmerkungen zur Laufzeit über ein Proxy zu erstellen.Sie können sie dann zu Ihren Java-Objekten durch Reflektion hinzufügen, wie es in anderen Antworten vorgeschlagen wird (aber Sie wären wahrscheinlich besser darin, einen alternativen Weg zu finden, da ein Durcheinander mit den vorhandenen Typen durch Reflektion gefährlich und schwer zu debuggen ist).

Aber es ist nicht sehr einfach ... Ich schrieb eine Bibliothek namens, ich hoffe, Javanna einfach, um dies einfach mit einer sauberen API zu tun.

Es ist in JCenter und Maven Central.

es verwenden:

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

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

Wenn jeder Eintrag der Karte nicht die Anmerkung erklärt Feld übereinstimmt (n) und Typ (en), wird eine Ausnahme ausgelöst. Wenn ein Wert fehlt, der keinen Standardwert hat, wird eine Ausnahme ausgelöst.

Dadurch kann davon ausgegangen werden, dass jede Annotationsinstanz, die erfolgreich erstellt wurde, genauso sicher wie eine Annotationsinstanz zur Kompilierung verwendet werden kann.

Als Bonus kann diese lib auch Annotation-Klassen analysieren und die Werte der Anmerkung als Karte zurück:

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

Das für die Erstellung von Mini-Frameworks bequem ist.

Verwandte Themen