Domanda

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

Per ulteriori spiegazioni: ho due moduli, modulo A e modulo B. moduleB dipende da moduleA, che non dipende da nulla. (modA è il tipo di dati e le interfacce principali e tale, modB è db / livello dati) modB dipende anche dalla libreria esterna. Nel mio caso, modB sta distribuendo una classe da modA a externalLibrary, che necessita di alcuni metodi per essere annotata. Le annotazioni specifiche fanno tutte parte di externalLib e, come ho detto, modA non dipende da externalLib e mi piacerebbe mantenerlo in questo modo.

Quindi, è possibile o hai suggerimenti per altri modi di vedere questo problema?

È stato utile?

Soluzione

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

Altri suggerimenti

È possibile tramite la libreria di strumentazione bytecode come Javassist .

In particolare, dai un'occhiata a AnnotationsAttribute per un esempio su come creare / impostare annotazioni e sezione tutorial sull'API bytecode per linee guida generali su come manipolare i file di classe.

Questo è tutt'altro che semplice e diretto, tuttavia - NON consiglierei questo approccio e suggerirei di considerare la risposta di Tom invece a meno che non sia necessario farlo per un numero enorme di classi (o detto che le classi non sono disponibili fino al runtime e quindi scrivere un adattatore è impossibile).

È inoltre possibile aggiungere un'annotazione a una classe Java in fase di runtime 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 definito nella classe interna java.lang.Class.AnnotationData ). Naturalmente questo approccio è piuttosto confuso e potrebbe rompersi in qualsiasi momento per le nuove versioni di Java. Ma per test / prototipi rapidi e sporchi questo approccio può essere utile a volte.

Esempio di dimostrazione di concetti 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 <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);
            }
        });
    }
}

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

Output:

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

Limitazioni di questo approccio:

  • Le nuove versioni di Java possono interrompere il codice in qualsiasi momento.
  • L'esempio sopra funziona solo per Java 8 - per farlo funzionare con versioni Java precedenti richiederebbe il controllo della versione Java in fase di esecuzione e la modifica dell'implementazione di conseguenza.
  • Se la classe annotata viene ridefinita (ad esempio durante il debug) , l'annotazione andrà persa.
  • Non testato a fondo; non sono sicuro che ci siano effetti collaterali negativi - uso a proprio rischio ...

È possibile creare annotazioni in fase di esecuzione tramite Proxy . Puoi quindi aggiungerli ai tuoi oggetti Java tramite reflection come suggerito in altre risposte (ma probabilmente sarebbe meglio trovare un modo alternativo per gestirlo, 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 appropriatamente, 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 qualsiasi voce della mappa non corrisponde ai campi e ai tipi dichiarati di annotazione, viene generata un'eccezione. Se manca un valore senza valore predefinito, viene generata un'eccezione.

Questo rende possibile supporre che ogni istanza di annotazione creata con successo sia sicura da usare 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 Mappa:

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

È utile per creare mini-framework.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top