Skanowanie adnotacji Java w czasie wykonywania [zamknięte]

253

Jaki jest najlepszy sposób przeszukiwania całej ścieżki klasy dla klasy z adnotacjami?

Robię bibliotekę i chcę pozwolić użytkownikom na dodawanie adnotacji do swoich klas, więc kiedy uruchomi się aplikacja sieci Web, muszę przeskanować całą ścieżkę klasy w poszukiwaniu określonych adnotacji.

Czy znasz bibliotekę lub narzędzie Java, aby to zrobić?

Edycja: Myślę o czymś takim, jak nowa funkcjonalność Java EE 5 Web Services lub EJB. Adnotujesz swoją klasę za pomocą @WebServicelub, @EJBa system znajduje te klasy podczas ładowania, aby były dostępne zdalnie.

Alotor
źródło

Odpowiedzi:

210

Użyj org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider

API

Dostawca komponentów, który skanuje ścieżkę klasy z pakietu podstawowego. Następnie stosuje filtry wykluczania i dołączania do klas wynikowych w celu znalezienia kandydatów.

ClassPathScanningCandidateComponentProvider scanner =
new ClassPathScanningCandidateComponentProvider(<DO_YOU_WANT_TO_USE_DEFALT_FILTER>);

scanner.addIncludeFilter(new AnnotationTypeFilter(<TYPE_YOUR_ANNOTATION_HERE>.class));

for (BeanDefinition bd : scanner.findCandidateComponents(<TYPE_YOUR_BASE_PACKAGE_HERE>))
    System.out.println(bd.getBeanClassName());
Arthur Ronald
źródło
5
Dzięki za informację. Czy wiesz także, jak skanować ścieżkę klasy w poszukiwaniu klas, których pola mają niestandardowe adnotacje?
Javatar,
6
@Javatar Użyj interfejsu API odbicia Java. <YOUR_CLASS> .class.getFields () Dla każdego pola wywołaj getAnnotation (<YOUR_ANNOTATION>)
Arthur Ronald
1
UWAGA: Jeśli robisz to w aplikacji Spring, Spring nadal będzie oceniać i działać na podstawie @Conditionaladnotacji. Jeśli więc klasa ma @Conditionalwartość false, nie zostanie zwrócona findCandidateComponents, nawet jeśli pasuje do filtra skanera. To mnie dzisiaj rzuciło - zamiast tego skorzystałem z rozwiązania Jonathana poniżej.
Adam Burley,
1
@ArthurRonald Przepraszamy, Arthur. Mam na myśli, że BeanDefinitionobiekt nie zapewnia bezpośredniego pobrania klasy. Wydaje się, że najbliższe jest getBeanClassNamezwrócenie w pełni kwalifikowanej nazwy klasy, ale dokładne zachowanie tej metody nie jest jasne. Ponadto nie jest jasne, w którym module ładującym została znaleziona klasa.
Maks.
3
@Max Spróbuj tego: Class<?> cl = Class.forName(beanDef.getBeanClassName()); farenda.com/spring/find-annotated-classes
James Watkins
149

Innym rozwiązaniem są odbicia Google .

Szybki przegląd:

  • Rozwiązanie wiosenne jest dobrym rozwiązaniem, jeśli używasz Spring. W przeciwnym razie jest to duża zależność.
  • Korzystanie bezpośrednio z ASM jest nieco kłopotliwe.
  • Bezpośrednie używanie Java Assist również jest niezręczne.
  • Annovention jest super lekki i wygodny. Nie ma jeszcze integracji z maven.
  • Refleksje Google przyciągają kolekcje Google. Indeksuje wszystko, a następnie jest super szybki.
Jonathan
źródło
43
new Reflections("my.package").getTypesAnnotatedWith(MyAnnotation.class). co ty.
zapp
4
czy muszę podać nazwę pakietu? dzika karta? co dla wszystkich klas w ścieżce klas?
Sunnyday,
1
Strzeż się, że nadal nie ma postępu w zakresie obsługi Java 9: github.com/ronmamo/reflections/issues/186
Vadzim
biblioteka org.reflections nie działa bezpośrednio pod java 13 (być może także wcześniej). Przy pierwszym wywołaniu wydaje się być w porządku. kolejne wystąpienia i zastosowania nie powiedzą, że adresy URL wyszukiwania nie są skonfigurowane.
Evvo
44

Możesz znaleźć klasy z dowolną adnotacją za pomocą ClassGraph , a także wyszukać inne kryteria zainteresowania, np. Klasy, które implementują dany interfejs. (Oświadczenie, jestem autorem ClassGraph.) ClassGraph może zbudować abstrakcyjną reprezentację całego wykresu klas (wszystkich klas, adnotacji, metod, parametrów metod i pól) w pamięci, dla wszystkich klas na ścieżce klas lub dla klas w pakiety umieszczone na białej liście i możesz wyszukiwać wykresy klas w dowolny sposób. ClassGraph obsługuje więcej mechanizmów specyfikacji ścieżek klas i programów ładujących klasy niż jakikolwiek inny skaner, a także bezproblemowo współpracuje z nowym systemem modułów JPMS, więc jeśli oprzesz swój kod na ClassGraph, Twój kod będzie maksymalnie przenośny. Zobacz API tutaj.

Luke Hutchison
źródło
1
Czy to wymaga Java 8 do uruchomienia?
David George
1
Zaktualizowano, aby używać Java7, nie ma problemu. Wystarczy usunąć annoations i przekonwertować funkcje, aby korzystać z anonimowych klas wewnętrznych. Podoba mi się styl 1 pliku. Kod jest ładny i czysty, więc nawet jeśli nie obsługuje kilku rzeczy, które chciałbym (klasa + adnotacja w tym samym czasie), myślę, że byłoby to cholernie łatwe do dodania. Świetna robota! Jeśli ktoś nie jest w stanie wykonać pracy, aby zmodyfikować wersję 7, prawdopodobnie powinien to zrobić Reflections. Ponadto, jeśli używasz guava / etc i chcesz zmienić kolekcje, proste jak ciasto. Świetne komentarze również w środku.
Andrew Backer,
2
@Alexandros dzięki, powinieneś sprawdzić ClassGraph, jest znacznie ulepszony w stosunku do FastClasspathScanner.
Luke Hutchison
2
@AndrewBacker ClassGraph (nowa wersja FastClasspathScanner) ma pełną obsługę operacji logicznych, poprzez filtry lub operacje ustawiania. Zobacz przykłady kodu tutaj: github.com/classgraph/classgraph/wiki/Code-examples
Luke Hutchison
1
@Luke Hutchison Już korzystam z ClassGraph. Pomógł mi w migracji do Java 10. Naprawdę przydatna biblioteka.
Alexandros,
24

Jeśli chcesz naprawdę lekkiej wagi (brak zależności, prosty interfejs API, plik jar 15 kb) i bardzo szybkiego rozwiązania, spójrz annotation-detectorna stronę https://github.com/rmuller/infomas-asl

Oświadczenie: Jestem autorem.

rmuller
źródło
20

Za pomocą interfejsu API Java Pluggable Annotation Processing API można napisać procesor adnotacji, który zostanie wykonany podczas procesu kompilacji i zgromadzi wszystkie klasy adnotacji i zbuduje plik indeksu na potrzeby środowiska wykonawczego.

Jest to najszybszy możliwy sposób na wykrycie klasy z adnotacjami, ponieważ nie trzeba skanować ścieżki klasy w czasie wykonywania, co zwykle jest bardzo powolne. To podejście działa również z dowolnym modułem ładującym klasy, a nie tylko z URLClassLoaders zwykle obsługiwanym przez skanery środowiska wykonawczego.

Powyższy mechanizm jest już zaimplementowany w bibliotece ClassIndex .

Aby z niego skorzystać, dodaj adnotację niestandardową za pomocą meta-adnotacji @IndexAnnotated . W czasie kompilacji zostanie utworzony plik indeksu: META-INF / annotations / com / test / YourCustomAnnotation zawierający wszystkie klasy z adnotacjami. Dostęp do indeksu można uzyskać w czasie wykonywania, wykonując:

ClassIndex.getAnnotated(com.test.YourCustomAnnotation.class)
Sławek
źródło
5

Czy jest już za późno na odpowiedź? Powiedziałbym, że lepiej, aby przejść przez biblioteki jak ClassPathScanningCandidateComponentProvider lub jak Scannotations

Ale nawet po tym, jak ktoś chce spróbować z ClassLoaderem, napisałem sam, aby wydrukować adnotacje z zajęć w pakiecie:

public class ElementScanner {

public void scanElements(){
    try {
    //Get the package name from configuration file
    String packageName = readConfig();

    //Load the classLoader which loads this class.
    ClassLoader classLoader = getClass().getClassLoader();

    //Change the package structure to directory structure
    String packagePath  = packageName.replace('.', '/');
    URL urls = classLoader.getResource(packagePath);

    //Get all the class files in the specified URL Path.
    File folder = new File(urls.getPath());
    File[] classes = folder.listFiles();

    int size = classes.length;
    List<Class<?>> classList = new ArrayList<Class<?>>();

    for(int i=0;i<size;i++){
        int index = classes[i].getName().indexOf(".");
        String className = classes[i].getName().substring(0, index);
        String classNamePath = packageName+"."+className;
        Class<?> repoClass;
        repoClass = Class.forName(classNamePath);
        Annotation[] annotations = repoClass.getAnnotations();
        for(int j =0;j<annotations.length;j++){
            System.out.println("Annotation in class "+repoClass.getName()+ " is "+annotations[j].annotationType().getName());
        }
        classList.add(repoClass);
    }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

/**
 * Unmarshall the configuration file
 * @return
 */
public String readConfig(){
    try{
        URL url = getClass().getClassLoader().getResource("WEB-INF/config.xml");
        JAXBContext jContext = JAXBContext.newInstance(RepositoryConfig.class);
         Unmarshaller um =  jContext.createUnmarshaller();
         RepositoryConfig rc = (RepositoryConfig) um.unmarshal(new File(url.getFile()));
         return rc.getRepository().getPackageName();
        }catch(Exception e){
            e.printStackTrace();
        }
    return null;

}
}

A w pliku konfiguracyjnym umieścisz nazwę pakietu i rozpakujesz go do klasy.

Kenobiwan
źródło
3

Za pomocą Spring możesz także napisać poniższe, używając klasy AnnotationUtils. to znaczy:

Class<?> clazz = AnnotationUtils.findAnnotationDeclaringClass(Target.class, null);

Aby uzyskać więcej informacji i wszystkie różne metody, sprawdź oficjalne dokumenty: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/core/annotation/AnnotationUtils.html

magik
źródło
4
Fajny pomysł, ale jeśli podasz nullwartość jako drugi parametr (który definiuje klasę, w której hierarchia dziedziczenia Spring będzie skanować w poszukiwaniu klasy, która korzysta z adnotacji), zawsze otrzymasz nullzwrot, zgodnie z implementacją.
jonashackt
3

Jest wspaniały komentarz zapp, który tonie we wszystkich tych odpowiedziach:

new Reflections("my.package").getTypesAnnotatedWith(MyAnnotation.class)
Strefa
źródło
2

Interfejs API Classloader nie ma metody „wyliczania”, ponieważ ładowanie klas jest działaniem „na żądanie” - zwykle w ścieżce klasy znajdują się tysiące klas, z których tylko część będzie kiedykolwiek potrzebna (rt.jar sam jest obecnie 48 MB!).

Tak więc, nawet jeśli można wyliczyć wszystkie klasy, zajęłoby to dużo czasu i pamięci.

Proste podejście polega na wypisaniu odpowiednich klas w pliku instalacyjnym (xml lub cokolwiek, co pasuje do twoich upodobań); jeśli chcesz to zrobić automatycznie, ogranicz się do jednego katalogu JAR lub jednego katalogu klas.

MFF
źródło
2

Google Reflection, jeśli chcesz odkryć również interfejsy.

Wiosna ClassPathScanningCandidateComponentProvidernie odkrywa interfejsów.

madhu_karnati
źródło
1

Google Reflections wydaje się być znacznie szybszy niż Spring. Znaleziono to żądanie funkcji uwzględniające tę różnicę: http://www.opensaga.org/jira/browse/OS-738

Jest to powód, aby używać Reflections, ponieważ czas uruchamiania mojej aplikacji jest naprawdę ważny podczas programowania. Refleksje wydają się również bardzo łatwe w użyciu w moim przypadku użycia (znajdź wszystkich implementatorów interfejsu).

Martin Aubele
źródło
1
Jeśli spojrzysz na problem JIRA, tam są komentarze, że odeszli od Refleksji z powodu problemów ze stabilnością.
Wim Deblauwe,
1

Jeśli szukasz alternatywy dla odbić , chciałbym polecić Panda Utilities - AnnotationsScanner . Jest to skaner bez Guawy (Guava ma ~ 3 MB, Panda Utilities ma ~ 200kb) skaner oparty na kodzie źródłowym biblioteki odbić.

Jest również dedykowany do wyszukiwania w przyszłości. Jeśli chcesz skanować wielokrotnie dołączone źródła lub nawet udostępnić interfejs API, który pozwala komuś skanować bieżącą ścieżkę klasy, AnnotationsScannerProcessbuforuje wszystkie pobrane pliki ClassFiles, więc jest to naprawdę szybkie.

Prosty przykład AnnotationsScannerużycia:

AnnotationsScanner scanner = AnnotationsScanner.createScanner()
        .includeSources(ExampleApplication.class)
        .build();

AnnotationsScannerProcess process = scanner.createWorker()
        .addDefaultProjectFilters("net.dzikoysk")
        .fetch();

Set<Class<?>> classes = process.createSelector()
        .selectTypesAnnotatedWith(AnnotationTest.class);
dzikoysk
źródło
1

Wiosna ma coś, co nazywa się AnnotatedTypeScannerklasą.
Ta klasa używa wewnętrznie

ClassPathScanningCandidateComponentProvider

Ta klasa ma kod do rzeczywistego skanowania ścieżki klasy zasobów . Robi to za pomocą metadanych klasy dostępnych w czasie wykonywania.

Można po prostu rozszerzyć tę klasę lub użyć tej samej klasy do skanowania. Poniżej znajduje się definicja konstruktora.

/**
     * Creates a new {@link AnnotatedTypeScanner} for the given annotation types.
     * 
     * @param considerInterfaces whether to consider interfaces as well.
     * @param annotationTypes the annotations to scan for.
     */
    public AnnotatedTypeScanner(boolean considerInterfaces, Class<? extends Annotation>... annotationTypes) {

        this.annotationTypess = Arrays.asList(annotationTypes);
        this.considerInterfaces = considerInterfaces;
    }
swayamraina
źródło