java - resueltos - Modificar el parámetro de cadena de anotación de una definición de clase en el tiempo de ejecución
programacion orientada a objetos java (7)
Imagina 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 se inicia el jvm. Me gustaría poder cambiar "algún valor" a otra cosa en tiempo de ejecución, de modo que cualquier reflexión posterior tendrá mi nuevo valor en lugar del valor predeterminado "algún valor".
es posible? ¿Si es así, cómo?
É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);
Este código hace más o menos lo que usted solicita, es una simple prueba de concepto:
- una implementación adecuada también debe tratar con las
declaredAnnotations
- si la implementación de anotaciones en Class.java cambia, el código se romperá (es decir, puede romperse en cualquier momento en el futuro)
- No tengo idea si hay efectos secundarios ...
Salida:
oldAnotation = algún 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();
}
Los valores de los atributos de anotación deben ser constantes, por lo tanto, a menos que desee realizar una manipulación de código de byte grave, no será posible. ¿Hay una manera más limpia, como crear una clase contenedora con la anotación que deseas?
Prueba 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();
}
SPRING puede hacer este trabajo muy fácilmente, podría ser útil para el desarrollador de primavera. sigue estos pasos :-
Primera solución: - 1) crear un Bean devolviendo un valor para someProperty. Aquí inyecté el somePropertyValue con la anotación @Value desde el archivo DB o de propiedades: -
@Value("${config.somePropertyValue}")
private String somePropertyValue;
@Bean
public String somePropertyValue(){
return somePropertyValue;
}
2) Después de esto, es posible inyectar el somePropertyValue en la anotación @Something de esta manera: -
@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 el somePropertyValue en la anotación @Something de esta manera: -
@Something(someProperty = "#{config.somePropertyValue}")
public class Foobar {
//...
}
puedo acceder y modificar las anotaciones de esta manera en jdk1.8, pero no estoy seguro de 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();
}
Advertencia: No probado en OSX - ver comentario de @Marcel
Probado en OSX. Funciona bien.
Como también tuve la necesidad de cambiar los valores de las anotaciones en tiempo de ejecución, revisé esta pregunta.
Aquí hay una versión modificada del enfoque de @assylias (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 instancia de anotación. Por lo tanto, no es necesario conocer la clase de anotación concreta de antemano. Además, los efectos secundarios deben ser mínimos ya que la instancia de la anotación original permanece intacta.
Probado con Java 8.