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
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());
}
}
}
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.
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
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.
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
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
}
}
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ć.
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.
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.
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.
Odpowiedzi:
Szukałem od jakiegoś czasu i wydaje się, że są różne podejścia, oto podsumowanie:
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);
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ć
Pet
jako ServiceProviderInterface (SPI) i zadeklarować jego implementacje. to zrobić tworząc plikresources/META-INF/services
z nazwąexamples.reflections.Pet
i zadeklarować wszystkie implementacjePet
w nimAdnotacja 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.java
wewną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.
źródło
Co powiedział Erickson, ale jeśli nadal chcesz to zrobić, spójrz na Refleksje . Z ich strony:
źródło
new Reflections("my.package").getSubTypesOf(MyInterface.class)
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
ServiceLoader
w Javie 6, lub zaimplementować własną w poprzednich wersjach. Podałem przykład w innej odpowiedzi.źródło
META-INF/services/java.sql.Driver
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,
ITask
a Spring zapełni ją wszystkimi implementacjami:@Service public class TaskService { @Autowired private List<ITask> tasks; }
źródło
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 } }
źródło
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
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 }
źródło
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 zClassInfo
obiektu.Tak, pierwszym krokiem jest zidentyfikowanie „wszystkich” zajęć, na których Ci zależało. Jeśli masz już te informacje, możesz wyliczyć każdy z nich i użyć instanceof, aby sprawdzić poprawność relacji. Powiązany artykuł jest tutaj: https://web.archive.org/web/20100226233915/www.javaworld.com/javaworld/javatips/jw-javatip113.html
źródło
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,
ITask
a 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(); } }
źródło
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.
źródło
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.
źródło