런타임 시 클래스 정의의 주석 문자열 매개변수 수정
클래스가 있다고 상상해보십시오.
@Something(someProperty = "some value")
public class Foobar {
//...
}
이미 컴파일되어 있고(소스를 제어할 수 없음) jvm이 시작될 때 클래스 경로의 일부입니다. 런타임에 "일부 값"을 다른 것으로 변경할 수 있기를 원합니다. 따라서 이후의 모든 리플렉션 은 기본 "일부 값" 대신 새 값을 갖게 됩니다.
이게 가능해? 그렇다면 어떻게?
이 코드는 요청한 대로 수행합니다. 간단한 개념 증명입니다.
- 적절한 구현은 또한
declaredAnnotations
- Class.java의 주석 구현이 변경되면 코드가 중단됩니다(즉, 향후 언제든지 중단될 수 있음).
- 부작용이 있을지 모르겠네요...
산출:
oldAnnotation = 일부 값 modifyAnnotation =
다른 값
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();
}
경고: OSX에서 테스트되지 않음 - @Marcel의 주석 참조
OSX에서 테스트되었습니다. 잘 작동합니다.
또한 런타임에 주석 값을 변경할 필요가 있었기 때문에 이 질문을 다시 방문했습니다.
다음은 @assylias 접근 방식의 수정된 버전입니다(영감을 주셔서 감사합니다).
/**
* 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;
}
사용 예:
@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());
}
이 접근 방식의 장점은 새 주석 인스턴스를 만들 필요가 없다는 것입니다. 따라서 구체적인 주석 클래스를 미리 알 필요가 없습니다. 또한 원래 주석 인스턴스가 그대로 유지되므로 부작용이 최소화되어야 합니다.
Java 8로 테스트했습니다.
이 사람은 그것의 값을 변경 자바 8. 내 컴퓨터에서 작동 ignoreUnknown
주석에 @JsonIgnoreProperties(ignoreUnknown = true)
에서 사실 로 거짓 .
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);
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은 이 작업을 매우 쉽게 수행할 수 있으며 스프링 개발자에게 유용할 수 있습니다. 이 차례를 따라라 :-
첫 번째 솔루션:- 1)someProperty에 대한 값을 반환하는 Bean을 만듭니다. 여기에 DB 또는 속성 파일에서 @Value 주석과 함께 somePropertyValue를 주입했습니다.
@Value("${config.somePropertyValue}")
private String somePropertyValue;
@Bean
public String somePropertyValue(){
return somePropertyValue;
}
2) 그 후에 다음과 같이 @Something 주석에 somePropertyValue를 주입할 수 있습니다.
@Something(someProperty = "#{@somePropertyValue}")
public class Foobar {
//...
}
두 번째 솔루션 :-
1) 빈에 getter setter 생성 :-
@Component
public class config{
@Value("${config.somePropertyValue}")
private String somePropertyValue;
public String getSomePropertyValue() {
return somePropertyValue;
}
public void setSomePropertyValue(String somePropertyValue) {
this.somePropertyValue = somePropertyValue;
}
}
2) 그 후에 다음과 같이 @Something 주석에 somePropertyValue를 주입할 수 있습니다.
@Something(someProperty = "#{config.somePropertyValue}")
public class Foobar {
//...
}
jdk1.8에서 이런 방식으로 주석에 액세스하고 수정할 수 있지만 효과가 없는 이유는 확실하지 않습니다.
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();
}
주석 속성 값은 상수여야 합니다. 따라서 심각한 바이트 코드 조작을 원하지 않는 한 불가능합니다. 원하는 주석으로 래퍼 클래스를 만드는 것과 같은 더 깨끗한 방법이 있습니까?
ReferenceURL : https://stackoverflow.com/questions/14268981/modify-a-class-definitions-annotation-string-parameter-at-runtime
'IT이야기' 카테고리의 다른 글
항아리 안의 참조 항아리 (0) | 2021.10.13 |
---|---|
armv7이란 (0) | 2021.10.13 |
각 그룹 내에서 하나의 쉼표로 구분된 문자열로 열 축소/연결/집계 (0) | 2021.10.13 |
실제로 1KB(KiloByte)는 1024바이트인가 (0) | 2021.10.13 |
PowerMockRunner로 JUnit 테스트를 실행할 수 없음 (0) | 2021.10.13 |