Imagine que hay una clase:

@Something(someProperty = "some value")
public class Foobar {
    //...
}

Que ya está compilado (no puedo controlar la fuente), y es parte de la ruta de clases cuando la jvm se inicia. Me gustaría ser capaz de cambiar «algo de valor» a algo más en tiempo de ejecución, de tal manera que cualquier reflexión a partir de entonces tendría mi nuevo valor en lugar de la predeterminada «algo de valor».

Es esto posible? Si es así, ¿cómo?

  • La clase tiene una annotations y un declaredAnnotations mapa de campos que se pueden intentar modificar con la reflexión…
  • grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/… alrededor de las líneas de 3086. Esto es muy frágil porque puede haber efectos secundarios y si la aplicación de Class.java cambios podría dejar de trabajar…
  • Oh, eso es genial, así que estás diciendo, básicamente, simplemente cambie (o un palo de antemano) mi propia instancia de la anotación, con el valor que yo quiera?
  • Es frágil, incluso sin necesidad de cambiar la aplicación, debido a la utilización de SoftReference, lo que hace posible que se pierdan todos los cambios en puntos arbitrarios.

7 Comentarios

  1. 40

    Este código hace más o menos lo que pide es una simple prueba de concepto:

    • una adecuada implementación debe ocuparse también de los declaredAnnotations
    • si la aplicación de anotaciones en Class.java los cambios, el código de descanso (es decir, que se puede romper en cualquier momento en el futuro)
    • No tengo idea de si hay efectos secundarios…

    De salida:

    oldAnnotation = valor

    modifiedAnnotation = otro valor

    public static void main(String[] args) throws Exception {
        final Something oldAnnotation = (Something) Foobar.class.getAnnotations()[0];
        System.out.println("oldAnnotation = " + oldAnnotation.someProperty());
        Annotation newAnnotation = new Something() {
    
            @Override
            public String someProperty() {
                return "another value";
            }
    
            @Override
            public Class<? extends Annotation> annotationType() {
                return oldAnnotation.annotationType();
            }
        };
        Field field = Class.class.getDeclaredField("annotations");
        field.setAccessible(true);
        Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) field.get(Foobar.class);
        annotations.put(Something.class, newAnnotation);
    
        Something modifiedAnnotation = (Something) Foobar.class.getAnnotations()[0];
        System.out.println("modifiedAnnotation = " + modifiedAnnotation.someProperty());
    }
    
    @Something(someProperty = "some value")
    public static class Foobar {
    }
    
    @Retention(RetentionPolicy.RUNTIME)
    @interface Something {
    
        String someProperty();
    }
    • Disculpas por la recaudación de este de entre los muertos, pero tengo una pregunta. Estoy modificando algunos valores de anotación en una prueba de simulacro basado en un aparato generador de patrón de tipo. A veces no se supera la prueba a actualizar la anotación de valor y bombas con «Clase elenco de excepción», mientras que inmediatamente después de que todo funciona OK (verde). Algún consejo? Estoy usando JUnit4 bajo un Robolectric-Android ambiente…
    • Sin ver la prueba en cuestión es difícil de decir – que podría ser debido a la interacción con el marco de trabajo ficticios que sí utiliza la reflexión o cambios en el código de bytes. Probablemente, usted debe publicar una pregunta aparte con detalles adicionales.
    • Gracias por tu pronta respuesta. Hay cierto consenso respecto a CGLIB y se burla de marco que afectan a este enfoque. He llegado a la conclusión de que este método no es el mejor camino para mis pruebas, tal y como está, pero +1 por el fresco de código. Gracias.
    • esto no funciona con java 8. getDeclaredField("annotations") parece que no funciona
    • También tenga en cuenta que según el Javadoc de Anotación, uno también debe sobrescribir el método toString, hashCode y es igual a métodos y enviarlos a los oldAnnotation
    • Puede alguien decirme cómo cambiar la anotación en el campo? Esta solución funciona filne para la clase de anotaciones, pero no para los campos 🙁
    • En este artículo se muestra cómo manipular las anotaciones en Java 8: rationaleemotions.wordpress.com/2016/05/27/…. Buscar «clase pública AnnotationHelper» a alrededor de la mitad de la página.

  2. 35

    Advertencia: No probados en OSX – véase el comentario de @Marcel

    Probado en OSX. Funciona bien.

    Ya que yo también tenía la necesidad de cambiar los valores de anotación en tiempo de ejecución, volví a esta pregunta.

    Aquí es una versión modificada de @assylias enfoque (muchas gracias por la inspiración).

    /**
     * Changes the annotation value for the given key of the given annotation to newValue and returns
     * the previous value.
     */
    @SuppressWarnings("unchecked")
    public static Object changeAnnotationValue(Annotation annotation, String key, Object newValue){
        Object handler = Proxy.getInvocationHandler(annotation);
        Field f;
        try {
            f = handler.getClass().getDeclaredField("memberValues");
        } catch (NoSuchFieldException | SecurityException e) {
            throw new IllegalStateException(e);
        }
        f.setAccessible(true);
        Map<String, Object> memberValues;
        try {
            memberValues = (Map<String, Object>) f.get(handler);
        } catch (IllegalArgumentException | IllegalAccessException e) {
            throw new IllegalStateException(e);
        }
        Object oldValue = memberValues.get(key);
        if (oldValue == null || oldValue.getClass() != newValue.getClass()) {
            throw new IllegalArgumentException();
        }
        memberValues.put(key,newValue);
        return oldValue;
    }

    Ejemplo de uso:

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface ClassAnnotation {
    String value() default "";
    }
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface FieldAnnotation {
    String value() default "";
    }
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface MethodAnnotation {
    String value() default "";
    }
    @ClassAnnotation("class test")
    public static class TestClass{
    @FieldAnnotation("field test")
    public Object field;
    @MethodAnnotation("method test")
    public void method(){
    }
    }
    public static void main(String[] args) throws Exception {
    final ClassAnnotation classAnnotation = TestClass.class.getAnnotation(ClassAnnotation.class);
    System.out.println("old ClassAnnotation = " + classAnnotation.value());
    changeAnnotationValue(classAnnotation, "value", "another class annotation value");
    System.out.println("modified ClassAnnotation = " + classAnnotation.value());
    Field field = TestClass.class.getField("field");
    final FieldAnnotation fieldAnnotation = field.getAnnotation(FieldAnnotation.class);
    System.out.println("old FieldAnnotation = " + fieldAnnotation.value());
    changeAnnotationValue(fieldAnnotation, "value", "another field annotation value");
    System.out.println("modified FieldAnnotation = " + fieldAnnotation.value());
    Method method = TestClass.class.getMethod("method");
    final MethodAnnotation methodAnnotation = method.getAnnotation(MethodAnnotation.class);
    System.out.println("old MethodAnnotation = " + methodAnnotation.value());
    changeAnnotationValue(methodAnnotation, "value", "another method annotation value");
    System.out.println("modified MethodAnnotation = " + methodAnnotation.value());
    }

    La ventaja de este enfoque es, que uno no necesita crear una nueva anotación de instancia. Por lo tanto, uno no necesita saber el hormigón anotación de la clase con antelación. También los efectos secundarios debe ser mínima, ya que la anotación original instancia permanece intacta.

    Probado con Java 8.

    • Gracias Balder, ¿Puede por favor explicar en línea f = handler.getClass().getDeclaredField("memberValues"); lo que es memberValues aquí?
    • «memberValues» es el Mapa de una Anotación Java, donde es miembro del valor de pares de bases están almacenados – es decir, aquí cualquier valor de una anotación se almacena como un par de su nombre/clave y su valor acutal. El método anterior, simplemente accede a este campo a través de la reflexión mediante el establecimiento es accesible y, a continuación, reemplaza el valor existente con el nuevo valor.
    • Puede alguien decirme cómo cambiar la anotación en el campo? Esta solución funciona filne para la clase de anotaciones, pero no para los campos 🙁
    • Acabo de probar esto y para mí este enfoque también trabaja en el campo y método de Anotaciones – he añadido un ejemplo que demuestra esto. Puede usted explicar en más detalle que tiene problemas?
    • Solución fantástica, si me podría dar diez votos me gustaría! También trabajó bien con Java 7.
    • Creo que es equivocado. La pregunta fue: «…otra cosa en tiempo de ejecución, de tal manera que cualquier reflexión a partir de entonces tendría mi nuevo valor…» . El problema es que getMethod sólo devuelve una copia de un método eficaz de datos de cambio sólo en una copia. Esta es la razón por la que su prueba de obras tan lejos, pero se producirá un error si se llama getMethod(..) de nuevo, trate de TestClass.clase.getMethod(«el método»).getAnnotation(MethodAnnotation.class).el valor de() y aparecerá el valor antiguo. (probado en osx, Jdk 8_25)
    • Acabo de probar tu ejemplo y ciertamente no funciona en mi computadora Windows 7 JDK 8_60. Quizás el JRE aplicación es diferente en osx (no probado esto mismo)…
    • Alguien intenta esto con Junit y org.databene.contiperf.PerfTest y Java 1.7? Puedo cambiar mi anotación, pero mi método aún tiene valor antiguo. Alguien sabe por qué?
    • Hola @Balder. Me gustaría usar la solución para cambiar un WebService targetNamespace en tiempo de ejecución en una forma que cada vez que entra en baseurl/Algo?wsdl obtener el nuevo valor. Dónde o cuándo debo llamar a la changeAnnotationValue() para que esto suceda? Gracias.
    • usted obtener una copia cuando se consulta una clase para un Method (o de cualquier otro miembro), pero la copia se inicializa con la información almacenada en caché, si existe. Sin embargo, esta información almacenada en caché suavemente que se hace referencia y se puede quitar en cualquier momento. Así que básicamente es una cuestión de suerte, si el siguiente reflexivo de las consultas obtener el manipulado de anotación o reconstruir el original.
    • He probado esta solución. Pero su cambio de la anotación de valor para todas las instancias de la clase. Es posible cambiar sólo para el caso específico de la clase?
    • final ClassAnnotation classAnnotation = TestClass.clase.getAnnotation(ClassAnnotation.class); donde se ClassAnnotation y TestClass.class

  3. 3

    Éste funciona en mi máquina con Java 8. Cambia el valor de ignoreUnknown en la anotación @JsonIgnoreProperties(ignoreUnknown = true) de verdadero a falso.

    final List<Annotation> matchedAnnotation = Arrays.stream(SomeClass.class.getAnnotations()).filter(annotation -> annotation.annotationType().equals(JsonIgnoreProperties.class)).collect(Collectors.toList());    
    final Annotation modifiedAnnotation = new JsonIgnoreProperties() {
    @Override public Class<? extends Annotation> annotationType() {
    return matchedAnnotation.get(0).annotationType();
    }    @Override public String[] value() {
    return new String[0];
    }    @Override public boolean ignoreUnknown() {
    return false;
    }    @Override public boolean allowGetters() {
    return false;
    }    @Override public boolean allowSetters() {
    return false;
    }
    };    
    final Method method = Class.class.getDeclaredMethod("getDeclaredAnnotationMap", null);
    method.setAccessible(true);
    final Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) method.invoke(SomeClass.class, null);
    annotations.put(JsonIgnoreProperties.class, modifiedAnnotation);
  4. 1

    De probar esta solución para Java 8

    public static void main(String[] args) throws Exception {
    final Something oldAnnotation = (Something) Foobar.class.getAnnotations()[0];
    System.out.println("oldAnnotation = " + oldAnnotation.someProperty());
    Annotation newAnnotation = new Something() {
    @Override
    public String someProperty() {
    return "another value";
    }
    @Override
    public Class<? extends Annotation> annotationType() {
    return oldAnnotation.annotationType();
    }
    };
    Method method = Class.class.getDeclaredMethod("annotationData", null);
    method.setAccessible(true);
    Object annotationData = method.invoke(getClass(), null);
    Field declaredAnnotations = annotationData.getClass().getDeclaredField("declaredAnnotations");
    declaredAnnotations.setAccessible(true);
    Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) declaredAnnotations.get(annotationData);
    annotations.put(Something.class, newAnnotation);
    Something modifiedAnnotation = (Something) Foobar.class.getAnnotations()[0];
    System.out.println("modifiedAnnotation = " + modifiedAnnotation.someProperty());
    }
    @Something(someProperty = "some value")
    public static class Foobar {
    }
    @Retention(RetentionPolicy.RUNTIME)
    @interface Something {
    String someProperty();
    }
    • Este no funciona en mi máquina (OS X).
    • java.lang.UnsupportedOperationException en java.util.AbstractMap.poner(Fuente Desconocida)
  5. 1

    De PRIMAVERA puede hacer este trabajo muy fácilmente , podría ser útil para la primavera de desarrollador .
    siga estos pasos :-

    Primera Solución :-
    1)crear un Bean de devolver un valor para someProperty . Aquí me inyecta la somePropertyValue con @Valor de anotación de DB o archivo de propiedades :-

        @Value("${config.somePropertyValue}")
    private String somePropertyValue;
    @Bean
    public String somePropertyValue(){
    return somePropertyValue;
    }

    2)Después de esto , es posible inyectar la somePropertyValue en el @Algo anotación como esta :-

    @Something(someProperty = "#{@somePropertyValue}")
    public class Foobar {
    //...
    }

    Segunda solución :-

    1) crear getter setter en frijol :-

     @Component
    public class config{
    @Value("${config.somePropertyValue}")
    private String somePropertyValue;
    public String getSomePropertyValue() {
    return somePropertyValue;
    }
    public void setSomePropertyValue(String somePropertyValue) {
    this.somePropertyValue = somePropertyValue;
    }
    }

    2)Después de esto , es posible inyectar la somePropertyValue en el @Algo anotación como esta :-

    @Something(someProperty = "#{config.somePropertyValue}")
    public class Foobar {
    //...
    }
    • Esto sólo funciona con String los valores de anotación.
    • Agregar los frijoles nombre en @Componenet(«…») y @Ámbito de SCOPE_PROTOTYPE también es necesaria.
    • Hay un ejemplo completo para esto? Esto es literalmente lo que tengo en mi actual del caso de uso. Pero me parece que no puede conseguir que funcione.
    • Y también puede ser usado con las anotaciones JPA? O simplemente la primavera de anotaciones?
  6. 0

    soy capaz de acceder y modificar annotaions de esta manera en jdk1.8,pero no sé por qué no tiene ningún efecto,

    try {
    Field annotationDataField = myObject.getClass().getClass().getDeclaredField("annotationData");
    annotationDataField.setAccessible(true);
    Field annotationsField = annotationDataField.get(myObject.getClass()).getClass().getDeclaredField("annotations");
    annotationsField.setAccessible(true);
    Map<Class<? extends Annotation>, Annotation> annotations =  (Map<Class<? extends Annotation>, Annotation>) annotationsField.get(annotationDataField.get(myObject.getClass()));
    annotations.put(Something.class, newSomethingValue);
    } catch (IllegalArgumentException | IllegalAccessException e) {
    e.printStackTrace();
    } catch (NoSuchFieldException e) {
    e.printStackTrace();
    } catch (SecurityException e) {    
    e.printStackTrace();
    }
  7. -3

    La anotación de los valores de atributo tiene que ser constantes, así que a menos que usted quiere hacer algo serio de código de bytes de manipulación no será posible. Hay un modo limpio, tales como la creación de una clase contenedora con la anotación que deseo?

    • Nope, va a tener que ser este uno por desgracia. Lo difícil / loco es que para modificar las constantes en tiempo de ejecución? ¿La jvm hacer algún tipo de cadena de pasante en el montón, o es más de una línea, literalmente, en el programa de la parte de texto del código de bytes?

Dejar respuesta

Please enter your comment!
Please enter your name here