2009-10-28 5 views
60

È possibile aggiungere un'annotazione a un oggetto (nel mio caso, in particolare, un metodo) in fase di esecuzione?Aggiunta di annotazioni Java in fase di esecuzione

Per ulteriori spiegazioni: Ho due moduli, moduloA e moduloB. moduleB dipende dal modulo A, che non dipende da nulla. (modA è il mio core datatypes e interfacce e tale modB è db/data layer) modB dipende anche da externalLibrary. Nel mio caso, modB sta trasferendo una classe da modA a externalLibrary, che necessita di alcuni metodi per essere annotata. Le annotazioni specifiche fanno tutti parte di ExternalLib e, come ho detto, modA non dipende da ExternalLib e mi piacerebbe mantenerlo in questo modo.

Quindi, è possibile o avete suggerimenti per altri modi di considerare questo problema?

+0

Controlla che questo possa esserti d'aiuto http://stackoverflow.com/a/14276270/4741746 almeno lo possiamo modificare –

risposta

21

Non è possibile aggiungere un'annotazione in fase di esecuzione, sembra che sia necessario introdurre un adapter che il modulo B utilizza per avvolgere l'oggetto dal modulo A esponendo i metodi annotati richiesti.

+1

Io secondo questo. Ma potrei considerare di annotare l'originale, non vedo un grosso problema qui. Lo facciamo di solito, prendiamo il caso delle Entità JPA, che passate a un componente EJB remoto da archiviare in DB. E tu usi lo stesso per popolare la tua interfaccia utente. –

+0

Tom: Ah, certo. Forse con l'ereditarietà: estendi la classe dal modulo A, sostituisci il metodo in questione e quindi annota quello? – Clayton

+0

Aceto: questa è probabilmente la soluzione più semplice per me. Ho cercato di mantenere il mio "modello di dati" separato dalla mia "implementazione dei dati", ma onestamente non vedo un momento in cui avrei bisogno di collegare un'implementazione di dati diversa. – Clayton

38

È possibile tramite la libreria di strumentazione bytecode come Javassist.

In particolare, dare un'occhiata alla classe AnnotationsAttribute per un esempio su come creare/impostare annotazioni e tutorial section on bytecode API per le linee guida generali su come manipolare i file di classe.

Questo è tutt'altro che semplice e diretto, anche se - io non voglio raccomandare questo approccio e vi consiglio di prendere in considerazione la risposta di Tom, invece a meno che non avete bisogno di fare questo per un enorme numero di classi (o detto classi non sono a disposizione di voi fino a quando runtime e quindi scrivere un adattatore è impossibile).

12

È anche possibile aggiungere un'annotazione a una classe Java in fase di esecuzione utilizzando l'API di riflessione Java. In sostanza, è necessario ricreare le mappe di annotazione interne definite nella classe java.lang.Class (o per Java 8 definite nella classe interna java.lang.Class.AnnotationData). Naturalmente questo approccio è abbastanza hacky e potrebbe rompersi in qualsiasi momento per le nuove versioni di Java. Ma per test/prototipazione rapidi e sporchi questo approccio può essere utile a volte.

PROOVE del concetto di esempio per Java 8: esempio

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:

@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); 
} 

uscita:

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

limiti di questo approccio:

  • Nuove versioni di Java possono interrompere il codice in qualsiasi momento.
  • L'esempio precedente funziona solo per Java 8: far funzionare le versioni precedenti di Java richiederebbe la verifica della versione Java in fase di esecuzione e la modifica dell'implementazione di conseguenza.
  • Se la Classe annotata diventa redefined (ad esempio durante il debug), l'annotazione andrà persa.
  • Non completamente testato; Non so se ci sono effetti collaterali male - uso a proprio rischio ...
+0

Buon lavoro, mi piacerebbe davvero farlo funzionare con Java 1.7, forse questa mappa è utile: http://grepcode.com/file/repository .grepcode.com/java/root/jdk/openjdk/7u40-b43/java/lang/Class.java # Class.0annotazioni – gouessej

+1

Questo è fantastico: D Grazie per l'ottimo lavoro –

+0

Qualsiasi suggerimento su come farlo funzionare per campi? – heez

1

E 'possibile creare annotazioni in fase di runtime tramite un Proxy.È quindi possibile aggiungerli ai propri oggetti Java tramite la riflessione come suggerito in altre risposte (ma probabilmente sarebbe meglio trovare un modo alternativo per gestirli, poiché fare confusione con i tipi esistenti tramite reflection può essere pericoloso e difficile da eseguire il debug).

Ma non è molto facile ... Ho scritto una libreria chiamata, spero opportunamente, Javanna solo per farlo facilmente usando un'API pulita.

È in JCenter e Maven Central.

Usandolo:

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

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

Se una voce della mappa non corrisponde al campo di annotazione dichiarato (s) e il tipo (s), viene generata un'eccezione. Se manca un valore che non ha un valore predefinito, viene generata un'eccezione.

Ciò consente di presupporre che ogni istanza di annotazione creata correttamente sia sicura da utilizzare come un'istanza di annotazione in fase di compilazione.

Come bonus, questo lib può anche analizzare le classi di annotazione e restituire i valori del annotazioni come una mappa:

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

Questa funzione è utile per la creazione di mini-quadri.