Jak uzyskać listę wszystkich implementacji interfejsu w języku Java? [Zamknięte]

81

Czy mogę to zrobić z refleksją lub czymś w tym rodzaju?

user2427
źródło
Czy można to zrobić jakimś magicznym skrótem Eclipse? ?
Tomasz Waszczyk
4
Rozważ wybranie odpowiedzi.
Please_Dont_Bully_Me_SO_Lords

Odpowiedzi:

58

Szukałem od jakiegoś czasu i wydaje się, że są różne podejścia, oto podsumowanie:

  1. Biblioteka odbić jest dość popularna, jeśli nie masz nic przeciwko dodaniu zależności. Wyglądałoby to tak:

    Reflections reflections = new Reflections("firstdeveloper.examples.reflections");
    Set<Class<? extends Pet>> classes = reflections.getSubTypesOf(Pet.class);
    
  2. ServiceLoader (zgodnie z odpowiedzią Ericksona) i wyglądałoby to tak:

    ServiceLoader<Pet> loader = ServiceLoader.load(Pet.class);
    for (Pet implClass : loader) {
        System.out.println(implClass.getClass().getSimpleName()); // prints Dog, Cat
    }
    

    Zauważ, że aby to zadziałało, musisz zdefiniować Petjako ServiceProviderInterface (SPI) i zadeklarować jego implementacje. to zrobić tworząc plik resources/META-INF/servicesz nazwą examples.reflections.Peti zadeklarować wszystkie implementacje Petw nim

    examples.reflections.Dog
    examples.reflections.Cat
    
  3. Adnotacja na poziomie pakietu . Oto przykład:

    Package[] packages = Package.getPackages();
    for (Package p : packages) {
        MyPackageAnnotation annotation = p.getAnnotation(MyPackageAnnotation.class);
        if (annotation != null) {
            Class<?>[]  implementations = annotation.implementationsOfPet();
            for (Class<?> impl : implementations) {
                System.out.println(impl.getSimpleName());
            }
        }
    }
    

    i definicja adnotacji:

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.PACKAGE)
    public @interface MyPackageAnnotation {
        Class<?>[] implementationsOfPet() default {};
    }
    

    i musisz zadeklarować adnotację na poziomie pakietu w pliku o nazwie package-info.javawewnątrz tego pakietu. oto przykładowa zawartość:

    @MyPackageAnnotation(implementationsOfPet = {Dog.class, Cat.class})
    package examples.reflections;
    

    Należy zauważyć, że tylko pakiety, które są w danym momencie znane ClassLoader, zostaną załadowane przez wywołanie funkcji Package.getPackages().

Ponadto istnieją inne podejścia oparte na URLClassLoader, które zawsze będą ograniczone do klas, które zostały już załadowane, chyba że wykonasz wyszukiwanie w oparciu o katalog.

Ahmad Abdelghany
źródło
Które z trzech podejść jest wydajniejsze i najszybsze?
wiosna
@carlspring Nie mogę wypowiedzieć się absolutnie o ich względnej skuteczności, ale pierwsza alternatywa (biblioteka refleksji) zadziałała dla mnie całkiem nieźle.
Ahmad Abdelghany
Jak działa DriverManager z JDBC? Czy nie robi podobnej rzeczy (przeszukuje wszystkie implementacje interfejsu sterownika w ścieżce klas)?
Alex Semeniuk
1
@AlexSemeniuk Myślę, że teraz obsługują mechanizm Service Loader / Provider (podejście nr 2 powyżej) zgodnie z ich dokumentacją „Metody DriverManager getConnection i getDrivers zostały ulepszone tak, aby obsługiwały mechanizm dostawcy usług Java Standard Edition”. Zobacz docs.oracle.com/javase/8/docs/api/java/sql/DriverManager.html
Ahmad Abdelghany,
1
Warto zaznaczyć, że mechanizm dostawcy usług został zintegrowany z systemem modułu z Javą 9, co czyni go jeszcze wygodniejszym niż adnotacje na poziomie pakietu, tzn. Nie trzeba tworzyć własnego typu adnotacji, można deklarować implementacje w module -informacje, które mimo wszystko utworzysz podczas pisania oprogramowania modułowego i uzyskasz informacje zwrotne w czasie kompilacji dotyczące ważności implementacji.
Holger
28

Co powiedział Erickson, ale jeśli nadal chcesz to zrobić, spójrz na Refleksje . Z ich strony:

Korzystając z refleksji, możesz wyszukiwać metadane pod kątem:

  • pobierz wszystkie podtypy określonego typu
  • pobierz wszystkie typy z adnotacjami
  • pobierz wszystkie typy z adnotacjami, w tym z dopasowaniem parametrów adnotacji
  • pobierz wszystkie metody z adnotacjami
Peter Severin
źródło
12
dokładniej:new Reflections("my.package").getSubTypesOf(MyInterface.class)
zapp
27

Ogólnie rzecz biorąc, jest to kosztowne. Aby użyć odbicia, klasa musi zostać załadowana. Jeśli chcesz załadować każdą klasę dostępną w ścieżce klas, zajmie to trochę czasu i pamięci i nie jest zalecane.

Jeśli chcesz tego uniknąć, musisz zaimplementować własny parser plików klas, który działałby wydajniej, zamiast odbicia. Biblioteka inżynierii kodu bajtowego może pomóc w tym podejściu.

Mechanizm Service Provider jest konwencjonalne środki wyliczyć implementacje wtykowym usługi, a stała się bardziej powstała z wprowadzeniem projektu Jigsawa (moduły) w Java 9. Użyj ServiceLoaderw Javie 6, lub zaimplementować własną w poprzednich wersjach. Podałem przykład w innej odpowiedzi.

erickson
źródło
4
Niestety mechanizm usługodawcy wymaga od Ciebie wyszczególnienia klas, które mogą znajdować się na liście zainteresowań, w osobnym pliku, co często jest niewykonalne.
averasko
4
Czy można napisać kod źródłowy implementujący interfejs, skompilować go i wdrożyć, ale nie jest możliwe dołączenie pliku tekstowego z FQN implementacji? To rzadko, jeśli w ogóle, przypadek. Rzadko „często”.
erickson
Jak działa DriverManager z JDBC? Czy nie robi podobnej rzeczy (przeszukuje wszystkie implementacje interfejsu sterownika w ścieżce klas)?
Alex Semeniuk
1
@AlexSemeniuk Nie. Wykorzystuje mechanizm Service Loader, który opisałem powyżej; Sterownik JDBC 4.0+ musi podać swoją nazwę wMETA-INF/services/java.sql.Driver
erickson
16

Wiosna ma na to całkiem prosty sposób:

public interface ITask {
    void doStuff();
}

@Component
public class MyTask implements ITask {
   public void doStuff(){}
}

Następnie możesz autowiretować listę typów, ITaska Spring zapełni ją wszystkimi implementacjami:

@Service
public class TaskService {

    @Autowired
    private List<ITask> tasks;
}
kaybee99
źródło
4
Niezupełnie Wiosna będzie go zapełnić będą wszystkie ziarna typu iTask, co nie jest całkiem to samo.
Olivier Gérardin
5

Najbardziej niezawodnym mechanizmem wyświetlania wszystkich klas implementujących dany interfejs jest obecnie ClassGraph , ponieważ obsługuje on najszerszy możliwy zestaw mechanizmów specyfikacji ścieżek klas , w tym nowy system modułów JPMS. (Jestem autorem.)

try (ScanResult scanResult = new ClassGraph().whitelistPackages("x.y.z")
        .enableClassInfo().scan()) {
    for (ClassInfo ci : scanResult.getClassesImplementing("x.y.z.SomeInterface")) {
        foundImplementingClass(ci);  // Do something with the ClassInfo object
    }
}
Luke Hutchison
źródło
1
Biblioteka ClassGraph działa jak sharm, dziękuję @Luke Hutchison
Kyrylo Semenko
4

To, co powiedział Erikson, jest najlepsze. Oto powiązany wątek z pytaniami i odpowiedziami - http://www.velocityreviews.com/forums/t137693-find-all-implementing-classes-in-classpath.html

Biblioteka Apache BCEL umożliwia odczytywanie klas bez ich ładowania. Uważam, że będzie to szybsze, ponieważ powinieneś móc pominąć krok weryfikacji. Innym problemem związanym z ładowaniem wszystkich klas za pomocą modułu ładującego klasy jest to, że będziesz miał ogromny wpływ na pamięć, a także nieumyślnie uruchomisz jakiekolwiek statyczne bloki kodu, których prawdopodobnie nie chcesz robić.

Link do biblioteki Apache BCEL - http://jakarta.apache.org/bcel/


źródło
4

Dzięki ClassGraph jest to całkiem proste:

Groovy kod, aby znaleźć implementacje my.package.MyInterface:

@Grab('io.github.classgraph:classgraph:4.6.18')
import io.github.classgraph.*
new ClassGraph().enableClassInfo().scan().withCloseable { scanResult ->
    scanResult.getClassesImplementing('my.package.MyInterface').findAll{!it.abstract}*.name
}
verglor
źródło
Musisz zadzwonić scan().withCloseable { ... }w Groovy lub użyć try-with-resources w Javie: github.com/classgraph/classgraph/wiki/ ... Ponadto ostatnia część powinna być .name, nie .className, ponieważ .getName()jest to właściwa metoda na uzyskanie nazwy klasy z ClassInfoobiektu.
Luke Hutchison
2

Nowa wersja odpowiedzi @ kaybee99, ale teraz zwraca to, o co pyta użytkownik: implementacje ...

Wiosna ma na to całkiem prosty sposób:

public interface ITask {
    void doStuff();
    default ITask getImplementation() {
       return this;
    }

}

@Component
public class MyTask implements ITask {
   public void doStuff(){}
}

Następnie możesz autowiretować listę typów, ITaska Spring zapełni ją wszystkimi implementacjami:

@Service
public class TaskService {

    @Autowired(required = false)
    private List<ITask> tasks;

    if ( tasks != null)
    for (ITask<?> taskImpl: tasks) {
        taskImpl.doStuff();
    }   
}
Please_Dont_Bully_Me_SO_Lords
źródło
1

Ponadto, jeśli piszesz wtyczkę IDE (gdzie to, co próbujesz zrobić, jest stosunkowo powszechne), to IDE zwykle oferuje bardziej wydajne sposoby dostępu do hierarchii klas bieżącego stanu kodu użytkownika.

Uri
źródło
1

Napotkałem ten sam problem. Moim rozwiązaniem było użycie odbicia do zbadania wszystkich metod w klasie ObjectFactory, eliminując te, które nie były metodami createXXX (), zwracając wystąpienie jednego z moich powiązanych obiektów POJO. Każda tak odkryta klasa jest dodawana do tablicy Class [], która jest następnie przekazywana do wywołania instancji JAXBContext. Działa to dobrze, wystarczy załadować klasę ObjectFactory, która i tak miała być potrzebna. Muszę tylko utrzymywać klasę ObjectFactory, zadanie wykonywane ręcznie (w moim przypadku, ponieważ zacząłem od POJO i użyłem schematu) lub może być wygenerowane w razie potrzeby przez xjc. Tak czy inaczej, jest wydajny, prosty i skuteczny.

NDK
źródło