IT박스

런타임에 Java 주석 추가

itboxs 2020. 11. 12. 08:02
반응형

런타임에 Java 주석 추가


런타임에 객체 (특히 메서드)에 주석을 추가 할 수 있습니까?

좀 더 설명을 원하시면 : moduleA와 moduleB라는 두 개의 모듈이 있습니다. moduleB는 아무것도 의존하지 않는 moduleA에 의존합니다. (modA는 내 핵심 데이터 유형 및 인터페이스이며 modB는 db / data 레이어입니다) modB는 externalLibrary에도 의존합니다. 제 경우에는 modB가 modA에서 externalLibrary로 클래스를 전달하고 있는데, 주석을 달려면 특정 메서드가 필요합니다. 특정 주석은 모두 externalLib의 일부이며 제가 말했듯이 modA는 externalLib에 의존하지 않으며 그대로 유지하고 싶습니다.

그렇다면 이것이 가능합니까, 아니면이 문제를 보는 다른 방법에 대한 제안이 있습니까?


런타임에 주석을 추가 할 수 없습니다. 모듈 B가 필요한 주석 메서드를 노출하는 모듈 A의 개체를 래핑하는 데 사용 하는 어댑터 를 도입해야하는 것처럼 들립니다 .


Javassist 와 같은 바이트 코드 계측 라이브러리를 통해 가능합니다 .

특히 주석 을 생성 / 설정하는 방법에 대한 예제 AnnotationsAttribute 클래스를 참조 하고 클래스 파일을 조작하는 방법에 대한 일반적인 지침은 바이트 코드 API에 대한 자습서 섹션을 참조 하십시오.

그러나 이것은 간단하고 간단하지 않습니다. 나는이 접근 방식을 권장하지 않으며 막대한 수의 클래스에 대해이 작업을 수행해야하지 않는 한 Tom의 대답을 고려하는 것이 좋습니다 (또는 런타임까지 클래스를 사용할 수 없으므로 작성 어댑터는 불가능합니다).


Java 리플렉션 API를 사용하여 런타임에 Java 클래스에 주석을 추가 할 수도 있습니다. 본질적으로 클래스에 정의 된 내부 주석 맵을 다시 만들어야합니다 java.lang.Class(또는 내부 클래스에 정의 된 Java 8의 경우 java.lang.Class.AnnotationData). 당연히이 접근 방식은 매우 엉망이며 최신 Java 버전에서는 언제든지 중단 될 수 있습니다. 그러나 빠르고 더러운 테스트 / 프로토 타이핑의 경우이 접근 방식이 유용 할 수 있습니다.

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

사용 예 :

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

산출:

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

이 접근 방식의 한계 :

  • New versions of Java may break the code at any time.
  • The above example only works for Java 8 - making it work for older Java versions would require checking the Java version at runtime and changing the implementation accordingly.
  • If the annotated Class gets redefined (e.g. during debugging), the annotation will be lost.
  • Not thoroughly tested; not sure if there are any bad side effects - use at your own risk...

It is possible to create annotations at runtime via a Proxy. You can then add them to your Java objects via reflection as suggested in other answers (but you probably would be better off finding an alternative way to handle that, as messing with the existing types via reflection can be dangerous and hard to debug).

But it is not very easy... I wrote a library called, I hope appropriately, Javanna just to do this easily using a clean API.

It's in JCenter and Maven Central.

Using it:

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

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

If any entry of the map does not match the annotation declared field(s) and type(s), an Exception is thrown. If any value that has no default value is missing, an Exception is thrown.

This makes it possible to assume every annotation instance that is created successfully is as safe to use as a compile-time annotation instance.

As a bonus, this lib can also parse annotation classes and return the values of the annotation as a Map:

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

This is convenient for creating mini-frameworks.

참고URL : https://stackoverflow.com/questions/1635108/adding-java-annotations-at-runtime

반응형