web-dev-qa-db-fra.com

Comment obtenir une liste de toutes les implémentations d'une interface par programme en Java?

Puis-je le faire avec réflexion ou quelque chose comme ça?

59
user2427

Je cherche depuis un moment et il semble y avoir différentes approches, voici un résumé:

  1. La bibliothèque reflections est très populaire si vous ne craignez pas l’ajout de la dépendance. Cela ressemblerait à ceci:

    Reflections reflections = new Reflections("firstdeveloper.examples.reflections");
    Set<Class<? extends Pet>> classes = reflections.getSubTypesOf(Pet.class);
    
  2. ServiceLoader (selon la réponse de erickson) et cela ressemblerait à ceci:

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

    Notez que pour que cela fonctionne, vous devez définir Pet en tant que ServiceProviderInterface (SPI) et déclarer ses implémentations. vous le faites en créant un fichier dans resources/META-INF/services avec le nom examples.reflections.Pet et en déclarant toutes les implémentations de Pet 

    examples.reflections.Dog
    examples.reflections.Cat
    
  3. annotation au niveau du paquet. Voici un exemple:

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

    et la définition de l'annotation:

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

    et vous devez déclarer l'annotation au niveau du package dans un fichier nommé package-info.Java à l'intérieur de ce package. voici des exemples de contenu:

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

    Notez que seuls les packages connus du ClassLoader à ce moment-là seront chargés par un appel à Package.getPackages().

En outre, il existe d'autres approches basées sur URLClassLoader qui seront toujours limitées aux classes qui ont déjà été chargées, sauf si vous effectuez une recherche basée sur un répertoire. 

32
Ahmad Abdelghany

Qu'est-ce qu'erickson a dit, mais si vous voulez toujours le faire, jetez un coup d'œil à Reflections . De leur page:

À l'aide de Reflections, vous pouvez interroger vos métadonnées pour:

  • obtenir tous les sous-types d'un type
  • obtenir tous les types annotés avec une annotation
  • obtenir tous les types annotés avec une annotation, y compris les paramètres d'annotation correspondants
  • obtenir toutes les méthodes annotées avec certains 
27
Peter Severin

En général, cela coûte cher. Pour utiliser la réflexion, la classe doit être chargée. Si vous voulez charger toutes les classes disponibles sur le classpath, cela prendra du temps et de la mémoire, et ce n'est pas recommandé. 

Si vous voulez éviter cela, vous devez implémenter votre propre analyseur de fichiers de classe qui fonctionne plus efficacement, au lieu de la réflexion. Une bibliothèque d'ingénierie de code d'octet peut aider avec cette approche.

Le mécanisme fournisseur de services est le moyen classique d'énumérer les implémentations d'un service enfichable. Utilisez la ServiceLoader dans Java 6 ou implémentez la vôtre dans des versions antérieures. J'ai fourni un exemple dans une autre réponse.

22
erickson

Le printemps a un moyen assez simple d’atteindre ceci:

public interface ITask {
    void doStuff();
}

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

Ensuite, vous pouvez connecter automatiquement une liste de type ITask et Spring la renseignera avec toutes les implémentations:

@Service
public class TaskService {

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

Ce que erikson a dit est le meilleur. Voici un fil de questions-réponses - http://www.velocityreviews.com/forums/t137693-find-all-implementing-classes-in-classpath.html

La bibliothèque Apache BCEL vous permet de lire des classes sans les charger. Je pense que ce sera plus rapide car vous devriez pouvoir passer l'étape de vérification. L’autre problème lié au chargement de toutes les classes à l’aide du chargeur de classes est que vous subirez un énorme impact sur la mémoire et que vous exécuterez par inadvertance des blocs de code statiques que vous ne souhaiterez probablement pas.

Le lien de la bibliothèque Apache BCEL - http://jakarta.Apache.org/bcel/

4
Sachin

Oui, la première étape consiste à identifier "toutes" les classes qui vous intéressaient. Si vous avez déjà ces informations, vous pouvez les énumérer et utiliser instanceof pour valider la relation. Un article connexe est ici: http://www.javaworld.com/javaworld/javatips/jw-javatip113.html

4
Zhichao

De plus, si vous écrivez un plug-in IDE (où ce que vous essayez de faire est relativement courant), le IDE vous offre généralement des moyens plus efficaces d'accéder à la hiérarchie de classes de l'état actuel du fichier. Code d'utilisateur. 

1
Uri

Une nouvelle version de la réponse de @ kaybee99, mais renvoyant maintenant ce que l'utilisateur demande: les implémentations ...

Le printemps a un moyen assez simple d’atteindre ceci:

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

}

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

Ensuite, vous pouvez connecter automatiquement une liste de type ITask et Spring la renseignera avec toutes les implémentations:

@Service
public class TaskService {

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

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

J'ai rencontré le même problème. Ma solution consistait à utiliser la réflexion pour examiner toutes les méthodes d'une classe ObjectFactory, en éliminant celles qui n'étaient pas des méthodes createXXX () renvoyant une instance de l'un de mes POJO liés. Chaque classe ainsi découverte est ajoutée à un tableau Class [], qui a ensuite été transmis à l'appel d'instanciation JAXBContext. Cela fonctionne bien, car il suffit de charger la classe ObjectFactory, qui était sur le point de l'être de toute façon. Je n'ai besoin que de maintenir la classe ObjectFactory, une tâche exécutée manuellement (dans mon cas, parce que j'ai commencé avec des POJO et utilisé schemagen), ou peut être générée à la demande de xjc. Quoi qu'il en soit, il est performant, simple et efficace.

0
NDK

Avec ClassGraph c'est assez simple:

Code Groovy pour trouver des implémentations de 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

Essayez ClassGraph . (Avertissement, je suis l'auteur). ClassGraph prend en charge la recherche de classes implémentant une interface donnée, au moment de l'exécution ou de la construction, mais aussi bien plus. ClassGraph peut construire une représentation abstraite du graphique de classe entier (toutes les classes, annotations, méthodes, paramètres de méthode et champs) en mémoire, pour toutes les classes du chemin de classe ou pour les classes de packages sur liste blanche. Vous pouvez toutefois interroger ce graphique de classe. tu veux. ClassGraph prend en charge plus de mécanismes de spécification de chemin de classe et de chargeurs de classes que tout autre scanner, et fonctionne également de manière transparente avec le nouveau système de module JPMS. Ainsi, si vous basez votre code sur ClassGraph, votre code sera extrêmement portable. Voir l'API ici.

0
Luke Hutchison