Wie kann ich programmgesteuert eine Liste aller Implementierungen einer Schnittstelle in Java erhalten?

82

Kann ich es mit Reflexion oder so machen?

user2427
quelle
Ist es möglich, dies mit der Abkürzung einer magischen Eclipse zu tun? ?
Tomasz Waszczyk
4
Bitte erwägen Sie die Auswahl einer Antwort.
Please_Dont_Bully_Me_SO_Lords

Antworten:

56

Ich habe eine Weile gesucht und es scheint verschiedene Ansätze zu geben, hier eine Zusammenfassung:

  1. 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);
    
  2. 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
    
  3. 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());
            }
        }
    }
    

    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.javain 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.

Ahmad Abdelghany
quelle
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
  • Lassen Sie alle Methoden mit einigen kommentieren
Peter Severin
quelle
12
genauer:new Reflections("my.package").getSubTypesOf(MyInterface.class)
Zapp
27

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 .

erickson
quelle
4
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:

public interface ITask {
    void doStuff();
}

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

Anschließend können Sie eine Typliste automatisch verdrahten, ITaskund Spring füllt sie mit allen Implementierungen:

@Service
public class TaskService {

    @Autowired
    private List<ITask> tasks;
}
kaybee99
quelle
4
Nicht genau, Spring wird alle Bohnen vom Typ ITask bevölkern, was nicht ganz gleich ist.
Olivier Gérardin
5

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
    }
}
Luke Hutchison
quelle
1
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

Zhichao
quelle
4

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
4

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
}
Verglor
quelle
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:

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, ITaskund 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();
    }   
}
Please_Dont_Bully_Me_SO_Lords
quelle
1

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.

Uri
quelle
1

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.

NDK
quelle