Pergunta

É possível adicionar uma anotação a um objeto (no meu caso em particular, um método) em tempo de execução?

Para um pouco mais de explicação: tenho dois módulos, Modulea e ModuleB. Moduleb depende de Modulea, o que não depende de nada. (MODA são os meus principais tipos de dados e interfaces e tal, o Modb é DB/Data Cayer) Modb também depende do Externallibrary. No meu caso, o MODB está entregando uma classe da MODA para o Externallibrary, que precisa de determinados métodos a serem anotados. As anotações específicas fazem parte do Externallib e, como eu disse, o MODA não depende do Externallib e eu gostaria de mantê -lo dessa maneira.

Então, isso é possível, ou você tem sugestões para outras maneiras de analisar esse problema?

Foi útil?

Solução

Não é possível adicionar uma anotação em tempo de execução, parece que você precisa apresentar um adaptador Esse módulo B usa para envolver o objeto do módulo A expondo os métodos anotados necessários.

Outras dicas

É possível através da biblioteca de instrumentação de bytecode, como Javassist.

Em particular, dê uma olhada em AnotaçõesAttribute classe para um exemplo sobre como criar / definir anotações e Seção de tutorial na API BYTECODE Para diretrizes gerais sobre como manipular arquivos de classe.

Isso é tudo menos simples e direto, no entanto - eu não recomendaria essa abordagem e sugiro que você considere a resposta de Tom, a menos que precise fazer isso para um grande número de aulas (ou referidas aulas não estão disponíveis até o tempo de execução e, portanto, escrevendo um adaptador é impossível).

Também é possível adicionar uma anotação a uma classe Java em tempo de execução usando a API de reflexão Java. Essencialmente, é preciso recriar os mapas de anotação interna definidos na classe java.lang.Class (ou para Java 8 definido na classe interna java.lang.Class.AnnotationData). Naturalmente, essa abordagem é bastante hacky e pode quebrar a qualquer momento para versões Java mais recentes. Mas, para testes/prototipagem rápidos e sujos, essa abordagem pode ser útil às vezes.

Exemplo de proved de conceito para 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);
            }
        });
    }
}

Exemplo de uso:

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

Resultado:

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

Limitações dessa abordagem:

  • Novas versões do Java podem quebrar o código a qualquer momento.
  • O exemplo acima funciona apenas para o Java 8 - fazendo com que funcione para versões Java mais antigas exigiria verificar a versão Java no tempo de execução e alterar a implementação de acordo.
  • Se a classe anotada receber redefinido (por exemplo, durante a depuração), a anotação será perdida.
  • Não é completamente testado; Não tenho certeza se existem efeitos colaterais ruins - Use por sua conta e risco...

É possível criar anotações em tempo de execução por meio de um Proxy. Em seguida, você pode adicioná -los aos seus objetos Java por meio de reflexão, conforme sugerido em outras respostas (mas provavelmente seria melhor encontrar uma maneira alternativa de lidar com isso, pois mexer com os tipos existentes por reflexão pode ser perigoso e difícil de depurar).

Mas não é muito fácil ... escrevi uma biblioteca chamada, espero apropriadamente, Javanna Apenas para fazer isso facilmente usando uma API limpa.

Está dentro JCenter e Maven Central.

Usando isso:

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

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

Se qualquer entrada do mapa não corresponder à anotação declarada (s) e tipo (s) e tipo (s), uma exceção será lançada. Se algum valor que não tenha nenhum valor padrão estiver faltando, uma exceção será lançada.

Isso torna possível assumir que todas as instâncias de anotação criadas com sucesso são tão seguras para usar quanto uma instância de anotação em tempo de compilação.

Como bônus, este LIB também pode analisar as classes de anotação e retornar os valores da anotação como um mapa:

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

Isso é conveniente para criar mini-quadroworks.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top