Aggiunta di annotazioni Java in fase di esecuzione

È ansible aggiungere un’annotazione a un object (nel mio caso, in particolare, un metodo) 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 class 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, è ansible, o hai suggerimenti per altri modi di considerare questo problema?

Non è ansible aggiungere un’annotazione in fase di esecuzione, sembra che sia necessario introdurre un adattatore che il modulo B utilizza per avvolgere l’object dal modulo A esponendo i metodi annotati richiesti.

È ansible tramite la libreria di strumentazione bytecode come Javassist .

In particolare, date un’occhiata alla class AnnotationsAttribute per un esempio su come creare / impostare annotazioni e sezione tutorial sull’API bytecode per le linee guida generali su come manipolare i file di class.

Questo è tutto tranne che semplice e diretto, però – NON consiglierei questo approccio e suggerisco di prendere in considerazione la risposta di Tom, a meno che non sia necessario farlo per un numero enorme di classi (o dette classi non sono disponibili fino al runtime e quindi scrivere un adattatore è imansible).

È anche ansible aggiungere un’annotazione a una class Java in fase di runtime utilizzando l’API Java reflection. In sostanza è necessario ricreare le mappe di annotazione interne definite nella class java.lang.Class (o per Java 8 definite nella class 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.

Esempio di Proove of Concept per 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  void putAnnotation(Class c, Class annotationClass, Map valuesMap){ putAnnotation(c, annotationClass, annotationForMap(annotationClass, valuesMap)); } public static  void putAnnotation(Class c, Class 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  Object /*AnnotationData*/ createAnnotationData(Class c, Object /*AnnotationData*/ annotationData, Class annotationClass, T annotation, int classRedefinedCount) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { Map, Annotation> annotations = (Map, Annotation>) AnnotationData_annotations.get(annotationData); Map, Annotation> declaredAnnotations= (Map, Annotation>) AnnotationData_declaredAnotations.get(annotationData); Map, Annotation> newDeclaredAnnotations = new LinkedHashMap<>(annotations); newDeclaredAnnotations.put(annotationClass, annotation); Map, 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 annotationForMap(final Class annotationClass, final Map valuesMap){ return (T)AccessController.doPrivileged(new PrivilegedAction(){ 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); } }); } } 

Esempio di utilizzo:

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

Produzione:

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

Limitazioni di questo approccio:

  • Nuove versioni di Java potrebbero interrompere il codice in qualsiasi momento.
  • L’esempio sopra funziona solo per Java 8: far funzionare le versioni precedenti di Java richiederebbe la verifica della versione Java in fase di runtime e la modifica dell’implementazione di conseguenza.
  • Se la class annotata viene ridefinita (ad es. Durante il debug), l’annotazione andrà persa.
  • Non completamente testato; non sono sicuro se ci sono effetti collaterali negativi – utilizzare a proprio rischio

È ansible creare annotazioni in fase di esecuzione tramite un proxy . È quindi ansible 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() {{ put( "value", "the-simple-one" ); }} ); 

Se una qualsiasi voce della mappa non corrisponde ai campi o ai tipi di annotazione dichiarati, viene generata un’eccezione. Se manca un valore che non ha un valore predefinito, viene generata un’eccezione.

Ciò rende ansible assumere che ogni istanza di annotazione creata con successo sia sicura da utilizzare come un’istanza di annotazione in fase di compilazione.

Come bonus, questa libreria può anche analizzare le classi di annotazione e restituire i valori dell’annotazione come una mappa:

 Map values = Javanna.getAnnotationValues( annotation ); 

Questo è conveniente per la creazione di mini-framework.