Pouvez-vous trouver toutes les classes dans un package en utilisant la réflexion?

528

Est-il possible de trouver toutes les classes ou interfaces dans un package donné? (En regardant rapidement par exemple Package, il semblerait que non.)

Jonik
la source
2
Pour info, la solution Amit est liée à works, bien qu'elle comporte un bogue si le chemin de classe contient un caractère espace (et probablement aussi pour d'autres caractères non alphanumériques). si vous l'utilisez dans n'importe quel type de code de production, consultez mon commentaire sur sa réponse pour une solution de contournement.
Kip
2
Notez également ce post .
barfuin
1
Voir la réponse associée: stackoverflow.com/a/30149061/4102160
Cfx
1
Notez également ce post .
sp00m
1
Voir ma réponse ci-dessous à propos de ClassGraph, c'est actuellement la méthode la plus robuste pour analyser le chemin de classe et le chemin du module.
Luke Hutchison du

Réponses:

373

En raison de la nature dynamique des chargeurs de classe, cela n'est pas possible. Les chargeurs de classe ne sont pas tenus d'indiquer à la machine virtuelle les classes qu'elle peut fournir, à la place, il s'agit simplement de demandes de classes et doivent renvoyer une classe ou lever une exception.

Cependant, si vous écrivez vos propres chargeurs de classe, ou examinez les chemins de classe et ses jars, il est possible de trouver ces informations. Cependant, cela se fera via les opérations du système de fichiers, et non par réflexion. Il peut même y avoir des bibliothèques qui peuvent vous y aider.

S'il y a des classes qui sont générées ou livrées à distance, vous ne pourrez pas découvrir ces classes.

La méthode normale consiste plutôt à enregistrer quelque part les classes auxquelles vous devez accéder dans un fichier, ou à les référencer dans une classe différente. Ou utilisez simplement la convention quand il s'agit de nommer.

Addendum: La bibliothèque Reflections vous permettra de rechercher des classes dans le chemin de classe actuel. Il peut être utilisé pour obtenir toutes les classes dans un package:

 Reflections reflections = new Reflections("my.project.prefix");

 Set<Class<? extends Object>> allClasses = 
     reflections.getSubTypesOf(Object.class);
Staale
la source
12
L'incapacité de rechercher des noms de classe me dérange depuis longtemps. Bien sûr, c'est difficile et les performances peuvent varier considérablement, et pour certains chargeurs de classe, la liste est indéfinie ou illimitée, mais il existe des moyens de contourner cela.
M. Shiny et New 安 宇
16
Notez que cette solution ne fonctionnera pas car par défaut getSubTypesOf ne renvoie pas de sous-types d'Object. Voir la solution d'Aleksander Blomskøld pour savoir comment configurer le SubTypeScanner.
Alex Spurling
17
Les réflexions nécessitent de la goyave. La goyave est grande. La version 14.0.1 fait 2,1 Mo.
mike jones
3
N'a pas travaillé pour moi. Mac OSX - Version dépendances de Reflections 0.9.9-RC1 (maven) - JDK 1.7. Réexaminez la réponse acceptée. @ AleksanderBlomskøld est la réponse. !!!!!
Konstantinos Margaritis
68
Si cela retourne une liste vide, initialisez l'objet Reflections comme ceci: Reflections reflections = new Reflections ("your.package.here", new SubTypesScanner (false));
João Rocha da Silva
186

Vous devriez probablement jeter un œil à la bibliothèque Open Source Reflections . Avec lui, vous pouvez facilement réaliser ce que vous voulez.

Tout d'abord, configurez l'index de réflexions (c'est un peu compliqué car la recherche de toutes les classes est désactivée par défaut):

List<ClassLoader> classLoadersList = new LinkedList<ClassLoader>();
classLoadersList.add(ClasspathHelper.contextClassLoader());
classLoadersList.add(ClasspathHelper.staticClassLoader());

Reflections reflections = new Reflections(new ConfigurationBuilder()
    .setScanners(new SubTypesScanner(false /* don't exclude Object.class */), new ResourcesScanner())
    .setUrls(ClasspathHelper.forClassLoader(classLoadersList.toArray(new ClassLoader[0])))
    .filterInputsBy(new FilterBuilder().include(FilterBuilder.prefix("org.your.package"))));

Ensuite, vous pouvez interroger tous les objets d'un package donné:

Set<Class<?>> classes = reflections.getSubTypesOf(Object.class);
Aleksander Blomskøld
la source
6
Ah, c'est parti : code.google.com/p/reflections/issues/detail?id=122 . L'objet est exclu par défaut, mais vous pouvez le réactiver. Merci de me diriger vers cette bibliothèque, c'est génial!
mtrc
1
J'ai rencontré des problèmes sur mon Mac avec ce code (lié aux bibliothèques natives), mais les utiliser à la .addUrls(ClasspathHelper.forJavaClassPath())place des solutions ci-dessus les a résolus pour moi. Moins de code aussi!
David Pärsson
3
si quelqu'un se demande la façon la plus simple d'obtenir le package par défaut, le préfixe doit être une chaîne vide -> "".
JBA
2
La bibliothèque "Reflections" possède une licence délicate: github.com/ronmamo/reflections/blob/master/COPYING.txt . L'astuce est que la licence permet d'utiliser gratuitement uniquement la licence elle-même. Donc, pour vraiment utiliser la bibliothèque (pas la licence), tout le monde doit contacter l'auteur et négocier les conditions d'utilisation.
Serge Rogatch
3
Serge, je pense que vous comprenez mal WTFPL: wtfpl.net Je pense que WTFPL signifie que vous êtes libre de faire ce que vous voulez, pas seulement avec la licence mais aussi avec le code
Richo
122

Google Guava 14 comprend une nouvelle classe ClassPathavec trois méthodes pour rechercher des classes de niveau supérieur:

  • getTopLevelClasses()
  • getTopLevelClasses(String packageName)
  • getTopLevelClassesRecursive(String packageName)

Voir les ClassPathjavadocs pour plus d'informations.

Christoph Leiter
la source
cela a fonctionné pour moi là où la réflexion ne pouvait pas faire (pas d'ancêtre direct commun, pas d'annotation commune)
Riccardo Cossu
1
Comme je l'ai mentionné dans un commentaire ci - dessous , ClassPathest étiqueté avec @Beta, donc ce pourrait ne pas être une bonne idée pour certains ...
Christian
1
Dire que cela fonctionne là où la réflexion ne fonctionne pas est un peu étrange, la solution est sans aucun doute mise en œuvre en utilisant la fonctionnalité de réflexion (et de chargeur de classe).
Maarten Bodewes
5
Je pense qu'il voulait parler de la bibliothèque Reflections mentionnée dans l'autre réponse.
Christoph Leiter
Fonctionne sous Java 11, si vous utilisez la version 28.1-jre de goyave.
Gorjanz
112

Vous pouvez utiliser cette méthode 1 qui utilise le ClassLoader.

/**
 * Scans all classes accessible from the context class loader which belong to the given package and subpackages.
 *
 * @param packageName The base package
 * @return The classes
 * @throws ClassNotFoundException
 * @throws IOException
 */
private static Class[] getClasses(String packageName)
        throws ClassNotFoundException, IOException {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    assert classLoader != null;
    String path = packageName.replace('.', '/');
    Enumeration<URL> resources = classLoader.getResources(path);
    List<File> dirs = new ArrayList<File>();
    while (resources.hasMoreElements()) {
        URL resource = resources.nextElement();
        dirs.add(new File(resource.getFile()));
    }
    ArrayList<Class> classes = new ArrayList<Class>();
    for (File directory : dirs) {
        classes.addAll(findClasses(directory, packageName));
    }
    return classes.toArray(new Class[classes.size()]);
}

/**
 * Recursive method used to find all classes in a given directory and subdirs.
 *
 * @param directory   The base directory
 * @param packageName The package name for classes found inside the base directory
 * @return The classes
 * @throws ClassNotFoundException
 */
private static List<Class> findClasses(File directory, String packageName) throws ClassNotFoundException {
    List<Class> classes = new ArrayList<Class>();
    if (!directory.exists()) {
        return classes;
    }
    File[] files = directory.listFiles();
    for (File file : files) {
        if (file.isDirectory()) {
            assert !file.getName().contains(".");
            classes.addAll(findClasses(file, packageName + "." + file.getName()));
        } else if (file.getName().endsWith(".class")) {
            classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
        }
    }
    return classes;
}

__________
1 Cette méthode a été tirée à l'origine de http://snippets.dzone.com/posts/show/4831 , qui a été archivée par Internet Archive, comme liée à maintenant. L'extrait est également disponible sur https://dzone.com/articles/get-all-classes-within-package .

Ahmed Ashour
la source
6
J'ai eu un problème avec cela si mon chemin comprenait des espaces. La classe URL échappait aux espaces %20, mais le new File()constructeur a traité cela comme un signe de pourcentage littéral deux zéro. Je l'ai corrigé en changeant la dirs.add(...)ligne en ceci: dirs.add(new File(resource.toURI())); Cela signifiait également que je devais ajouter URISyntaxExceptionà la clause throws degetClasses
Kip
19
Vous venez de copier à partir de dzone.com/articles/get-all-classes-within-package ! veuillez vous référer à la source la prochaine fois
RS
19
+1 parce que cette solution ne nécessite PAS de bibliothèques externes ... JAMAIS, vraiment NE JAMAIS coupler votre code au hasard avec des bibliothèques juste pour réaliser une petite chose comme ça. savez-vous que vous ajoutez une surface d'attaque potentielle pour les attaquants? Nov 2015, un problème Apache Commons découvert qui conduit à l'exécution de commandes à distance simplement en ayant Apache Commons dans le chemin de classe d'une application déployée sur Jboss / Weblogic [ foxglovesecurity.com/2015/11/06/…
sc0p
@Qix a correctement noté que ce code ne prend pas en charge jar. Afin de prendre en charge les pots et les répertoires . Le code a été modifié comme indiqué ci-dessous:
user1523177
1
Bonne solution, mais il semble préférable que 'Class.forName (String className)' soit remplacé par 'Class.forName (String className, boolean initialize, ClassLoader loader)' où 'initialize = false;' afin de ne pas créer d'instances de classe.
Andrei_N
99

Printemps

Cet exemple concerne Spring 4, mais vous pouvez également trouver le scanner de chemin de classe dans les versions antérieures.

// create scanner and disable default filters (that is the 'false' argument)
final ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
// add include filters which matches all the classes (or use your own)
provider.addIncludeFilter(new RegexPatternTypeFilter(Pattern.compile(".*")));

// get matching classes defined in the package
final Set<BeanDefinition> classes = provider.findCandidateComponents("my.package.name");

// this is how you can load the class type from BeanDefinition instance
for (BeanDefinition bean: classes) {
    Class<?> clazz = Class.forName(bean.getBeanClassName());
    // ... do your magic with the class ...
}

Google Guava

Remarque: Dans la version 14, l'API est toujours marquée comme @Beta , donc méfiez-vous dans le code de production.

final ClassLoader loader = Thread.currentThread().getContextClassLoader();

for (final ClassPath.ClassInfo info : ClassPath.from(loader).getTopLevelClasses()) {
  if (info.getName().startsWith("my.package.")) {
    final Class<?> clazz = info.load();
    // do something with your clazz
  }
}
voho
la source
5
Excellente réponse. Il y a trop de solutions ici qui sont verbeuses, non testées, qui ne fonctionnent pas! Celui-ci est fantastique: il est concis et testé (il vient de Guava). Très bien! C'est utile, ça mérite plus de votes positifs.
JeanValjean
Malheureusement, la ClassPathclasse de Guava est également marquée avec @Beta: "Les API marquées de l'annotation @Beta au niveau de la classe ou de la méthode sont susceptibles de changer. Elles peuvent être modifiées de quelque manière que ce soit, voire supprimées, dans n'importe quelle version majeure. Si votre code est une bibliothèque elle-même (c'est-à-dire qu'elle est utilisée sur le CLASSPATH des utilisateurs hors de votre contrôle), vous ne devez pas utiliser les API bêta, sauf si vous les reconditionnez ... " code.google.com/p/guava-libraries/#Important_Warnings
Christian
@Christian Bon point, je n'ai pas remarqué! Je vous remercie. J'ajouterai une autre réponse en utilisant le scanner Spring classpath, qui n'est pas bêta pour sûr.
voho
Pour trouver des classes statiques imbriquées à l'aide d'une solution de goyave, la getAllClasses()méthode peut être utilisée.
v.ladynev
1
La solution Spring est la seule qui fonctionne si elle est exécutée à partir d'un fichier exécutable.
Luke
38

Bonjour. J'ai toujours eu des problèmes avec les solutions ci-dessus (et sur d'autres sites).
En tant que développeur, je programme un addon pour une API. L'API empêche l'utilisation de bibliothèques externes ou d'outils tiers. La configuration consiste également en un mélange de code dans des fichiers jar ou zip et des fichiers de classe situés directement dans certains répertoires. Mon code devait donc pouvoir fonctionner autour de chaque configuration. Après beaucoup de recherches, j'ai trouvé une méthode qui fonctionnera dans au moins 95% de toutes les configurations possibles.

Le code suivant est fondamentalement la méthode overkill qui fonctionnera toujours.

Le code:

Ce code analyse un package donné pour toutes les classes qui y sont incluses. Cela ne fonctionnera que pour toutes les classes dans le courant ClassLoader.

/**
 * Private helper method
 * 
 * @param directory
 *            The directory to start with
 * @param pckgname
 *            The package name to search for. Will be needed for getting the
 *            Class object.
 * @param classes
 *            if a file isn't loaded but still is in the directory
 * @throws ClassNotFoundException
 */
private static void checkDirectory(File directory, String pckgname,
        ArrayList<Class<?>> classes) throws ClassNotFoundException {
    File tmpDirectory;

    if (directory.exists() && directory.isDirectory()) {
        final String[] files = directory.list();

        for (final String file : files) {
            if (file.endsWith(".class")) {
                try {
                    classes.add(Class.forName(pckgname + '.'
                            + file.substring(0, file.length() - 6)));
                } catch (final NoClassDefFoundError e) {
                    // do nothing. this class hasn't been found by the
                    // loader, and we don't care.
                }
            } else if ((tmpDirectory = new File(directory, file))
                    .isDirectory()) {
                checkDirectory(tmpDirectory, pckgname + "." + file, classes);
            }
        }
    }
}

/**
 * Private helper method.
 * 
 * @param connection
 *            the connection to the jar
 * @param pckgname
 *            the package name to search for
 * @param classes
 *            the current ArrayList of all classes. This method will simply
 *            add new classes.
 * @throws ClassNotFoundException
 *             if a file isn't loaded but still is in the jar file
 * @throws IOException
 *             if it can't correctly read from the jar file.
 */
private static void checkJarFile(JarURLConnection connection,
        String pckgname, ArrayList<Class<?>> classes)
        throws ClassNotFoundException, IOException {
    final JarFile jarFile = connection.getJarFile();
    final Enumeration<JarEntry> entries = jarFile.entries();
    String name;

    for (JarEntry jarEntry = null; entries.hasMoreElements()
            && ((jarEntry = entries.nextElement()) != null);) {
        name = jarEntry.getName();

        if (name.contains(".class")) {
            name = name.substring(0, name.length() - 6).replace('/', '.');

            if (name.contains(pckgname)) {
                classes.add(Class.forName(name));
            }
        }
    }
}

/**
 * Attempts to list all the classes in the specified package as determined
 * by the context class loader
 * 
 * @param pckgname
 *            the package name to search
 * @return a list of classes that exist within that package
 * @throws ClassNotFoundException
 *             if something went wrong
 */
public static ArrayList<Class<?>> getClassesForPackage(String pckgname)
        throws ClassNotFoundException {
    final ArrayList<Class<?>> classes = new ArrayList<Class<?>>();

    try {
        final ClassLoader cld = Thread.currentThread()
                .getContextClassLoader();

        if (cld == null)
            throw new ClassNotFoundException("Can't get class loader.");

        final Enumeration<URL> resources = cld.getResources(pckgname
                .replace('.', '/'));
        URLConnection connection;

        for (URL url = null; resources.hasMoreElements()
                && ((url = resources.nextElement()) != null);) {
            try {
                connection = url.openConnection();

                if (connection instanceof JarURLConnection) {
                    checkJarFile((JarURLConnection) connection, pckgname,
                            classes);
                } else if (connection instanceof FileURLConnection) {
                    try {
                        checkDirectory(
                                new File(URLDecoder.decode(url.getPath(),
                                        "UTF-8")), pckgname, classes);
                    } catch (final UnsupportedEncodingException ex) {
                        throw new ClassNotFoundException(
                                pckgname
                                        + " does not appear to be a valid package (Unsupported encoding)",
                                ex);
                    }
                } else
                    throw new ClassNotFoundException(pckgname + " ("
                            + url.getPath()
                            + ") does not appear to be a valid package");
            } catch (final IOException ioex) {
                throw new ClassNotFoundException(
                        "IOException was thrown when trying to get all resources for "
                                + pckgname, ioex);
            }
        }
    } catch (final NullPointerException ex) {
        throw new ClassNotFoundException(
                pckgname
                        + " does not appear to be a valid package (Null pointer exception)",
                ex);
    } catch (final IOException ioex) {
        throw new ClassNotFoundException(
                "IOException was thrown when trying to get all resources for "
                        + pckgname, ioex);
    }

    return classes;
}

Ces trois méthodes vous permettent de trouver toutes les classes dans un package donné.
Vous l'utilisez comme ceci:

getClassesForPackage("package.your.classes.are.in");

L'explication:

La méthode obtient d'abord le courant ClassLoader. Il récupère ensuite toutes les ressources qui contiennent ledit package et répète ces URLs. Il crée ensuite un URLConnectionet détermine le type d'URl que nous avons. Il peut s'agir d'un répertoire ( FileURLConnection) ou d'un répertoire dans un fichier jar ou zip ( JarURLConnection). Selon le type de connexion dont nous disposons, deux méthodes différentes seront appelées.

Voyons d'abord ce qui se passe si c'est un FileURLConnection.
Il vérifie d'abord si le fichier transmis existe et s'il s'agit d'un répertoire. Si tel est le cas, il vérifie s'il s'agit d'un fichier de classe. Si c'est le cas, un Classobjet sera créé et placé dans le ArrayList. S'il ne s'agit pas d'un fichier de classe mais d'un répertoire, nous le parcourons simplement et faisons la même chose. Tous les autres cas / fichiers seront ignorés.

Si URLConnectionc'est un, JarURLConnectionl'autre méthode d'assistance privée sera appelée. Cette méthode parcourt toutes les entrées de l'archive zip / jar. Si une entrée est un fichier de classe et se trouve à l'intérieur du package, un Classobjet sera créé et stocké dans le ArrayList.

Une fois que toutes les ressources ont été analysées, elle (la méthode principale) renvoie le ArrayListcontenu de toutes les classes du package donné, que le courant ClassLoaderconnaît.

Si le processus échoue à un moment donné, un ClassNotFoundExceptionmessage contenant des informations détaillées sur la cause exacte sera affiché.

BrainStone
la source
4
Cet exemple semble nécessiter l'importation sun.net.www.protocol.file.FileURLConnection, ce qui génère un avertissement au moment de la compilation ("avertissement: sun.net.www.protocol.file.FileURLConnection est l'API propriétaire de Sun et pourra être supprimé dans une version ultérieure"). Existe-t-il une alternative à l'utilisation de cette classe, ou l'avertissement peut-il être supprimé à l'aide d'annotations?
Christian
Cette méthode ne fonctionne pas pour les classes d'amorçage, comme celles de java.lang, java.util, ... Celles-ci peuvent être trouvées en obtenant System.getProperty ("sun.boot.class.path"), en se séparant avec: ou; (selon le système d'exploitation), puis en exécutant des versions légèrement modifiées des fichiers checkDirectory et checkJarFile ci-dessus.
coderforlife
1
Vous pouvez contourner l'avertissement / l'erreur en utilisant connection.getClass (). GetCanonicalName (). Equals ("sun.net.www.protocol.file.FileURLConnection"). Si vous le voulez vraiment, vous pouvez créer une URLConnection qui, selon vous, DEVRAIT utiliser sun.net.www.protocol.file.FileURLConnection et également comparer le nom de la classe de connexion avec le nom de la classe que vous avez créée. S'ils sont tous les deux identiques, vous pouvez le traiter comme une instance de sun.net.www.protocol.file.FileURLConnection plutôt que d'échouer en cas de changement de nom de classe.
William Deans
1
@Christian Vous pouvez éviter que FileURLConnection fasse quelque chose comme ceci: if ( ... instanceof JarURLConnecton) { ... } else { // Asume that the Connection is valid and points to a File }c'est ce que j'ai fait sur mon code pour rechercher des classes annotées JPA
Zardoz89
15

Sans utiliser de bibliothèques supplémentaires:

package test;

import java.io.DataInputStream;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

public class Test {
    public static void main(String[] args) throws Exception{
        List<Class> classes = getClasses(Test.class.getClassLoader(),"test");
        for(Class c:classes){
            System.out.println("Class: "+c);
        }
    }

    public static List<Class> getClasses(ClassLoader cl,String pack) throws Exception{

        String dottedPackage = pack.replaceAll("[/]", ".");
        List<Class> classes = new ArrayList<Class>();
        URL upackage = cl.getResource(pack);

        DataInputStream dis = new DataInputStream((InputStream) upackage.getContent());
        String line = null;
        while ((line = dis.readLine()) != null) {
            if(line.endsWith(".class")) {
               classes.add(Class.forName(dottedPackage+"."+line.substring(0,line.lastIndexOf('.'))));
            }
        }
        return classes;
    }
}
Williams López
la source
Quand je lance ça dans un JAR, upackagec'est null... :(
Christian
Pour un package "com.mycompany.beans", remplacez "test" par "com / mycompany / beans"
James Jithin
4
J'obtiens un null lorsque j'utilise ce code. Semble fonctionner uniquement si votre bocal est un exécutable
Alao
si vous avez obtenu le nom du package à partir de String pack = getPackage().getName();, vous devez ajouterpack = pack.replaceAll("[.]", "/");
user2682877
13

En général, les chargeurs de classe ne permettent pas de parcourir toutes les classes sur le chemin de classe. Mais généralement, le seul chargeur de classe utilisé est UrlClassLoader à partir duquel nous pouvons récupérer la liste des répertoires et des fichiers jar (voir getURLs ) et les ouvrir un par un pour répertorier les classes disponibles. Cette approche, appelée analyse de chemin de classe, est implémentée dans Scannotation and Reflections .

Reflections reflections = new Reflections("my.package");
Set<Class<? extends Object>> classes = reflections.getSubTypesOf(Object.class);

Une autre approche consiste à utiliser l' API Java Pluggable Annotation Processing pour écrire un processeur d'annotations qui collectera toutes les classes annotées au moment de la compilation et construira le fichier d'index pour une utilisation à l'exécution. Ce mécanisme est implémenté dans la bibliothèque ClassIndex :

// package-info.java
@IndexSubclasses
package my.package;

// your code
Iterable<Class> classes = ClassIndex.getPackageClasses("my.package");

Notez qu'aucune configuration supplémentaire n'est nécessaire car l'analyse est entièrement automatisée grâce au compilateur Java qui détecte automatiquement tous les processeurs trouvés sur le chemin de classe.

Sławek
la source
cela découvre-t-il des cours emballés dans un bocal? Cela ne semble pas fonctionner pour moi.
asgs
quel outil essayez-vous d'utiliser?
Sławek
J'utilise la bibliothèque Reflections. Mais je l'ai fait fonctionner après avoir suivi la solution de contournement mentionnée par @Aleksander Blomskøld pour les versions récentes de cette bibliothèque.
asgs
Salut, j'utilise eclipse et ne peux pas le faire fonctionner, ClassIndex.getPackageClasses ("my.package") retourne une carte vide
Juan
11

Le mécanisme le plus robuste pour répertorier toutes les classes dans un package donné est actuellement ClassGraph , car il gère le plus large éventail possible de mécanismes de spécification de chemin de classe, y compris le nouveau système de modules JPMS. (Je suis l'auteur.)

List<String> classNames = new ArrayList<>();
try (ScanResult scanResult = new ClassGraph().whitelistPackages("my.package")
        .enableClassInfo().scan()) {
    classNames.addAll(scanResult.getAllClasses().getNames());
}
Luke Hutchison
la source
5

Voici comment je le fais. J'analyse tous les sous-dossiers (sous-packages) et je n'essaye pas de charger des classes anonymes:

   /**
   * Attempts to list all the classes in the specified package as determined
   * by the context class loader, recursively, avoiding anonymous classes
   * 
   * @param pckgname
   *            the package name to search
   * @return a list of classes that exist within that package
   * @throws ClassNotFoundException
   *             if something went wrong
   */
  private static List<Class> getClassesForPackage(String pckgname) throws ClassNotFoundException {
      // This will hold a list of directories matching the pckgname. There may be more than one if a package is split over multiple jars/paths
      ArrayList<File> directories = new ArrayList<File>();
      String packageToPath = pckgname.replace('.', '/');
      try {
          ClassLoader cld = Thread.currentThread().getContextClassLoader();
          if (cld == null) {
              throw new ClassNotFoundException("Can't get class loader.");
          }

          // Ask for all resources for the packageToPath
          Enumeration<URL> resources = cld.getResources(packageToPath);
          while (resources.hasMoreElements()) {
              directories.add(new File(URLDecoder.decode(resources.nextElement().getPath(), "UTF-8")));
          }
      } catch (NullPointerException x) {
          throw new ClassNotFoundException(pckgname + " does not appear to be a valid package (Null pointer exception)");
      } catch (UnsupportedEncodingException encex) {
          throw new ClassNotFoundException(pckgname + " does not appear to be a valid package (Unsupported encoding)");
      } catch (IOException ioex) {
          throw new ClassNotFoundException("IOException was thrown when trying to get all resources for " + pckgname);
      }

      ArrayList<Class> classes = new ArrayList<Class>();
      // For every directoryFile identified capture all the .class files
      while (!directories.isEmpty()){
          File directoryFile  = directories.remove(0);             
          if (directoryFile.exists()) {
              // Get the list of the files contained in the package
              File[] files = directoryFile.listFiles();

              for (File file : files) {
                  // we are only interested in .class files
                  if ((file.getName().endsWith(".class")) && (!file.getName().contains("$"))) {
                      // removes the .class extension
                      int index = directoryFile.getPath().indexOf(packageToPath);
                      String packagePrefix = directoryFile.getPath().substring(index).replace('/', '.');;                          
                    try {                  
                      String className = packagePrefix + '.' + file.getName().substring(0, file.getName().length() - 6);                            
                      classes.add(Class.forName(className));                                
                    } catch (NoClassDefFoundError e)
                    {
                      // do nothing. this class hasn't been found by the loader, and we don't care.
                    }
                  } else if (file.isDirectory()){ // If we got to a subdirectory
                      directories.add(new File(file.getPath()));                          
                  }
              }
          } else {
              throw new ClassNotFoundException(pckgname + " (" + directoryFile.getPath() + ") does not appear to be a valid package");
          }
      }
      return classes;
  }  
Nadav B
la source
4

J'ai mis en place un projet github simple qui résout ce problème:

https://github.com/ddopson/java-class-enumerator

Cela devrait fonctionner pour les DEUX chemins de classe basés sur les fichiers ET pour les fichiers jar.

Si vous exécutez «make» après avoir extrait le projet, il l'imprimera:

 Cleaning...
rm -rf build/
 Building...
javac -d build/classes src/pro/ddopson/ClassEnumerator.java src/test/ClassIShouldFindOne.java src/test/ClassIShouldFindTwo.java src/test/subpkg/ClassIShouldFindThree.java src/test/TestClassEnumeration.java
 Making JAR Files...
jar cf build/ClassEnumerator_test.jar -C build/classes/ . 
jar cf build/ClassEnumerator.jar -C build/classes/ pro
 Running Filesystem Classpath Test...
java -classpath build/classes test.TestClassEnumeration
ClassDiscovery: Package: 'test' becomes Resource: 'file:/Users/Dopson/work/other/java-class-enumeration/build/classes/test'
ClassDiscovery: Reading Directory '/Users/Dopson/work/other/java-class-enumeration/build/classes/test'
ClassDiscovery: FileName 'ClassIShouldFindOne.class'  =>  class 'test.ClassIShouldFindOne'
ClassDiscovery: FileName 'ClassIShouldFindTwo.class'  =>  class 'test.ClassIShouldFindTwo'
ClassDiscovery: FileName 'subpkg'  =>  class 'null'
ClassDiscovery: Reading Directory '/Users/Dopson/work/other/java-class-enumeration/build/classes/test/subpkg'
ClassDiscovery: FileName 'ClassIShouldFindThree.class'  =>  class 'test.subpkg.ClassIShouldFindThree'
ClassDiscovery: FileName 'TestClassEnumeration.class'  =>  class 'test.TestClassEnumeration'
 Running JAR Classpath Test...
java -classpath build/ClassEnumerator_test.jar  test.TestClassEnumeration
ClassDiscovery: Package: 'test' becomes Resource: 'jar:file:/Users/Dopson/work/other/java-class-enumeration/build/ClassEnumerator_test.jar!/test'
ClassDiscovery: Reading JAR file: '/Users/Dopson/work/other/java-class-enumeration/build/ClassEnumerator_test.jar'
ClassDiscovery: JarEntry 'META-INF/'  =>  class 'null'
ClassDiscovery: JarEntry 'META-INF/MANIFEST.MF'  =>  class 'null'
ClassDiscovery: JarEntry 'pro/'  =>  class 'null'
ClassDiscovery: JarEntry 'pro/ddopson/'  =>  class 'null'
ClassDiscovery: JarEntry 'pro/ddopson/ClassEnumerator.class'  =>  class 'null'
ClassDiscovery: JarEntry 'test/'  =>  class 'null'
ClassDiscovery: JarEntry 'test/ClassIShouldFindOne.class'  =>  class 'test.ClassIShouldFindOne'
ClassDiscovery: JarEntry 'test/ClassIShouldFindTwo.class'  =>  class 'test.ClassIShouldFindTwo'
ClassDiscovery: JarEntry 'test/subpkg/'  =>  class 'null'
ClassDiscovery: JarEntry 'test/subpkg/ClassIShouldFindThree.class'  =>  class 'test.subpkg.ClassIShouldFindThree'
ClassDiscovery: JarEntry 'test/TestClassEnumeration.class'  =>  class 'test.TestClassEnumeration'
 Tests Passed. 

Voir aussi mon autre réponse

Dave Dopson
la source
4

Oui, en utilisant quelques API, vous pouvez, voici comment j'aime le faire, face à ce problème que j'utilisais en mode hibernate core et j'ai dû trouver des classes qui étaient annotées avec une certaine annotation.

Faites-en une annotation personnalisée à l'aide de laquelle vous marquerez les classes que vous souhaitez choisir.

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface EntityToBeScanned {

}

Puis marquez votre classe avec comme

@EntityToBeScanned 
public MyClass{

}

Faites cette classe utilitaire qui a la méthode suivante

public class ClassScanner {

    public static Set<Class<?>> allFoundClassesAnnotatedWithEntityToBeScanned(){
        Reflections reflections = new Reflections(".*");
        Set<Class<?>> annotated = reflections.getTypesAnnotatedWith(EntityToBeScanned.class);
        return annotated;
    }

}

Appelez la méthode allFoundClassesAnnotatedWithEntityToBeScanned () pour obtenir un ensemble de classes trouvé.

Vous aurez besoin des bibliothèques fournies ci-dessous

<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>21.0</version>
    </dependency>
<!-- https://mvnrepository.com/artifact/org.javassist/javassist -->
<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.22.0-CR1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.reflections/reflections -->
<dependency>
    <groupId>org.reflections</groupId>
    <artifactId>reflections</artifactId>
    <version>0.9.10</version>
</dependency>
Sujal Mandal
la source
3

Vous devez rechercher chaque entrée de chargeur de classe dans le chemin de classe:

    String pkg = "org/apache/commons/lang";
    ClassLoader cl = ClassLoader.getSystemClassLoader();
    URL[] urls = ((URLClassLoader) cl).getURLs();
    for (URL url : urls) {
        System.out.println(url.getFile());
        File jar = new File(url.getFile());
        // ....
    }   

Si l'entrée est un répertoire, recherchez simplement dans le sous-répertoire de droite:

if (jar.isDirectory()) {
    File subdir = new File(jar, pkg);
    if (!subdir.exists())
        continue;
    File[] files = subdir.listFiles();
    for (File file : files) {
        if (!file.isFile())
            continue;
        if (file.getName().endsWith(".class"))
            System.out.println("Found class: "
                    + file.getName().substring(0,
                            file.getName().length() - 6));
    }
}   

Si l'entrée est le fichier et qu'il s'agit d'un fichier jar, inspectez les entrées ZIP de celui-ci:

else {
    // try to open as ZIP
    try {
        ZipFile zip = new ZipFile(jar);
        for (Enumeration<? extends ZipEntry> entries = zip
                .entries(); entries.hasMoreElements();) {
            ZipEntry entry = entries.nextElement();
            String name = entry.getName();
            if (!name.startsWith(pkg))
                continue;
            name = name.substring(pkg.length() + 1);
            if (name.indexOf('/') < 0 && name.endsWith(".class"))
                System.out.println("Found class: "
                        + name.substring(0, name.length() - 6));
        }
    } catch (ZipException e) {
        System.out.println("Not a ZIP: " + e.getMessage());
    } catch (IOException e) {
        System.err.println(e.getMessage());
    }
}

Maintenant, une fois que vous avez tous les noms de classe avec package, vous pouvez essayer de les charger avec réflexion et analyser s'il s'agit de classes ou d'interfaces, etc.

Marin danubien
la source
Que saisiriez-vous pour un package dans un fichier Jar?
Kyle Bridenstine
Cet exemple ne passera pas par les sous-packages. Peut-être que cela intéresse certains ... @ mr-tea Précisez simplement le package que vous recherchez. J'ai mis cela dans un projet, spécifié un package de test dans ce projet, compilé et empaqueté et appelé l'exemple de la méthode principale du JAR. A fonctionné comme un charme. :)
Christian
3

J'ai essayé d'utiliser la bibliothèque Reflections, mais j'ai eu quelques problèmes à l'utiliser, et il y avait trop de pots que je devrais inclure juste pour obtenir simplement les classes sur un package.

Je posterai une solution que j'ai trouvée dans cette question en double: Comment obtenir tous les noms de classes dans un package?

La réponse a été écrite par sp00m ; J'ai ajouté quelques corrections pour le faire fonctionner:

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;

public final class ClassFinder {

    private final static char DOT = '.';
    private final static char SLASH = '/';
    private final static String CLASS_SUFFIX = ".class";
    private final static String BAD_PACKAGE_ERROR = "Unable to get resources from path '%s'. Are you sure the given '%s' package exists?";

    public final static List<Class<?>> find(final String scannedPackage) {
        final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        final String scannedPath = scannedPackage.replace(DOT, SLASH);
        final Enumeration<URL> resources;
        try {
            resources = classLoader.getResources(scannedPath);
        } catch (IOException e) {
            throw new IllegalArgumentException(String.format(BAD_PACKAGE_ERROR, scannedPath, scannedPackage), e);
        }
        final List<Class<?>> classes = new LinkedList<Class<?>>();
        while (resources.hasMoreElements()) {
            final File file = new File(resources.nextElement().getFile());
            classes.addAll(find(file, scannedPackage));
        }
        return classes;
    }

    private final static List<Class<?>> find(final File file, final String scannedPackage) {
        final List<Class<?>> classes = new LinkedList<Class<?>>();
        if (file.isDirectory()) {
            for (File nestedFile : file.listFiles()) {
                classes.addAll(find(nestedFile, scannedPackage));
            }
        //File names with the $1, $2 holds the anonymous inner classes, we are not interested on them. 
        } else if (file.getName().endsWith(CLASS_SUFFIX) && !file.getName().contains("$")) {

            final int beginIndex = 0;
            final int endIndex = file.getName().length() - CLASS_SUFFIX.length();
            final String className = file.getName().substring(beginIndex, endIndex);
            try {
                final String resource = scannedPackage + DOT + className;
                classes.add(Class.forName(resource));
            } catch (ClassNotFoundException ignore) {
            }
        }
        return classes;
    }

}

Pour l'utiliser, il suffit d'appeler la méthode find comme sp00n mentionnée dans cet exemple: j'ai ajouté la création d'instances des classes si nécessaire.

List<Class<?>> classes = ClassFinder.find("com.package");

ExcelReporting excelReporting;
for (Class<?> aClass : classes) {
    Constructor constructor = aClass.getConstructor();
    //Create an object of the class type
    constructor.newInstance();
    //...
}
Martín C
la source
3

Je viens d'écrire une classe util, elle inclut des méthodes de test, vous pouvez vérifier ~

IteratePackageUtil.java:

package eric.j2se.reflect;

import java.util.Set;

import org.reflections.Reflections;
import org.reflections.scanners.ResourcesScanner;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;

/**
 * an util to iterate class in a package,
 * 
 * @author eric
 * @date Dec 10, 2013 12:36:46 AM
 */
public class IteratePackageUtil {
    /**
     * <p>
     * Get set of all class in a specified package recursively. this only support lib
     * </p>
     * <p>
     * class of sub package will be included, inner class will be included,
     * </p>
     * <p>
     * could load class that use the same classloader of current class, can't load system packages,
     * </p>
     * 
     * @param pkg
     *            path of a package
     * @return
     */
    public static Set<Class<? extends Object>> getClazzSet(String pkg) {
        // prepare reflection, include direct subclass of Object.class
        Reflections reflections = new Reflections(new ConfigurationBuilder().setScanners(new SubTypesScanner(false), new ResourcesScanner())
                .setUrls(ClasspathHelper.forClassLoader(ClasspathHelper.classLoaders(new ClassLoader[0])))
                .filterInputsBy(new FilterBuilder().includePackage(pkg)));

        return reflections.getSubTypesOf(Object.class);
    }

    public static void test() {
        String pkg = "org.apache.tomcat.util";

        Set<Class<? extends Object>> clazzSet = getClazzSet(pkg);
        for (Class<? extends Object> clazz : clazzSet) {
            System.out.println(clazz.getName());
        }
    }

    public static void main(String[] args) {
        test();
    }
}
Eric Wang
la source
3

La solution d'Aleksander Blomskøld n'a pas fonctionné pour moi pour les tests paramétrés @RunWith(Parameterized.class)lors de l'utilisation de Maven. Les tests ont été nommés correctement et ont également été trouvés mais pas exécutés:

-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running some.properly.named.test.run.with.maven.SomeTest
Tests run: 0, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.123 sec

Un problème similaire a été signalé ici .

Dans mon cas, @Parametersc'est la création d'instances de chaque classe dans un package. Les tests ont bien fonctionné lorsqu'ils étaient exécutés localement dans l'EDI. Cependant, lors de l'exécution de Maven, aucune classe n'a été trouvée avec la solution d'Aleksander Blomskøld.

Je l'ai fait fonctionner avec les extraits suivants qui ont été inspirés par le commentaire de David Pärsson sur la réponse d'Aleksander Blomskøld:

Reflections reflections = new Reflections(new ConfigurationBuilder()
            .setScanners(new SubTypesScanner(false /* don't exclude Object.class */), new ResourcesScanner())
            .addUrls(ClasspathHelper.forJavaClassPath()) 
            .filterInputsBy(new FilterBuilder()
            .include(FilterBuilder.prefix(basePackage))));

Set<Class<?>> subTypesOf = reflections.getSubTypesOf(Object.class);
Thorsten
la source
3

Et ça:

public static List<Class<?>> getClassesForPackage(final String pkgName) throws IOException, URISyntaxException {
    final String pkgPath = pkgName.replace('.', '/');
    final URI pkg = Objects.requireNonNull(ClassLoader.getSystemClassLoader().getResource(pkgPath)).toURI();
    final ArrayList<Class<?>> allClasses = new ArrayList<Class<?>>();

    Path root;
    if (pkg.toString().startsWith("jar:")) {
        try {
            root = FileSystems.getFileSystem(pkg).getPath(pkgPath);
        } catch (final FileSystemNotFoundException e) {
            root = FileSystems.newFileSystem(pkg, Collections.emptyMap()).getPath(pkgPath);
        }
    } else {
        root = Paths.get(pkg);
    }

    final String extension = ".class";
    try (final Stream<Path> allPaths = Files.walk(root)) {
        allPaths.filter(Files::isRegularFile).forEach(file -> {
            try {
                final String path = file.toString().replace('/', '.');
                final String name = path.substring(path.indexOf(pkgName), path.length() - extension.length());
                allClasses.add(Class.forName(name));
            } catch (final ClassNotFoundException | StringIndexOutOfBoundsException ignored) {
            }
        });
    }
    return allClasses;
}

Vous pouvez alors surcharger la fonction:

public static List<Class<?>> getClassesForPackage(final Package pkg) throws IOException, URISyntaxException {
    return getClassesForPackage(pkg.getName());
}

Si vous devez le tester:

public static void main(final String[] argv) throws IOException, URISyntaxException {
    for (final Class<?> cls : getClassesForPackage("my.package")) {
        System.out.println(cls);
    }
    for (final Class<?> cls : getClassesForPackage(MyClass.class.getPackage())) {
        System.out.println(cls);
    }
}

Si votre IDE n'a pas d'aide à l'importation:

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.FileSystemNotFoundException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;

Ça marche:

  • de votre IDE

  • pour un fichier JAR

  • sans dépendances externes

tirz
la source
2

Presque toutes les réponses utilisent Reflectionsou lisent les fichiers de classe du système de fichiers. Si vous essayez de lire des classes à partir du système de fichiers, vous pouvez obtenir des erreurs lorsque vous empaquetez votre application au format JAR ou autre. Vous pouvez également ne pas vouloir utiliser une bibliothèque distincte à cette fin.

Voici une autre approche qui est pure java et ne dépend pas du système de fichiers.

import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

public class PackageUtil {

    public static Collection<Class> getClasses(final String pack) throws Exception {
        final StandardJavaFileManager fileManager = ToolProvider.getSystemJavaCompiler().getStandardFileManager(null, null, null);
        return StreamSupport.stream(fileManager.list(StandardLocation.CLASS_PATH, pack, Collections.singleton(JavaFileObject.Kind.CLASS), false).spliterator(), false)
                .map(javaFileObject -> {
                    try {
                        final String[] split = javaFileObject.getName()
                                .replace(".class", "")
                                .replace(")", "")
                                .split(Pattern.quote(File.separator));

                        final String fullClassName = pack + "." + split[split.length - 1];
                        return Class.forName(fullClassName);
                    } catch (ClassNotFoundException e) {
                        throw new RuntimeException(e);
                    }

                })
                .collect(Collectors.toCollection(ArrayList::new));
    }
}

Java 8 n'est pas indispensable . Vous pouvez utiliser des boucles au lieu de flux. Et vous pouvez le tester comme ça

public static void main(String[] args) throws Exception {
    final String pack = "java.nio.file"; // Or any other package
    PackageUtil.getClasses(pack).stream().forEach(System.out::println);
}
bhdrkn
la source
1
Il n'est pas très utile à cause de: il faut avoir JDK à utiliser ToolProvider.getSystemJavaCompiler(), ce code n'analyse pas les packages imbriqués.
v.ladynev
Je ne peux pas le faire fonctionner avec un paquet d'un pot externe
Enrico Giurin
1

Si vous n'utilisez aucun chargeur de classe dynamique, vous pouvez rechercher le chemin de classe et pour chaque entrée, recherchez le répertoire ou le fichier JAR.

Lawrence Dol
la source
1

À noter

Si vous souhaitez avoir une liste de toutes les classes sous un package, vous pouvez utiliser Reflectionla manière suivante:

List<Class> myTypes = new ArrayList<>();

Reflections reflections = new Reflections("com.package");
for (String s : reflections.getStore().get(SubTypesScanner.class).values()) {
    myTypes.add(Class.forName(s));
}

Cela créera une liste de classes que vous pourrez ensuite utiliser comme vous le souhaitez.

Maroun
la source
1

C'est très possible, mais sans bibliothèques supplémentaires comme Reflectionsc'est difficile ...
C'est difficile parce que vous n'avez pas d'instrument complet pour obtenir le nom de la classe.
Et, je prends le code de ma ClassFinderclasse:

package play.util;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * Created by LINKOR on 26.05.2017 in 15:12.
 * Date: 2017.05.26
 */
public class FileClassFinder {
private JarFile file;
private boolean trouble;
public FileClassFinder(String filePath) {
    try {
        file = new JarFile(filePath);
    } catch (IOException e) {
        trouble = true;
    }
}

public List<String> findClasses(String pkg) {
    ArrayList<String> classes = new ArrayList<>();
    Enumeration<JarEntry> entries = file.entries();
    while (entries.hasMoreElements()) {
        JarEntry cls = entries.nextElement();
        if (!cls.isDirectory()) {
            String fileName = cls.getName();
            String className = fileName.replaceAll("/",         ".").replaceAll(File.pathSeparator, ".").substring(0, fileName.lastIndexOf('.'));
            if (className.startsWith(pkg)) classes.add(className.substring(pkg.length() + 1));
        }
    }
    return classes;
}
}
Muskovets
la source
0

Sur la base de la réponse de @ Staale , et dans une tentative de ne pas compter sur des bibliothèques tierces, j'implémenterais l'approche du système de fichiers en inspectant l'emplacement physique du premier package avec:

import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
...
Class<?>[] foundClasses = new Class<?>[0];
final ArrayList<Class<?>> foundClassesDyn = new ArrayList<Class<?>>();

new java.io.File(
    klass.getResource(
        "/" + curPackage.replace( "." , "/")
    ).getFile()
).listFiles(
    new java.io.FileFilter() {
        public boolean accept(java.io.File file) {
            final String classExtension = ".class";

            if ( file.isFile()
                && file.getName().endsWith(classExtension)
                // avoid inner classes
                && ! file.getName().contains("$") )
            {
                try {
                    String className = file.getName();
                    className = className.substring(0, className.length() - classExtension.length());
                    foundClassesDyn.add( Class.forName( curPackage + "." + className ) );
                } catch (ClassNotFoundException e) {
                    e.printStackTrace(System.out);
                }
            }

            return false;
        }
    }
);

foundClasses = foundClassesDyn.toArray(foundClasses);
Communauté
la source
0

Si vous cherchez simplement à charger un groupe de classes connexes, Spring peut vous aider.

Spring peut instancier une liste ou une carte de toutes les classes qui implémentent une interface donnée dans une seule ligne de code. La liste ou la carte contiendra des instances de toutes les classes qui implémentent cette interface.

Cela étant dit, au lieu de charger la liste des classes hors du système de fichiers, implémentez simplement la même interface dans toutes les classes que vous souhaitez charger, quel que soit le package et utilisez Spring pour vous fournir des instances de toutes. De cette façon, vous pouvez charger (et instancier) toutes les classes que vous désirez, quel que soit le package dans lequel elles se trouvent.

D'un autre côté, si vous les avez tous dans un package, c'est ce que vous voulez, alors demandez simplement à toutes les classes de ce package d'implémenter une interface donnée.

Rodney P. Barbati
la source
0

Java simple: FindAllClassesUsingPlainJavaReflectionTest.java

@Slf4j
class FindAllClassesUsingPlainJavaReflectionTest {

  private static final Function<Throwable, RuntimeException> asRuntimeException = throwable -> {
    log.error(throwable.getLocalizedMessage());
    return new RuntimeException(throwable);
  };

  private static final Function<String, Collection<Class<?>>> findAllPackageClasses = basePackageName -> {

    Locale locale = Locale.getDefault();
    Charset charset = StandardCharsets.UTF_8;
    val fileManager = ToolProvider.getSystemJavaCompiler()
                                  .getStandardFileManager(/* diagnosticListener */ null, locale, charset);

    StandardLocation location = StandardLocation.CLASS_PATH;
    JavaFileObject.Kind kind = JavaFileObject.Kind.CLASS;
    Set<JavaFileObject.Kind> kinds = Collections.singleton(kind);
    val javaFileObjects = Try.of(() -> fileManager.list(location, basePackageName, kinds, /* recurse */ true))
                             .getOrElseThrow(asRuntimeException);

    String pathToPackageAndClass = basePackageName.replace(".", File.separator);
    Function<String, String> mapToClassName = s -> {
      String prefix = Arrays.stream(s.split(pathToPackageAndClass))
                            .findFirst()
                            .orElse("");
      return s.replaceFirst(prefix, "")
              .replaceAll(File.separator, ".");
    };

    return StreamSupport.stream(javaFileObjects.spliterator(), /* parallel */ true)
                        .filter(javaFileObject -> javaFileObject.getKind().equals(kind))
                        .map(FileObject::getName)
                        .map(fileObjectName -> fileObjectName.replace(".class", ""))
                        .map(mapToClassName)
                        .map(className -> Try.of(() -> Class.forName(className))
                                             .getOrElseThrow(asRuntimeException))
                        .collect(Collectors.toList());
  };

  @Test
  @DisplayName("should get classes recursively in given package")
  void test() {
    Collection<Class<?>> classes = findAllPackageClasses.apply(getClass().getPackage().getName());
    assertThat(classes).hasSizeGreaterThan(4);
    classes.stream().map(String::valueOf).forEach(log::info);
  }
}

PS: pour simplifier les passe-partout pour la gestion des erreurs, etc., j'utilise ici vavret les lombokbibliothèques

d'autres implémentations peuvent être trouvées dans mon référentiel GitHub daggerok / java-réflexion-find-annotated-classes-or-methods

Maksim Kostromin
la source
0

Je n'ai pas pu trouver un court travail découpé pour quelque chose d'aussi simple. Alors voilà, je l'ai fait moi-même après avoir vissé pendant un moment:

    Reflections reflections =
        new Reflections(new ConfigurationBuilder()
                .filterInputsBy(new FilterBuilder().includePackage(packagePath))
                .setUrls(ClasspathHelper.forPackage(packagePath))
                .setScanners(new SubTypesScanner(false)));

    Set<String> typeList = reflections.getAllTypes(); 
botenvouwer
la source
0

Si vous êtes au printemps, vous pouvez utiliser PathMatchingResourcePatternResolver;

  PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
  Resource[] resources = resolver.getResources("classpath*:some/package/name/*.class");

    Arrays.asList(resources).forEach(r->{
        ...
    });
Noir
la source
-4

Ce n'est pas possible, car toutes les classes du package peuvent ne pas être chargées, alors que vous connaissez toujours le package d'une classe.

Marko
la source