¿Cómo leo todas las clases de un paquete de Java en la ruta de clases?

94

Necesito leer las clases contenidas en un paquete de Java. Esas clases están en classpath. Necesito hacer esta tarea directamente desde un programa Java. ¿Conoces una forma sencilla de hacerlo?

List<Class> classes = readClassesFrom("my.package")
Jørgen R
fuente
7
En pocas palabras, no, no se puede hacer tan fácilmente. Hay algunos trucos extremadamente largos que funcionan en algunas situaciones, pero sugiero encarecidamente un diseño diferente.
skaffman
La solución se puede encontrar en el proyecto Weld .
Ondra Žižka
Consulte este enlace para obtener la respuesta: stackoverflow.com/questions/176527/…
Karthik E

Respuestas:

48

Si tiene Spring en su classpath, lo siguiente lo hará.

Busque todas las clases en un paquete que están anotadas con 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
fuente
Gracias por el fragmento de código. :) Si desea evitar la duplicación de clases en una lista de candidatos, cambie el tipo de colección de Lista a Conjunto. Y use hasEnclosingClass () y getEnclosingClassName () de classMetaData. Use el método getEnclosingClass sobre una clase subyacente
traeper
29

Puede utilizar el Proyecto Reflections descrito aquí

Es bastante completo y fácil de usar.

Breve descripción del sitio web anterior:

Reflections escanea su classpath, indexa los metadatos, le permite consultarlos en tiempo de ejecución y puede guardar y recopilar esa información para muchos módulos dentro de su proyecto.

Ejemplo:

Reflections reflections = new Reflections(
    new ConfigurationBuilder()
        .setUrls(ClasspathHelper.forJavaClassPath())
);
Set<Class<?>> types = reflections.getTypesAnnotatedWith(Scannable.class);
Renato
fuente
¿Funciona en Equinoccio? Y si es así, ¿puede hacerlo sin activar complementos?
Etiqueta
funciona para el equinoccio. en versiones anteriores de Reflections, agregue bundle jar vfsType. ver aquí
zapp
28

Yo uso este, funciona con archivos o archivos 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
fuente
1
esto funciona si elimina la referencia a File.Separator y solo usa '/'
Will Glass
1
Se requiere un cambio más para que esto funcione con archivos jar cuando la ruta contiene espacios. Debe decodificar la ruta del archivo jar. Cambiar: jarFileName = packageURL.getFile (); to: jarFileName = URLDecoder.decode (packageURL.getFile ());
Will Glass
obtengo solo el nombre del paquete en jar
obtengo el
new File (uri) solucionará su problema con los espacios.
Trejkaz
entryName.lastIndexOf ('.') sería -1
marstone
11

Spring ha implementado una excelente función de búsqueda de rutas de clases en PathMatchingResourcePatternResolver. Si usa el classpath*prefijo:, puede encontrar todos los recursos, incluidas las clases en una jerarquía determinada, e incluso filtrarlos si lo desea. Entonces puedes usar los hijos de AbstractTypeHierarchyTraversingFilter,AnnotationTypeFilter y AssignableTypeFilterpara filtrar esos recursos, ya sea en el nivel de anotaciones de clase o en las interfaces que implementan.

John Ellinwood
fuente
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[]{});
}

Esta solución se probó en el entorno EJB .

Paul Kuit
fuente
6

Scannotation y Reflections utilizan un enfoque de escaneo de rutas de clases:

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

Otro enfoque es utilizar la API de procesamiento de anotaciones Java Pluggable para escribir un procesador de anotaciones que recopilará todas las clases anotadas en tiempo de compilación y creará el archivo de índice para su uso en tiempo de ejecución. Este mecanismo se implementa en la biblioteca ClassIndex :

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

Hasta donde yo sé, esa funcionalidad todavía falta sospechosamente en la API de reflexión de Java. Puede obtener un objeto de paquete simplemente haciendo esto:

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

Pero como probablemente haya notado, eso no le permitirá enumerar las clases en ese paquete. A partir de ahora, debe adoptar un enfoque más orientado al sistema de archivos.

Encontré algunas implementaciones de muestra en esta publicación.

No estoy 100% seguro de que estos métodos funcionen cuando tus clases estén enterradas en archivos JAR, pero espero que uno de ellos lo haga por ti.

Estoy de acuerdo con @skaffman ... si tienes otra forma de hacer esto, te recomendaría hacerlo.

Brent escribe código
fuente
4
No es sospechoso, simplemente no funciona de esa manera. Las clases no "pertenecen" a los paquetes, tienen referencias a ellos. La asociación no apunta en la otra dirección.
skaffman
1
@skaffman Punto muy interesante. Nunca lo pensé de esa manera. Entonces, mientras deambulemos por ese camino de pensamiento, ¿por qué la asociación no es bidireccional (esto es más por mi propia curiosidad ahora)?
Brent escribe código
3

El mecanismo más robusto para listar todas las clases en un paquete dado es actualmente ClassGraph , porque maneja la más amplia gama posible de mecanismos de especificación de rutas de clases , incluido el nuevo sistema de módulos JPMS. (Yo soy el autor)

List<String> classNames;
try (ScanResult scanResult = new ClassGraph().whitelistPackages("my.package")
        .enableClassInfo().scan()) {
    classNames = scanResult.getAllClasses().getNames();
}
Luke Hutchison
fuente
Me encontré con esto un par de años después. ¡Esta es una gran herramienta! Funciona mucho más rápido que la reflexión y me gusta que no invoca inicializadores estáticos. Justo lo que necesitaba para resolver un problema que teníamos.
Akagixxer
2

eXtcos parece prometedor. Imagina que quieres encontrar todas las clases que:

  1. Extienda de la clase "Componente" y almacénelos
  2. Están anotados con "MyComponent" y
  3. Están en el paquete "común".

Con eXtcos esto es tan simple como

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
fuente
2
  1. Bill Burke ha escrito un (buen artículo sobre el escaneo de clases) y luego escribió Scannotation .

  2. Hibernate ya tiene esto escrito:

    • org.hibernate.ejb.packaging.Scanner
    • org.hibernate.ejb.packaging.NativeScanner
  3. CDI podría resolver esto, pero no lo sé, aún no lo he investigado completamente

.

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

También para anotaciones:

abstract class MyAnnotationQualifier
extends AnnotationLiteral<Entity> implements Entity {}
Ondra Žižka
fuente
El enlace al artículo está muerto.
user2418306
1

Lo he implementado y funciona en la mayoría de los casos. Como es largo, lo puse en un archivo aquí. .

La idea es encontrar la ubicación del archivo fuente de la clase que está disponible en la mayoría de los casos (una excepción conocida son los archivos de clase JVM, hasta donde he probado). Si el código está en un directorio, escanee todos los archivos y solo detecte los archivos de clase. Si el código está en un archivo JAR, escanee todas las entradas.

Este método solo se puede utilizar cuando:

  1. Tiene una clase que está en el mismo paquete que desea descubrir. Esta clase se llama SeedClass. Por ejemplo, si desea enumerar todas las clases en 'java.io', la clase semilla puede ser java.io.File.

  2. Sus clases están en un directorio o en un archivo JAR que tiene información de archivo fuente (no archivo de código fuente, sino solo archivo fuente). Por lo que he intentado, funciona casi al 100% excepto la clase JVM (esas clases vienen con la JVM).

  3. Su programa debe tener permiso para acceder a ProtectionDomain de esas clases. Si su programa se carga localmente, no debería haber ningún problema.

He probado el programa solo para mi uso habitual, por lo que aún puede tener problemas.

Espero que esto ayude.

NawaMan
fuente
1
¡Parece ser algo muy interesante! Intentaré usarlo. Si me resultó útil, ¿puedo usar su código en un proyecto de código abierto?
1
Me estoy preparando para abrirlo también. Así que adelante: D
NawaMan
@NawaMan: ¿Lo abriste, finalmente? Si es así, ¿dónde podemos encontrar la última versión? ¡Gracias!
Joanis
1

Aquí hay otra opción, una ligera modificación a otra respuesta arriba / abajo:

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

Cuando los applets eran un lugar común, se podía tener una URL en la ruta de clases. Cuando el cargador de clases requería una clase, buscaba todas las ubicaciones en la ruta de clases, incluidos los recursos http. Debido a que puede tener cosas como URL y directorios en la ruta de clases, no hay una manera fácil de obtener una lista definitiva de las clases.

Sin embargo, puedes acercarte bastante. Algunas de las bibliotecas de Spring están haciendo esto ahora. Puede obtener todos los archivos jar en la ruta de clases y abrirlos como archivos. Luego puede tomar esta lista de archivos y crear una estructura de datos que contenga sus clases.

Brianegge
fuente
0

utilizar maven de dependencia:

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

luego usa este código:

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

Brent: la razón por la que la asociación es unidireccional tiene que ver con el hecho de que cualquier clase en cualquier componente de su CLASSPATH puede declararse en cualquier paquete (excepto java / javax). Por lo tanto, simplemente no hay mapeo de TODAS las clases en un "paquete" dado porque nadie sabe ni puede saber. Puede actualizar un archivo jar mañana y eliminar o agregar clases. Es como intentar obtener una lista de todas las personas llamadas John / Jon / Johan en todos los países del mundo: ninguno de nosotros es omnisciente, por lo tanto, ninguno de nosotros tendrá la respuesta correcta.

idarwin
fuente
Buena respuesta filosófica, pero ¿cómo funciona entonces el escaneo de frijoles CDI? ¿O cómo Hibernate escanea @Entities?
Ondra Žižka