web-dev-qa-db-de.com

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

Kann ich das mit Nachdenken oder so machen?

59
user2427

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

  1. - reflections -Bibliothek ist sehr beliebt, wenn Sie die Abhängigkeit nicht ändern möchten. Es würde so aussehen:

    Reflections reflections = new Reflections("firstdeveloper.examples.reflections");
    Set<Class<? extends Pet>> classes = reflections.getSubTypesOf(Pet.class);
    
  2. ServiceLoader (wie 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
    }
    

    Damit dies funktioniert, müssen Sie Pet als ServiceProviderInterface (SPI) definieren und deren Implementierungen deklarieren. Dazu erstellen Sie eine Datei in resources/META-INF/services mit dem Namen examples.reflections.Pet und deklarieren alle Implementierungen von Pet darin 

    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 Anmerkungsdefinition:

    @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 innerhalb dieses Pakets angeben. 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 Package.getPackages() geladen werden.

Darüber hinaus gibt es andere auf URLClassLoader basierende Ansätze, die immer auf bereits geladene Klassen beschränkt sind, sofern Sie keine verzeichnisbasierte Suche durchführen. 

32

Was erickson gesagt hat, aber wenn Sie es trotzdem tun wollen, werfen Sie einen Blick auf Reflections . Von ihrer Seite:

Mit Reflections können Sie Ihre Metadaten abfragen:

  • holen Sie sich alle Untertypen eines Typs
  • alle Typen werden mit Anmerkungen versehen
  • alle Typen werden mit Anmerkungen versehen, einschließlich Annotationsparameter
  • alle Methoden werden mit einigen Anmerkungen versehen 
27
Peter Severin

Im Allgemeinen ist das teuer. Um die Reflektion verwenden zu können, muss die Klasse geladen werden. Wenn Sie jede auf dem Klassenpfad verfügbare Klasse laden möchten, ist dies Zeit und Speicherplatz erforderlich und wird nicht empfohlen. 

Wenn Sie dies vermeiden möchten, müssen Sie einen eigenen Klassendatei-Parser implementieren, der effizienter arbeitet als Reflektion. Bei diesem Ansatz kann eine Bytecode-Entwicklungsbibliothek hilfreich sein.

Der Service Provider-Mechanismus ist das herkömmliche Mittel, um Implementierungen eines steckbaren Services aufzulisten. Verwenden Sie die ServiceLoader in Java 6 oder implementieren Sie Ihre eigene Version in früheren Versionen. Ich habe ein Beispiel in einer anderen Antwort gegeben.

22
erickson

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(){}
}

Dann können Sie automatisch eine Liste des Typs ITask verwenden, und Spring füllt sie mit allen Implementierungen auf:

@Service
public class TaskService {

    @Autowired
    private List<ITask> tasks;
}
10
kaybee99

Was erikson gesagt hat, ist am besten. 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 sein, da Sie den Überprüfungsschritt überspringen können sollten. Das andere Problem beim Laden aller Klassen mit dem Classloader ist, dass Sie einen großen Einfluss auf den Speicher haben und versehentlich statische Codeblöcke ausführen, die Sie wahrscheinlich nicht möchten.

Der Link zur Apache BCEL-Bibliothek - http://jakarta.Apache.org/bcel/

4
Sachin

Ja, der erste Schritt besteht darin, "alle" Klassen zu identifizieren, an denen Sie interessiert waren. Wenn Sie bereits über diese Informationen verfügen, können Sie sie einzeln auflisten und die Beziehung mithilfe von instanceof überprüfen. Ein verwandter Artikel ist hier: http://www.javaworld.com/javaworld/javatips/jw-javatip113.html

4
Zhichao

Wenn Sie ein IDE -Plugin schreiben (wobei das, was Sie versuchen, relativ häufig durchzuführen ist), bietet Ihnen die IDE normalerweise effizientere Möglichkeiten, um auf die Klassenhierarchie des aktuellen Status von zuzugreifen Benutzercode. 

1
Uri

Eine neue Version von @ kaybee99's Antwort, aber jetzt zurückgeben, was der Benutzer fragt:.

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(){}
}

Dann können Sie automatisch eine Liste des Typs ITask verwenden, und Spring füllt sie mit allen Implementierungen auf:

@Service
public class TaskService {

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

    if ( tasks != null)
    for (ITask<?> taskImpl: tasks) {
        taskImpl.doStuff();
    }   
}

Ich bin auf die gleiche Ausgabe gestoßen. Meine Lösung bestand darin, mithilfe von Reflection alle Methoden in einer ObjectFactory-Klasse zu untersuchen und diejenigen zu eliminieren, die keine createXXX () -Methoden waren und eine Instanz eines meiner gebundenen POJOs zurückgeben. Jede so entdeckte Klasse wird einem Class [] - Array hinzugefügt, das dann an den Instantiierungsaufruf JAXBContext übergeben wird. 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 habe und Schemaagen verwendet habe) oder nach Bedarf von xjc generiert werden kann. Auf jeden Fall ist es performant, einfach und effektiv.

0
NDK

Mit ClassGraph ist es ziemlich einfach:

Groovy 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().getClassesImplementing('my.package.MyInterface').findAll{!it.abstract}*.className
0
verglor

Versuchen Sie ClassGraph . (Haftungsausschluss, ich bin der Autor). ClassGraph unterstützt das Scannen nach Klassen, die eine bestimmte Schnittstelle implementieren, entweder zur Laufzeit oder zur Erstellungszeit, aber auch vieles mehr. ClassGraph kann eine abstrakte Darstellung des gesamten Klassendiagramms (aller Klassen, Anmerkungen, Methoden, Methodenparameter und Felder) im Speicher für alle Klassen im Klassenpfad oder für Klassen in Whitelist-Paketen erstellen. Sie können dieses Klassendiagramm jedoch abfragen Sie wollen. ClassGraph unterstützt mehr Classpath-Spezifikationsmechanismen und Classloader als jeder andere Scanner und funktioniert auch nahtlos mit dem neuen JPMS-Modulsystem. Wenn Sie also Ihren Code auf ClassGraph basieren, ist Ihr Code maximal portierbar. Siehe die API hier.

0
Luke Hutchison