2009-10-28 22 views
60

Czy jest możliwe dodanie adnotacji do obiektu (w moim przypadku w szczególności Metoda) w czasie wykonywania?Dodawanie adnotacji Java w środowisku wykonawczym

Dla nieco więcej wyjaśnień: Mam dwa moduły, moduł A i moduł B. moduł B zależy od modułu A, który nie zależy od niczego. (modA to moje podstawowe typy i interfejsy, a modB to warstwa db/data) modB również zależy od biblioteki externalLibrary. W moim przypadku modB przekazuje klasę od modA do biblioteki externalLibrary, która wymaga adnotacji określonych metod. Konkretne adnotacje są częścią externalLib i, jak powiedziałem, modA nie zależy od externalLib i chciałbym zachować to w ten sposób.

Czy jest to możliwe, czy też masz sugestie dotyczące innych sposobów patrzenia na ten problem?

+0

Sprawdź to może być pomocna http://stackoverflow.com/a/14276270/4741746 przynajmniej możemy go zmodyfikować –

Odpowiedz

21

Nie można dodać adnotacji w czasie wykonywania, wydaje się, że trzeba wprowadzić adapter, której moduł B używa do owinięcia obiektu z modułu A odsłaniając wymagane metody z adnotacjami.

+1

Ja drugi tego. Ale mogę rozważyć przypisanie oryginału, nie widzę tutaj dużego problemu. Zwykle robimy to w przypadku jednostek JPA, które przechodzą do zdalnego komponentu EJB do przechowywania w DB. Używasz tego samego, aby zapełnić swój interfejs użytkownika. –

+0

Tom: Ach, oczywiście. Być może z dziedziczeniem: Rozszerz klasę z modułu A, pomiń tę metodę, a następnie dodaj adnotację? – Clayton

+0

Ocet: to chyba najłatwiejsze rozwiązanie dla mnie. Starałem się, aby mój "model danych" był oddzielony od mojej "implementacji danych", ale szczerze mówiąc, nie widzę czasu, w którym potrzebowałbym zastosować inną implementację danych. – Clayton

38

Jest to możliwe przez bibliotekę oprzyrządowania kodu bajtowego, taką jak Javassist.

W szczególności przyjrzyj się klasie AnnotationsAttribute, aby dowiedzieć się, jak tworzyć/ustawiać adnotacje i tutorial section on bytecode API, aby uzyskać ogólne wskazówki dotyczące manipulowania plikami klas.

Nie jest to jednak proste i proste - NIE polecam tego podejścia i sugeruję, abyś rozważył odpowiedź Toma, chyba że musisz to zrobić dla ogromnej liczby zajęć (lub że zajęcia nie są dostępne do runtime i tym samym zapisywanie adaptera jest niemożliwe).

12

Możliwe jest również dodanie adnotacji do klasy Java w środowisku wykonawczym za pomocą interfejsu API Java reflection. Zasadniczo należy odtworzyć wewnętrzne mapy adnotacji zdefiniowane w klasie java.lang.Class (lub dla Java 8 zdefiniowanej w wewnętrznej klasie java.lang.Class.AnnotationData). Naturalnie takie podejście jest dość hackowe i może przerwać w dowolnym momencie nowsze wersje Javy. Ale dla szybkiego i brudnego testowania/prototypowania takie podejście może być przydatne czasami.

proove przykładu koncepcji for Java 8: Przykład

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

Zastosowanie:

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

wyjściowa:

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

Ograniczenia tego podejścia:

  • Nowe wersje Javy mogą w każdej chwili przerwać kod.
  • Powyższy przykład działa tylko w przypadku języka Java 8 - sprawdzenie, czy działa dla starszych wersji Javy, wymagałoby sprawdzenia wersji Java w środowisku wykonawczym i odpowiedniej zmiany implementacji.
  • Jeśli przypisana klasa otrzyma redefined (np. Podczas debugowania), adnotacja zostanie utracona.
  • Niezupełnie przetestowane; nie wiem, czy są jakieś złe skutki uboczne - wykorzystanie na własne ryzyko ...
+0

Dobra robota, byłbym bardzo wdzięczny, aby działał z Javą 1.7, może ta mapa jest pomocna: http://grepcode.com/file/repository .grepcode.com/java/root/jdk/openjdk/7u40-b43/java/lang/Class.java # Class.0annotations – gouessej

+1

To jest niesamowite: D Dziękuję za wspaniałą pracę –

+0

Wszelkie sugestie, jak uzyskać to do pracy dla pola? – heez

1

Możliwe jest tworzenie adnotacji w czasie wykonywania przez Proxy.Możesz następnie dodać je do swoich obiektów Java poprzez odbicie, co sugerują inne odpowiedzi (ale prawdopodobnie lepiej byłoby znaleźć alternatywny sposób na obsłużenie tego, ponieważ zakłócanie istniejących typów przez odbicie może być niebezpieczne i trudne do debugowania).

Ale to nie jest łatwe ... Napisałem bibliotekę o nazwie, mam nadzieję, że odpowiednio, Javanna tylko po to, aby to zrobić za pomocą czystego API.

Jest w JCenter i Maven Central.

Używanie go:

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

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

Jeżeli którykolwiek wpis mapy nie odpowiada deklarowanej pole (y) adnotacji i typ (-y), zgłaszany jest wyjątek. Jeśli brakuje wartości, która nie ma wartości domyślnej, zostanie zgłoszony wyjątek.

Dzięki temu można założyć, że każda instancja adnotacji, która została utworzona pomyślnie, jest tak samo bezpieczna, jak instancja adnotacji podczas kompilacji.

Jako bonus, to lib może również analizować klas adnotacji i zwrot wartości adnotacji jako mapą:

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

Jest to wygodne do tworzenia mini-ram.

Powiązane problemy