web-dev-qa-db-fra.com

Pouvez-vous trouver toutes les classes d'un paquet utilisant la réflexion?

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

468
Jonik

En raison de la nature dynamique des chargeurs de classes, cela n’est pas possible. Les chargeurs de classes ne sont pas tenus d'indiquer à la VM les classes qu'il peut fournir. Ils se contentent de transmettre des demandes de classes et doivent renvoyer une classe ou renvoyer une exception.

Toutefois, si vous écrivez vos propres chargeurs de classe ou examinez les chemins de classe et ses fichiers jars, il est possible de trouver cette information. Ce sera via les opérations du 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 les découvrir.

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

Addendum: La bibliothèque de réflexions vous permettra de rechercher des classes dans le classpath actuel. Il peut être utilisé pour obtenir toutes les classes d'un package:

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

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

Vous devriez probablement jeter un coup d'œil à l'open source Bibliothèque de réflexions . Avec cela, vous pouvez facilement atteindre ce que vous voulez.

Commencez par configurer 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 paquet donné:

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

Google Guava 14 comprend une nouvelle classe ClassPath avec trois méthodes d'analyse des classes de niveau supérieur:

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

Voir le ClassPath javadocs pour plus d’informations.

111
Christoph Leiter

Vous pouvez utiliser cette méthode1 qui utilise la variable 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 , qui était archivé par Internet Archive, avec un lien vers maintenant. L'extrait de code est également disponible à l'adresse https://dzone.com/articles/get-all-classes-within-package .

96
user59634

Printemps

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

Remarque: Dans la version 14, l'API est toujours marquée @Beta, 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
  }
}
82
voho

Bonjour. J'ai toujours eu des 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 permet 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 autour de chaque configuration. Après de nombreuses recherches, j'ai mis au point une méthode qui fonctionnera dans au moins 95% des 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 de la ClassLoader courante.

/**
 * 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 rechercher 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 la variable ClassLoader actuelle. Il récupère ensuite toutes les ressources contenant ledit package et effectue une itération de ces URLs. Il crée ensuite une URLConnection et 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 un fichier Zip (JarURLConnection). Selon le type de connexion utilisé, deux méthodes différentes seront appelées.

Voyons d’abord ce qui se passe s’il s’agit d’une 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 c'est le cas, un objet Class sera créé et placé dans la ArrayList. Si ce n'est pas un fichier de classe, mais un répertoire, nous le parcourons simplement et faisons la même chose. Tous les autres cas/fichiers seront ignorés.

Si la URLConnection est une JarURLConnection, l'autre méthode d'assistance privée sera appelée. Cette méthode itère sur 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 objet Class sera créé et stocké dans ArrayList.

Une fois que toutes les ressources ont été analysées, la méthode principale retourne la ArrayList contenant toutes les classes du package donné, que la ClassLoader actuelle connaît.

Si le processus échoue à tout moment, une ClassNotFoundException sera renvoyée contenant des informations détaillées sur la cause exacte.

31
BrainStone

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;
    }
}
12
Williams López

En général, les chargeurs de classes ne permettent pas de parcourir toutes les classes du classpath. Mais habituellement, le seul chargeur de classes 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 balayage de chemin de classe, est implémentée dans Scannotation et Reflections .

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

Une autre approche consiste à utiliser API de traitement d'annotations Java connectable 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 ClassIndex library:

// 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, car le compilateur Java découvre automatiquement tous les processeurs trouvés dans le chemin d'accès aux classes.

12
Sławek

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 de chargement de classe et de classLoaders, a été soigneusement parallélisé et est hautement optimisé pour une utilisation à grande vitesse et à faible consommation de mémoire. Il peut même générer une visualisation GraphViz du graphe de classes, 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 existe de nombreuses variantes possibles - voir la documentation (lien ci-dessus) pour plus d’informations.

6
Luke Hutchison

Voici comment je le fais. J'analyse tous les sous-dossiers (sous-packages) et je n'essaie 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;
  }  
5
Nadav B

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

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

Cela devrait fonctionner pour les deux classpaths ET pour les fichiers jar.

Si vous lancez 'make' après avoir vérifié le projet, il l’affiche:

 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

4
Dave Dopson

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

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

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 marque ta classe avec comme

@EntityToBeScanned 
public MyClass{

}

Faire cette classe d'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 Set of Classes found.

Vous aurez besoin des libs donnés 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>
4
Sujal Mandal

J'essayais d'utiliser la bibliothèque Reflections, mais je rencontrais des problèmes pour l'utiliser et il y avait trop de fichiers jar que je devrais inclure pour obtenir simplement les classes d'un package.

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

La réponse a été écrite par sp00m ; J'ai ajouté quelques corrections pour que cela fonctionne:

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, appelez simplement la méthode find 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();
    //...
}
3
Martín C

Vous devez rechercher chaque entrée du 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 entry est un répertoire, il suffit de regarder 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 son fichier jar, inspectez ses entrées Zip: 

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 classes dans le paquet, vous pouvez essayer de les charger avec réflexion et d’analyser s’il s’agit de classes ou d’interfaces, etc.

3
Danubian Sailor

Presque toutes les réponses utilisent Reflections ou lisent des fichiers de classe à partir du système de fichiers. Si vous essayez de lire des classes à partir du système de fichiers, des erreurs peuvent survenir lorsque vous empaquetez votre application au format JAR ou autre. Aussi, vous ne voudrez peut-être pas utiliser une bibliothèque séparée à cette fin. 

Voici une autre approche qui est purement 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 un must . Vous pouvez utiliser des boucles for à la place des flux ..__ Et vous pouvez le tester comme ceci

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);
}
2
bhdrkn

À noter

Si vous voulez avoir une liste de toutes les classes sous un paquet, vous pouvez utiliser Reflection de la 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 à votre guise.

2
Maroun

La solution d'Aleksander Blomskøld n'a pas fonctionné pour moi pour les tests paramétrés @RunWith(Parameterized.class) avec Maven Les tests ont été nommés correctement et également là où ils ont été trouvés mais non 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, @Parameters crée des instances de chaque classe dans un package. Les tests ont bien fonctionné lorsqu'ils ont été exécutés localement dans l'EDI. Cependant, lors de l'exécution de Maven, aucune solution n'a été trouvée avec la solution d'Aleksander Blomskøld.

Je l'ai fait fonctionner avec le morceau suivant qui a été inspiré 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);
2
Thorsten

Si vous n'utilisez pas de programme de chargement de classe dynamique, vous pouvez rechercher le chemin d'accès aux classes et, pour chaque entrée, le répertoire ou le fichier JAR.

1
Lawrence Dol

Je viens d'écrire une classe d'util, elle comprend des méthodes de test, vous pouvez avoir un contrôle ~

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

C'est très possible, mais sans bibliothèques supplémentaires comme Reflections c'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 classe ClassFinder:

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

plain Java: 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-temps en cas d'erreur de traitement, etc., j'utilise ici les bibliothèques vavr et lombok

d’autres implémentations pourraient être trouvées dans mon repo Daggerok/Java-réflexion-trouver-annoté-méthodes-ou-méthodes GitHub

0
Maksim Kostromin

Sur la base de la réponse de @ Staale , et dans le but de ne pas compter sur des bibliothèques tierces, je mettrais en œuvre l'approche du système de fichiers en inspectant l'emplacement physique du premier paquet 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);
0
user1026870

Si vous souhaitez simplement charger un groupe de classes liées, alors 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 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 en dehors du système de fichiers, implémentez simplement la même interface dans toutes les classes que vous voulez 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 de votre choix, quel que soit le paquet dans lequel elles se trouvent.

D'un autre côté, si vous souhaitez les avoir tous dans un package, vous devez simplement faire en sorte que toutes les classes de ce package implémentent une interface donnée.

0