ServiceLoader (gemäß erickson Antwort) und es würde so aussehen:
ServiceLoader<Pet> loader = ServiceLoader.load(Pet.class);
for (Pet implClass : loader) {
System.out.println(implClass.getClass().getSimpleName()); // prints Dog, Cat
}
Beachten Sie, dass Sie dies Petals ServiceProviderInterface (SPI) definieren und dessen Implementierungen deklarieren müssen, damit dies funktioniert . Sie tun das , indem eine Datei bei der Erstellung resources/META-INF/servicesmit dem Namen examples.reflections.Petund erklären alle Implementierungen Petdarin
examples.reflections.Dog
examples.reflections.Cat
Annotation auf Paketebene . Hier ist ein Beispiel:
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());
}
}
}
Beachten Sie, dass nur Pakete, die dem ClassLoader zu diesem Zeitpunkt bekannt sind, durch einen Aufruf von geladen werden Package.getPackages().
Darüber hinaus gibt es andere auf URLClassLoader basierende Ansätze, die immer auf bereits geladene Klassen beschränkt sind, es sei denn, Sie führen eine verzeichnisbasierte Suche durch.
Welcher der drei Ansätze ist effizienter und schneller?
Carlspring
@carlspring Ich kann keine absolute Aussage über ihre relative Effizienz machen, aber die erste Alternative (Reflexionsbibliothek) hat für mich ziemlich gut funktioniert.
Ahmad Abdelghany
Wie funktioniert DriverManager von JDBC? Tut es nicht das Gleiche (Durchsuchen aller Implementierungen der Treiberschnittstelle im Klassenpfad)?
Alex Semeniuk
1
@AlexSemeniuk Ich denke, sie unterstützen jetzt den Service Loader / Provider-Mechanismus (Ansatz 2 oben) gemäß ihren Dokumenten. "Die DriverManager-Methoden getConnection und getDrivers wurden erweitert, um den Java Standard Edition-Service Provider-Mechanismus zu unterstützen." Siehe docs.oracle.com/javase/8/docs/api/java/sql/DriverManager.html
Ahmad Abdelghany
1
Es ist erwähnenswert, dass der Dienstanbietermechanismus mit Java 9 in das Modulsystem integriert wurde, was ihn noch komfortabler macht als Annotationen auf Paketebene, dh Sie müssen keinen eigenen Annotationstyp erstellen, um die Implementierungen im Modul zu deklarieren -info erstellen Sie trotzdem, wenn Sie modulare Software schreiben, und erhalten Feedback zur Kompilierungszeit bezüglich der Gültigkeit der Implementierung.
Holger
28
Was erickson gesagt hat, aber wenn Sie es trotzdem tun möchten, schauen Sie sich Reflections an . Von ihrer Seite:
Mit Reflections können Sie Ihre Metadaten abfragen nach:
Holen Sie sich alle Untertypen eines Typs
Alle Typen mit Anmerkungen versehen
Lassen Sie alle Typen mit Anmerkungen versehen, einschließlich der Übereinstimmung der Anmerkungsparameter
Im Allgemeinen ist dies teuer. Um Reflection verwenden zu können, muss die Klasse geladen werden. Wenn Sie jede im Klassenpfad verfügbare Klasse laden möchten, nimmt dies Zeit und Speicher in Anspruch und wird nicht empfohlen.
Wenn Sie dies vermeiden möchten, müssen Sie einen eigenen Parser für Klassendateien implementieren, der effizienter arbeitet als Reflexion. Eine Bytecode-Engineering-Bibliothek kann bei diesem Ansatz hilfreich sein.
Der Service Provider-Mechanismus ist das herkömmliche Mittel zum Auflisten von Implementierungen eines steckbaren Dienstes und hat sich mit der Einführung von Project Jigsaw (Modulen) in Java 9 etabliert. Verwenden Sie den ServiceLoaderin Java 6 oder implementieren Sie Ihren eigenen in früheren Versionen. Ich habe in einer anderen Antwort ein Beispiel angegeben .
Leider müssen Sie für den Service Provider-Mechanismus die Klassen, die möglicherweise in der Liste der Interessen enthalten sind, in einer separaten Datei auflisten, was häufig nicht möglich ist.
Averasko
4
Es ist möglich, Quellcode zu schreiben, der eine Schnittstelle implementiert, kompiliert und bereitstellt, aber nicht möglich, eine Textdatei in den FQN der Implementierung aufzunehmen. Das ist selten, wenn überhaupt, der Fall. Kaum "oft".
Erickson
Wie funktioniert DriverManager von JDBC? Tut es nicht das Gleiche (Durchsuchen aller Implementierungen der Treiberschnittstelle im Klassenpfad)?
Alex Semeniuk
1
@AlexSemeniuk Nein, es wird der oben beschriebene Service Loader-Mechanismus verwendet. Ein JDBC 4.0+ Treiber muss seinen Namen inMETA-INF/services/java.sql.Driver
erickson
14
Der Frühling hat eine ziemlich einfache Möglichkeit, dies zu erreichen:
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
}
}
Die ClassGraph-Bibliothek funktioniert wie ein Sharm, danke @Luke Hutchison
Kyrylo Semenko
4
Ja, der erste Schritt besteht darin, "alle" Klassen zu identifizieren, die Sie interessiert haben. Wenn Sie diese Informationen bereits haben, können Sie sie auflisten und mit instanceof die Beziehung überprüfen. Ein verwandter Artikel ist hier: http://www.javaworld.com/javaworld/javatips/jw-javatip113.html
Mit der Apache BCEL-Bibliothek können Sie Klassen lesen, ohne sie zu laden. Ich glaube, es wird schneller gehen, da Sie den Überprüfungsschritt überspringen sollten. Das andere Problem beim Laden aller Klassen mit dem Klassenladeprogramm besteht darin, dass Sie eine enorme Speicherbelastung erleiden und versehentlich statische Codeblöcke ausführen, die Sie wahrscheinlich nicht möchten.
Sie müssen scan().withCloseable { ... }Groovy aufrufen oder Try-with-Resources in Java verwenden: github.com/classgraph/classgraph/wiki/… Auch der letzte Teil sollte .namenicht sein .className, da dies .getName()die richtige Methode ist, um den Namen einer Klasse abzurufen von einem ClassInfoObjekt.
Luke Hutchison
2
Eine neue Version der Antwort von @ kaybee99, die nun jedoch zurückgibt, was der Benutzer verlangt: die Implementierungen ...
Der Frühling hat eine ziemlich einfache Möglichkeit, dies zu erreichen:
Wenn Sie ein IDE-Plugin schreiben (wo das, was Sie versuchen, relativ häufig ist), bietet Ihnen die IDE in der Regel effizientere Möglichkeiten, auf die Klassenhierarchie des aktuellen Status des Benutzercodes zuzugreifen.
Ich bin auf dasselbe Problem gestoßen. Meine Lösung bestand darin, mithilfe von Reflection alle Methoden in einer ObjectFactory-Klasse zu untersuchen und diejenigen zu eliminieren, bei denen es sich nicht um createXXX () -Methoden handelte, die eine Instanz eines meiner gebundenen POJOs zurückgeben. Jede so entdeckte Klasse wird einem Class [] -Array hinzugefügt, das dann an den JAXBContext-Instanziierungsaufruf übergeben wurde. Dies funktioniert gut, da nur die ObjectFactory-Klasse geladen werden muss, die ohnehin benötigt wurde. Ich muss nur die ObjectFactory-Klasse verwalten, eine Aufgabe, die entweder von Hand ausgeführt wird (in meinem Fall, weil ich mit POJOs begonnen und Schema verwendet habe) oder von xjc nach Bedarf generiert werden kann. In jedem Fall ist es performant, einfach und effektiv.
Antworten:
Ich habe eine Weile gesucht und es scheint verschiedene Ansätze zu geben, hier eine Zusammenfassung:
Die Reflexionsbibliothek ist sehr beliebt, wenn es Ihnen nichts ausmacht, die Abhängigkeit hinzuzufügen. Es würde so aussehen:
Reflections reflections = new Reflections("firstdeveloper.examples.reflections"); Set<Class<? extends Pet>> classes = reflections.getSubTypesOf(Pet.class);
ServiceLoader (gemäß erickson Antwort) und es würde so aussehen:
ServiceLoader<Pet> loader = ServiceLoader.load(Pet.class); for (Pet implClass : loader) { System.out.println(implClass.getClass().getSimpleName()); // prints Dog, Cat }
Beachten Sie, dass Sie dies
Pet
als ServiceProviderInterface (SPI) definieren und dessen Implementierungen deklarieren müssen, damit dies funktioniert . Sie tun das , indem eine Datei bei der Erstellungresources/META-INF/services
mit dem Namenexamples.reflections.Pet
und erklären alle ImplementierungenPet
darinAnnotation auf Paketebene . Hier ist ein Beispiel:
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()); } } }
und die Annotationsdefinition:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PACKAGE) public @interface MyPackageAnnotation { Class<?>[] implementationsOfPet() default {}; }
und Sie müssen die Annotation auf Paketebene in einer Datei mit dem Namen
package-info.java
in diesem Paket deklarieren . Hier sind Beispielinhalte:@MyPackageAnnotation(implementationsOfPet = {Dog.class, Cat.class}) package examples.reflections;
Beachten Sie, dass nur Pakete, die dem ClassLoader zu diesem Zeitpunkt bekannt sind, durch einen Aufruf von geladen werden
Package.getPackages()
.Darüber hinaus gibt es andere auf URLClassLoader basierende Ansätze, die immer auf bereits geladene Klassen beschränkt sind, es sei denn, Sie führen eine verzeichnisbasierte Suche durch.
quelle
Was erickson gesagt hat, aber wenn Sie es trotzdem tun möchten, schauen Sie sich Reflections an . Von ihrer Seite:
quelle
new Reflections("my.package").getSubTypesOf(MyInterface.class)
Im Allgemeinen ist dies teuer. Um Reflection verwenden zu können, muss die Klasse geladen werden. Wenn Sie jede im Klassenpfad verfügbare Klasse laden möchten, nimmt dies Zeit und Speicher in Anspruch und wird nicht empfohlen.
Wenn Sie dies vermeiden möchten, müssen Sie einen eigenen Parser für Klassendateien implementieren, der effizienter arbeitet als Reflexion. Eine Bytecode-Engineering-Bibliothek kann bei diesem Ansatz hilfreich sein.
Der Service Provider-Mechanismus ist das herkömmliche Mittel zum Auflisten von Implementierungen eines steckbaren Dienstes und hat sich mit der Einführung von Project Jigsaw (Modulen) in Java 9 etabliert. Verwenden Sie den
ServiceLoader
in Java 6 oder implementieren Sie Ihren eigenen in früheren Versionen. Ich habe in einer anderen Antwort ein Beispiel angegeben .quelle
META-INF/services/java.sql.Driver
Der Frühling hat eine ziemlich einfache Möglichkeit, dies zu erreichen:
public interface ITask { void doStuff(); } @Component public class MyTask implements ITask { public void doStuff(){} }
Anschließend können Sie eine Typliste automatisch verdrahten,
ITask
und Spring füllt sie mit allen Implementierungen:@Service public class TaskService { @Autowired private List<ITask> tasks; }
quelle
Der robusteste Mechanismus zum Auflisten aller Klassen, die eine bestimmte Schnittstelle implementieren, ist derzeit ClassGraph , da er ein möglichst breites Spektrum von Klassenpfadspezifikationsmechanismen verarbeitet , einschließlich des neuen JPMS-Modulsystems. (Ich bin der Autor.)
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 } }
quelle
Ja, der erste Schritt besteht darin, "alle" Klassen zu identifizieren, die Sie interessiert haben. Wenn Sie diese Informationen bereits haben, können Sie sie auflisten und mit instanceof die Beziehung überprüfen. Ein verwandter Artikel ist hier: http://www.javaworld.com/javaworld/javatips/jw-javatip113.html
quelle
Was Erikson sagte, ist das Beste. Hier ist ein verwandter Frage- und Antwort-Thread - http://www.velocityreviews.com/forums/t137693-find-all-implementing-classes-in-classpath.html
Mit der Apache BCEL-Bibliothek können Sie Klassen lesen, ohne sie zu laden. Ich glaube, es wird schneller gehen, da Sie den Überprüfungsschritt überspringen sollten. Das andere Problem beim Laden aller Klassen mit dem Klassenladeprogramm besteht darin, dass Sie eine enorme Speicherbelastung erleiden und versehentlich statische Codeblöcke ausführen, die Sie wahrscheinlich nicht möchten.
Der Link zur Apache BCEL-Bibliothek - http://jakarta.apache.org/bcel/
quelle
Mit ClassGraph ist es ziemlich einfach:
Grooviger Code zum Finden von Implementierungen von
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 }
quelle
scan().withCloseable { ... }
Groovy aufrufen oder Try-with-Resources in Java verwenden: github.com/classgraph/classgraph/wiki/… Auch der letzte Teil sollte.name
nicht sein.className
, da dies.getName()
die richtige Methode ist, um den Namen einer Klasse abzurufen von einemClassInfo
Objekt.Eine neue Version der Antwort von @ kaybee99, die nun jedoch zurückgibt, was der Benutzer verlangt: die Implementierungen ...
Der Frühling hat eine ziemlich einfache Möglichkeit, dies zu erreichen:
public interface ITask { void doStuff(); default ITask getImplementation() { return this; } } @Component public class MyTask implements ITask { public void doStuff(){} }
Anschließend können Sie eine Typliste automatisch verdrahten,
ITask
und Spring füllt sie mit allen Implementierungen:@Service public class TaskService { @Autowired(required = false) private List<ITask> tasks; if ( tasks != null) for (ITask<?> taskImpl: tasks) { taskImpl.doStuff(); } }
quelle
Wenn Sie ein IDE-Plugin schreiben (wo das, was Sie versuchen, relativ häufig ist), bietet Ihnen die IDE in der Regel effizientere Möglichkeiten, auf die Klassenhierarchie des aktuellen Status des Benutzercodes zuzugreifen.
quelle
Ich bin auf dasselbe Problem gestoßen. Meine Lösung bestand darin, mithilfe von Reflection alle Methoden in einer ObjectFactory-Klasse zu untersuchen und diejenigen zu eliminieren, bei denen es sich nicht um createXXX () -Methoden handelte, die eine Instanz eines meiner gebundenen POJOs zurückgeben. Jede so entdeckte Klasse wird einem Class [] -Array hinzugefügt, das dann an den JAXBContext-Instanziierungsaufruf übergeben wurde. Dies funktioniert gut, da nur die ObjectFactory-Klasse geladen werden muss, die ohnehin benötigt wurde. Ich muss nur die ObjectFactory-Klasse verwalten, eine Aufgabe, die entweder von Hand ausgeführt wird (in meinem Fall, weil ich mit POJOs begonnen und Schema verwendet habe) oder von xjc nach Bedarf generiert werden kann. In jedem Fall ist es performant, einfach und effektiv.
quelle