Comment lire toutes les classes d'un package Java dans le chemin de classe?

94

J'ai besoin de lire les classes contenues dans un package Java. Ces classes sont dans classpath. Je dois effectuer cette tâche directement à partir d'un programme Java. Connaissez-vous une façon simple de faire?

List<Class> classes = readClassesFrom("my.package")
Jørgen R
la source
7
En termes simples, non, vous ne pouvez pas faire cela facilement. Il existe des astuces extrêmement longues qui fonctionnent dans certaines situations, mais je suggère fortement une conception différente.
skaffman
La solution peut être trouvée dans le projet Weld .
Ondra Žižka
Reportez-vous à ce lien pour obtenir la réponse: stackoverflow.com/questions/176527/…
Karthik E

Réponses:

48

Si vous avez Spring dans votre classpath, ce qui suit le fera.

Recherchez toutes les classes d'un package annotées avec XmlRootElement:

private List<Class> findMyTypes(String basePackage) throws IOException, ClassNotFoundException
{
    ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
    MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);

    List<Class> candidates = new ArrayList<Class>();
    String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                               resolveBasePackage(basePackage) + "/" + "**/*.class";
    Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
    for (Resource resource : resources) {
        if (resource.isReadable()) {
            MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
            if (isCandidate(metadataReader)) {
                candidates.add(Class.forName(metadataReader.getClassMetadata().getClassName()));
            }
        }
    }
    return candidates;
}

private String resolveBasePackage(String basePackage) {
    return ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(basePackage));
}

private boolean isCandidate(MetadataReader metadataReader) throws ClassNotFoundException
{
    try {
        Class c = Class.forName(metadataReader.getClassMetadata().getClassName());
        if (c.getAnnotation(XmlRootElement.class) != null) {
            return true;
        }
    }
    catch(Throwable e){
    }
    return false;
}
Shalom938
la source
Merci pour l'extrait de code. :) Si vous souhaitez éviter la duplication de classe dans une liste de candidats, modifiez le type de collection de Liste à Ensemble. Et utilisez hasEnclosingClass () et getEnclosingClassName () de classMetaData. Utilisez la méthode getEnclosingClass sur une classe sous-jacente
traeper
29

Vous pouvez utiliser le projet Reflections décrit ici

C'est assez complet et facile à utiliser.

Brève description du site Web ci-dessus:

Reflections analyse votre chemin de classe, indexe les métadonnées, vous permet de l'interroger lors de l'exécution et peut enregistrer et collecter ces informations pour de nombreux modules de votre projet.

Exemple:

Reflections reflections = new Reflections(
    new ConfigurationBuilder()
        .setUrls(ClasspathHelper.forJavaClassPath())
);
Set<Class<?>> types = reflections.getTypesAnnotatedWith(Scannable.class);
Renato
la source
Fonctionne-t-il dans Equinox? Et si oui, peut-il le faire sans activer les plugins?
Tag
fonctionne pour l'équinoxe. sur les anciennes versions de Reflections, ajoutez le bundle jar vfsType. voir ici
zapp
28

J'utilise celui-ci, il fonctionne avec des fichiers ou des archives jar

public static ArrayList<String>getClassNamesFromPackage(String packageName) throws IOException{
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    URL packageURL;
    ArrayList<String> names = new ArrayList<String>();;

    packageName = packageName.replace(".", "/");
    packageURL = classLoader.getResource(packageName);

    if(packageURL.getProtocol().equals("jar")){
        String jarFileName;
        JarFile jf ;
        Enumeration<JarEntry> jarEntries;
        String entryName;

        // build jar file name, then loop through zipped entries
        jarFileName = URLDecoder.decode(packageURL.getFile(), "UTF-8");
        jarFileName = jarFileName.substring(5,jarFileName.indexOf("!"));
        System.out.println(">"+jarFileName);
        jf = new JarFile(jarFileName);
        jarEntries = jf.entries();
        while(jarEntries.hasMoreElements()){
            entryName = jarEntries.nextElement().getName();
            if(entryName.startsWith(packageName) && entryName.length()>packageName.length()+5){
                entryName = entryName.substring(packageName.length(),entryName.lastIndexOf('.'));
                names.add(entryName);
            }
        }

    // loop through files in classpath
    }else{
    URI uri = new URI(packageURL.toString());
    File folder = new File(uri.getPath());
        // won't work with path which contains blank (%20)
        // File folder = new File(packageURL.getFile()); 
        File[] contenuti = folder.listFiles();
        String entryName;
        for(File actual: contenuti){
            entryName = actual.getName();
            entryName = entryName.substring(0, entryName.lastIndexOf('.'));
            names.add(entryName);
        }
    }
    return names;
}
Edoardo Panfili
la source
1
cela fonctionne si vous supprimez la référence à File.Separator et utilisez simplement '/'
Will Glass
1
Une autre modification est nécessaire pour que cela fonctionne avec les fichiers JAR lorsque le chemin contient des espaces. Vous devez décoder le chemin du fichier jar. Modification: jarFileName = packageURL.getFile (); à: jarFileName = URLDecoder.decode (packageURL.getFile ());
Will Glass
je n'obtiens que le nom du package dans jar..n'obtient pas le nom de la classe
AutoMEta
new File (uri) résoudra votre problème d'espaces.
Trejkaz
entryName.lastIndexOf ('.') serait -1
marstone
11

Spring a implémenté une excellente fonction de recherche de chemin de classe dans le PathMatchingResourcePatternResolver. Si vous utilisez le classpath*préfixe:, vous pouvez trouver toutes les ressources, y compris les classes dans une hiérarchie donnée, et même les filtrer si vous le souhaitez. Ensuite , vous pouvez utiliser les enfants AbstractTypeHierarchyTraversingFilter, AnnotationTypeFilteret AssignableTypeFilterde filtrer ces ressources soit sur les annotations au niveau de la classe ou sur les interfaces qu'ils mettent en œuvre.

John Ellinwood
la source
6

Java 1.6.0_24:

public static File[] getPackageContent(String packageName) throws IOException{
    ArrayList<File> list = new ArrayList<File>();
    Enumeration<URL> urls = Thread.currentThread().getContextClassLoader()
                            .getResources(packageName);
    while (urls.hasMoreElements()) {
        URL url = urls.nextElement();
        File dir = new File(url.getFile());
        for (File f : dir.listFiles()) {
            list.add(f);
        }
    }
    return list.toArray(new File[]{});
}

Cette solution a été testée dans l' environnement EJB .

Paul Kuit
la source
6

La numérotation et les réflexions utilisent une approche d'analyse de chemin de classe:

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 :

Iterable<Class> classes = ClassIndex.getPackageClasses("my.package");
Sławek
la source
3

Cette fonctionnalité est toujours suspicieusement absente de l'API de réflexion Java pour autant que je sache. Vous pouvez obtenir un objet package en faisant simplement ceci:

Package packageObj = Package.getPackage("my.package");

Mais comme vous l'avez probablement remarqué, cela ne vous permettra pas de lister les classes dans ce package. Pour le moment, vous devez adopter une sorte d'approche plus orientée système de fichiers.

J'ai trouvé quelques exemples d'implémentations dans cet article

Je ne suis pas sûr à 100% que ces méthodes fonctionneront lorsque vos classes seront enterrées dans des fichiers JAR, mais j'espère que l'une d'elles le fera pour vous.

Je suis d'accord avec @skaffman ... si vous avez une autre façon de procéder, je vous recommande de le faire à la place.

Brent écrit le code
la source
4
Ce n'est pas suspect, cela ne fonctionne tout simplement pas de cette façon. Les classes "n'appartiennent" pas aux packages, elles ont des références à eux. L'association ne pointe pas dans l'autre sens.
skaffman
1
@skaffman Point très intéressant. Je n'y ai jamais pensé de cette façon. Donc, tant que nous errons sur cette piste de pensée, pourquoi l'association n'est-elle pas bidirectionnelle (c'est plus pour ma propre curiosité maintenant)?
Brent écrit le code le
3

Le mécanisme le plus robuste pour lister 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;
try (ScanResult scanResult = new ClassGraph().whitelistPackages("my.package")
        .enableClassInfo().scan()) {
    classNames = scanResult.getAllClasses().getNames();
}
Luke Hutchison
la source
Je suis tombé dessus quelques années plus tard. C'est un excellent outil! Cela fonctionne beaucoup plus rapidement que la réflexion et j'aime le fait qu'il n'invoque pas d'initialiseurs statiques. Juste ce dont j'avais besoin pour résoudre un problème que nous avions.
akagixxer
2

eXtcos semble prometteur. Imaginez que vous vouliez trouver toutes les classes qui:

  1. Étendez la classe "Component" et stockez-les
  2. Sont annotés avec "MyComponent", et
  3. Sont dans le package «commun».

Avec eXtcos, c'est aussi simple que

ClasspathScanner scanner = new ClasspathScanner();
final Set<Class> classStore = new ArraySet<Class>();

Set<Class> classes = scanner.getClasses(new ClassQuery() {
    protected void query() {
        select().
        from(“common”).
        andStore(thoseExtending(Component.class).into(classStore)).
        returning(allAnnotatedWith(MyComponent.class));
    }
});
noelob
la source
2
  1. Bill Burke a écrit un (bel article sur la numérisation de classe), puis il a écrit Scannotation .

  2. Hibernate a déjà écrit ceci:

    • org.hibernate.ejb.packaging.Scanner
    • org.hibernate.ejb.packaging.NativeScanner
  3. CDI pourrait résoudre ce problème, mais je ne sais pas - n'a pas encore étudié complètement

.

@Inject Instance< MyClass> x;
...
x.iterator() 

Aussi pour les annotations:

abstract class MyAnnotationQualifier
extends AnnotationLiteral<Entity> implements Entity {}
Ondra Žižka
la source
Le lien vers l'article est mort.
user2418306
1

Je l'ai implémenté et cela fonctionne dans la plupart des cas. Comme il est long, je le mets dans un fichier ici .

L'idée est de trouver l'emplacement du fichier source de classe qui est disponible dans la plupart des cas (une exception connue sont les fichiers de classe JVM - pour autant que j'ai testé). Si le code se trouve dans un répertoire, parcourez tous les fichiers et repérez uniquement les fichiers de classe. Si le code se trouve dans un fichier JAR, analysez toutes les entrées.

Cette méthode ne peut être utilisée que lorsque:

  1. Vous avez une classe qui se trouve dans le même package que vous souhaitez découvrir. Cette classe s'appelle une SeedClass. Par exemple, si vous souhaitez lister toutes les classes dans «java.io», la classe de départ peut être java.io.File.

  2. Vos classes sont dans un répertoire ou dans un fichier JAR, il contient des informations sur le fichier source (pas un fichier de code source, mais juste un fichier source). Autant que j'ai essayé, cela fonctionne presque à 100% sauf la classe JVM (ces classes sont livrées avec la JVM).

  3. Votre programme doit avoir l'autorisation d'accéder à ProtectionDomain de ces classes. Si votre programme est chargé localement, il ne devrait y avoir aucun problème.

J'ai testé le programme uniquement pour mon utilisation régulière, il peut donc toujours avoir des problèmes.

J'espère que ça aide.

NawaMan
la source
1
cela semble être un truc très intéressant! je vais essayer de l'utiliser. Si j'ai trouvé utile pour moi, puis-je utiliser votre code dans un projet open source?
1
Je me prépare aussi à l'open-source. Alors allez-y: D
NawaMan
@NawaMan: L'avez-vous enfin ouvert? Si oui: où trouver la dernière version? Merci!
Joanis
1

Voici une autre option, légère modification d'une autre réponse ci-dessus / ci-dessous:

Reflections reflections = new Reflections("com.example.project.package", 
    new SubTypesScanner(false));
Set<Class<? extends Object>> allClasses = 
    reflections.getSubTypesOf(Object.class);
Alex Rashkov
la source
0

À l'époque où les applets étaient monnaie courante, on pouvait avoir une URL sur le chemin de classe. Lorsque le chargeur de classe a besoin d'une classe, il recherche tous les emplacements sur le chemin de classe, y compris les ressources http. Parce que vous pouvez avoir des choses comme des URL et des répertoires sur le chemin de classe, il n'y a pas de moyen facile d'obtenir une liste définitive des classes.

Cependant, vous pouvez vous en approcher assez. Certaines des bibliothèques Spring le font maintenant. Vous pouvez obtenir tous les fichiers JAR sur le chemin de classe et les ouvrir comme des fichiers. Vous pouvez ensuite prendre cette liste de fichiers, et créer une structure de données contenant vos classes.

Brianegge
la source
0

utiliser la dépendance maven:

groupId: net.sf.extcos
artifactId: extcos
version: 0.4b

puis utilisez ce code:

ComponentScanner scanner = new ComponentScanner();
        Set classes = scanner.getClasses(new ComponentQuery() {
            @Override
            protected void query() {
                select().from("com.leyton").returning(allExtending(DynamicForm.class));
            }
        });
Yalami
la source
-2

Brent - la raison pour laquelle l'association est à sens unique a à voir avec le fait que n'importe quelle classe sur n'importe quel composant de votre CLASSPATH peut se déclarer dans n'importe quel package (sauf pour java / javax). Ainsi, il n'y a tout simplement pas de mappage de TOUTES les classes dans un "package" donné parce que personne ne sait ni ne peut le savoir. Vous pouvez mettre à jour un fichier jar demain et supprimer ou ajouter des classes. C'est comme essayer d'obtenir une liste de toutes les personnes nommées John / Jon / Johan dans tous les pays du monde - aucun de nous n'est omniscient, donc aucun de nous n'aura jamais la bonne réponse.

Idarwin
la source
Belle réponse philosophique, mais comment fonctionne la numérisation de bean CDI alors? Ou comment Hibernate scanne-t-il @Entities?
Ondra Žižka