web-dev-qa-db-fra.com

Trouver toutes les classes implémentant une interface spécifique

Je suis en train de développer une application (programmateur Quartz) dans laquelle nous avons une classe d’emplois chargée d’exécuter le travail. Nous devons indiquer/transmettre le nom de la classe d’emplois lors de la création d’un déclencheur dans le planificateur Quartz.

Je souhaite fournir un point d'extension à tous ceux qui souhaitent utiliser l'API (à côté des travaux génériques que je fournirai dans le cadre de l'API). L'idée est de créer une interface (marqueur) et si quelqu'un veut déclarer sa classe en tant que classe de travail du planificateur, il lui suffit de (déclarer) d'implémenter l'interface.

Je ne sais pas comment trouver les classes qui suivent le contrat (en implémentant l'interface) afin de pouvoir les montrer à l'utilisateur qui souhaite planifier un déclencheur dans le planificateur.

Mon exigence n'est pas de charger les classes au moment de l'exécution, mais d'afficher la liste des classes d'utilisateurs qui implémentent l'interface requise afin que l'utilisateur puisse sélectionner la classe et que son nom puisse être transmis au planificateur. end sera responsable de la création d'une instance de classe.

Quelqu'un peut-il suggérer comment je peux atteindre l'objectif ci-dessus ou existe-t-il un meilleur moyen de réaliser ce que j'essaie de faire?

Modifier

J'ai parcouru la doc de ServiceLoader et il semble que pour implémenter un service, il faut créer un fichier dans le dossier META-INF avec le nom de la classe d'implémentation, ce qui me conduit à penser que si l'utilisateur de mon L'API souhaite 20 implémentations différentes, il doit mettre 20 entrées dans le fichier, ce qui me semble une charge de travail supplémentaire pour l'utilisateur final, car chaque classe de travail sera créée pour exécuter un travail spécifique et il peut y avoir des centaines de classes de travail.

S'il vous plaît corrigez-moi si mon hypothèse est fausse.

30
Umesh Awasthi

J'avais un besoin similaire: je voulais m'assurer que toutes les classes créées qui implémentaient une certaine interface étaient toujours vraiment sérialisables. J'ai créé un JavaClassFinder qui parcourt tous les répertoires du chemin de classe et trouve toutes les classes pouvant être assignées à l'interface qui me tenait à cœur. Voici un extrait de code:

public <T> List<Class<? extends T>> findAllMatchingTypes(Class<T> toFind) {
    foundClasses = new ArrayList<Class<?>>();
    List<Class<? extends T>> returnedClasses = new ArrayList<Class<? extends T>>();
    this.toFind = toFind;
    walkClassPath();
    for (Class<?> clazz : foundClasses) {
        returnedClasses.add((Class<? extends T>) clazz);
    }
    return returnedClasses;
}

Je suis heureux de partager le code avec vous si cela peut vous aider. Le seul inconvénient est que cela ne gérera que les fichiers .class - je n'ai pas ajouté la fonctionnalité pour décompresser les fichiers .jars et lire les fichiers de classe à partir de là. (Mais ce ne serait pas un projet énorme d'ajouter cela.)

UPDATE: J'ai vérifié le code ci-dessus dans mon code source et j'ai constaté qu'il dépend de nombreuses classes d'assistance dans notre bibliothèque d'utilitaires standard. Pour faciliter les choses, j'ai compressé tout le code nécessaire, que vous pouvez télécharger à partir de JavaClassFinder.Zip . Cela va s'installer directement dans Eclipse, et vous pouvez prendre n'importe quelle partie du code dont vous avez besoin. 

Vous trouverez un test JUnit3 dans le projet, appelé JavaClassFinderTest.Java, qui présente les fonctionnalités et l'utilisation de la classe JavaClassFinder. Junit est la seule dépendance externe nécessaire pour exécuter le test Junit.

Utilisation de base de cet utilitaire:

    JavaClassFinder classFinder = new JavaClassFinder();
    List<Class<? extends MyTagInterface>> classes = classFinder.findAllMatchingTypes(MyTagInterface.class);

Cela vous donnera une liste contenant toutes les classes du classpath pouvant être assignées à partir de "MyTagInterface.class" (par exemple). J'espère que cela t'aides.

33
Sam Goldberg

Vous pouvez trouver une réponse ici .

Je peux suggérer d'utiliser org.reflections

entrez la description du lien ici

Reflections reflections = new Reflections("com.mycompany");    
Set<Class<? extends MyInterface>> classes = reflections.getSubTypesOf(MyInterface.class);
32
Alex Stybaev

Je pense que org.reflections est une solution appropriée, comme l'a mentionné Alex Stybaev, vous n'avez pas besoin de référencer les classes dans un fichier de propriétés.

Une autre approche (que je prendrais) est Spring , puisque j'utilise quand même Spring pour mes applications et que je n’ai donc pas besoin de dépendances supplémentaires.

Vous pouvez trouver des astuces pour résoudre votre problème avec Spring (et des alternatives dans les autres commentaires ou réponses) ici:

Dans les commentaires de votre question, heikkim et polypiel renvoient également à des questions permettant de résoudre le problème avec Spring:

2
ChrLipp

C'est probablement la meilleure façon (standard) de faire cela en utilisant le mécanisme Java SPI, voir Javadoc . L’inconvénient (qui est également une fonctionnalité intéressante) est qu’il attend des Jars qui définissent des extensions de les répertorier dans META-INF/services/your.fully.qualified.Interface.

La seule autre solution à laquelle je puisse penser serait de muronner tous les ClassLoaders dans l'espoir que vous pourrez y lister les fichiers, charger les fichiers de classe et voir s'ils implémentent votre interface ou non - ce qui n'est pas agréable faire.

1
Romain

D'après les réponses de Trouvez des classes Java implémentant une interface , j'ai utilisé http://software.clapper.org/javautil/ : Très vite, très utile.

0
Jayan

Pour le faire en Java pur, la réponse la plus correcte est déjà votée (d'après sam goldberg le 10 avr. 2012)

Cependant, son code complet est inutilement complexe. J'ai utilisé son idée et poussé ClassFinder à cet endroit: http://icedtea.classpath.org/hg/icedtea-web/file/0527ad4eb2dd/netx/net/sourceforge/jnlp/controlpanel/ClassFinder.Java

Remarque - cela fonctionnera dans une application autonome (ou dans toute application utilisant un chemin d'accès classique avec jars et dirs) et non dans ServerContainer.

si votre application est exécutée sur le serveur Web, vous devez connaître l'emplacement de votre guerre/votre oreille et y effectuer une recherche. Ou vous devez demander à votre classe de classe parent où il obtient les vôtres (et autres) sources.

Deuxième remarque - je filtre les classes d'emplacement finales pour qu'elles correspondent uniquement à netx et à icedtea-web, car je ne cherche pas les dépendances habituelles. Donc si vous avez besoin d'inclure rt.jar s'il vous plaît, récupérez ces filtres. Ou bien supprimez bootclasspath si, au contraire, vous n'en avez pas du tout besoin.

0
judovana