Modificar o parâmetro de string de anotação de uma definição de class no tempo de execução

Imagine que há uma aula:

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

Que já está compilado (não consigo controlar a fonte) e faz parte do caminho de class quando o jvm é inicializado. Eu gostaria de poder alterar “algum valor” para outra coisa em tempo de execução, de modo que qualquer reflection posterior teria meu novo valor em vez do padrão “algum valor”.

Isso é possível? Se sim, como?

   

Este código faz mais ou menos o que você pede – é uma simples prova de conceito:

  • uma implementação adequada também precisa lidar com as declaredAnnotations
  • se a implementação de annotations em Class.java for alterada, o código será interrompido (ou seja, poderá quebrar a qualquer momento no futuro)
  • Não tenho ideia se existem efeitos colaterais …

Saída:

oldAnnotation = algum valor
modifiedAnnotation = outro 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, Annotation> annotations = (Map, 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(); } 

Atenção: Não testado no OSX – ver comentário de @Marcel

Testado no OSX. Funciona bem.

Como também tive a necessidade de alterar os valores das annotations em tempo de execução, revisei essa questão.

Aqui está uma versão modificada da abordagem @assylias (muito obrigado pela inspiração).

 /** * 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 memberValues; try { memberValues = (Map) 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; } 

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

A vantagem dessa abordagem é que não é necessário criar uma nova instância de anotação. Portanto, não é necessário conhecer antecipadamente a class de anotação concreta. Além disso, os efeitos colaterais devem ser mínimos, pois a instância de anotação original permanece intocada.

Testado com o Java 8.

Este funciona na minha máquina com o Java 8. Ele altera o valor de ignoreUnknown na anotação @JsonIgnoreProperties(ignoreUnknown = true) de true para false .

 final List 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, Annotation> annotations = (Map, Annotation>) method.invoke(SomeClass.class, null); annotations.put(JsonIgnoreProperties.class, modifiedAnnotation); 

Experimente esta solução para o 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, Annotation> annotations = (Map, 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(); } 

A SPRING pode fazer esse trabalho com muita facilidade, pode ser útil para desenvolvedores de primavera. Siga esses passos :-

Primeira Solução: – 1) crie um Bean retornando um valor para someProperty. Aqui injetamos o somePropertyValue com a anotação @Value do database ou arquivo de propriedades: –

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

2) Depois disso, é possível injetar o somePropertyValue na anotação @Something da seguinte forma:

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

Segunda solução: –

1) cria getter setter no bean: –

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

2) Depois disso, é possível injetar o somePropertyValue na anotação @Something da seguinte forma:

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

Eu sou capaz de acessar e modificar annotations dessa maneira em jdk1.8, mas não tenho certeza porque não tem efeito,

 try { Field annotationDataField = myObject.getClass().getClass().getDeclaredField("annotationData"); annotationDataField.setAccessible(true); Field annotationsField = annotationDataField.get(myObject.getClass()).getClass().getDeclaredField("annotations"); annotationsField.setAccessible(true); Map, Annotation> annotations = (Map, 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(); } 

Os valores dos atributos de anotação devem ser constantes – assim, a menos que você queira fazer alguma manipulação séria de código de byte, isso não será possível. Existe uma maneira mais limpa, como criar uma class wrapper com a anotação que você deseja?