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


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


439
2018-02-06 13:43


origine


Réponses:


En raison de la nature dynamique des chargeurs de classe, cela n'est pas possible. Les chargeurs de classe ne sont pas tenus de dire à la machine virtuelle quelles classes elle peut fournir, ils sont simplement des demandes de classes, et doivent renvoyer une classe ou lancer une exception.

Cependant, si vous écrivez vos propres chargeurs de classes, ou examinez les classpaths et leurs jars, il est possible de trouver cette information. Ce sera par l'intermédiaire des opérations de système de fichiers bien, et pas de réflexion. Il pourrait même y avoir des bibliothèques qui peuvent vous aider à le faire.

Si des classes sont générées ou livrées à distance, vous ne pourrez pas découvrir ces classes.

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

Addenda: La bibliothèque de réflexions vous permettra de rechercher des classes dans le classpath courant. Il peut être utilisé pour obtenir toutes les classes dans un paquet:

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

 Set<Class<? extends Object>> allClasses = 
     reflections.getSubTypesOf(Object.class);

313
2018-02-06 13:47



Vous devriez probablement regarder l'open source Bibliothèque de réflexions. Avec cela, vous pouvez facilement réaliser ce que vous voulez.

D'abord, configurez l'index des réflexions (c'est un peu brouillon puisque 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"))));

Vous pouvez ensuite interroger tous les objets d'un package donné:

Set<Class<?>> classes = reflections.getSubTypesOf(Object.class);

159
2018-03-05 17:29



Google Guava 14 inclut une nouvelle classe ClassPath avec trois méthodes pour analyser les classes de niveau supérieur:

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

Voir le ClassPath javadocs pour plus d'informations.


104
2017-11-20 16:06



Vous pourriez utiliser cette méthode1 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é prise à partir de http://snippets.dzone.com/posts/show/4831, ce qui était archivé par Internet Archive, comme lié à maintenant. L'extrait est également disponible à l'adresse https://dzone.com/articles/get-all-classes-within-package.


83
2018-02-06 13:49



Printemps

Cet exemple concerne Spring 4, mais vous pouvez également trouver le scanner classpath 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 Goyave

Remarque: En version 14, l'API est toujours marquée comme @Bêta, alors méfiez-vous du 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
  }
}

75
2018-01-29 12:15



Bonjour. J'ai toujours eu quelques problèmes avec les solutions ci-dessus (et sur d'autres sites).
En tant que développeur, je suis en train de programmer un addon pour une API. L'API autorise 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. Donc, mon code devait être capable de fonctionner à chaque installation. 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 d'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 du 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 d'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 ce paquet et les itérations de ces URLs. Il crée ensuite un URLConnection et détermine quel type d'URL nous avons. Il peut s'agir soit d'un répertoire (FileURLConnection) ou un répertoire dans un fichier jar ou zip (JarURLConnection). Selon le type de connexion que nous avons, deux méthodes différentes seront appelées.

D'abord, voyons ce qui se passe si c'est un FileURLConnection.
Il vérifie d'abord si le fichier transmis existe et est un répertoire. Si c'est le cas, il vérifie s'il s'agit d'un fichier de classe. Si oui Class objet sera créé et mis dans le ArrayList. Si ce n'est pas un fichier de classe mais un répertoire, il suffit de l'itérer et de faire la même chose. Tous les autres cas / fichiers seront ignorés.

Si la URLConnection est un JarURLConnection l'autre méthode d'assistance privée sera appelée. Cette méthode itère sur toutes les entrées dans l'archive zip / jar. Si une entrée est un fichier de classe et est à l'intérieur du paquet Class objet sera créé et stocké dans le ArrayList.

Après que toutes les ressources ont été analysées (la méthode principale) renvoie le ArrayList contient toutes les classes du paquet donné, que le courant ClassLoader sait à propos

Si le processus échoue à n'importe quel moment ClassNotFoundException sera jeté containg des informations détaillées sur la cause exacte.


29
2018-03-17 18:47



Sans utiliser aucune bibliothèque supplémentaire:

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

12
2018-02-17 22:48



En général, les chargeurs de classes ne permettent pas de parcourir toutes les classes du chemin de classes. 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 lister les classes disponibles. Cette approche, appelée analyse de chemin de classe, est implémentée dans Scannotation et Réflexions.

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

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

// 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écouvre automatiquement les processeurs trouvés sur le chemin de classe.


10
2017-12-22 12:02



J'ai écrit FastClasspathScanner pour résoudre ce problème. Il gère de nombreux types de tâches d'analyse de chemins de classes, possède une API simple, fonctionne avec de nombreux environnements ClassLoaders et classpath différents, a été soigneusement parallélisé et est hautement optimisé pour une grande vitesse et une faible consommation de mémoire. Il peut même générer une visualisation GraphViz du graphe de classe, montrant comment les classes sont connectées les unes aux autres.

Pour votre question initiale de trouver toutes les classes ou interfaces dans un paquet donné, vous pouvez faire:

List<String> classNames = new FastClasspathScanner("com.mypackage").scan()
    .getNamesOfAllClasses();

Il y a beaucoup de variantes possibles à ceci - voyez la documentation (liée ci-dessus) pour l'information complète.


6
2017-07-31 01:48