Pregunta

Digamos que tengo un paquete java comandos que contiene clases que todos heredan de ICommand ¿puedo obtener todas esas clases de alguna manera? Estoy bloqueando algo entre las líneas de:

Package p = Package.getPackage("commands");
Class<ICommand>[] c = p.getAllPackagedClasses(); //not real 

¿Es algo así posible?

¿Fue útil?

Solución

Este es un ejemplo básico, asumiendo que las clases no están empaquetadas con JAR:

// Prepare.
String packageName = "com.example.commands";
List<Class<ICommand>> commands = new ArrayList<Class<ICommand>>();
URL root = Thread.currentThread().getContextClassLoader().getResource(packageName.replace(".", "/"));

// Filter .class files.
File[] files = new File(root.getFile()).listFiles(new FilenameFilter() {
    public boolean accept(File dir, String name) {
        return name.endsWith(".class");
    }
});

// Find classes implementing ICommand.
for (File file : files) {
    String className = file.getName().replaceAll(".class<*>quot;, "");
    Class<?> cls = Class.forName(packageName + "." + className);
    if (ICommand.class.isAssignableFrom(cls)) {
        commands.add((Class<ICommand>) cls);
    }
}

Otros consejos

A continuación, una implementación que utiliza la API JSR-199, es decir, las clases de javax.tools. * :

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

JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(
        null, null, null);

Location location = StandardLocation.CLASS_PATH;
String packageName = "commands";
Set<JavaFileObject.Kind> kinds = new HashSet<JavaFileObject.Kind>();
kinds.add(JavaFileObject.Kind.CLASS);
boolean recurse = false;

Iterable<JavaFileObject> list = fileManager.list(location, packageName,
        kinds, recurse);

for (JavaFileObject javaFileObject : list) {
    commands.add(javaFileObject.getClass());
}

Aquí hay un método de utilidad, usando Spring.

Se pueden encontrar detalles sobre el patrón here

    public static List<Class> listMatchingClasses(String matchPattern) throws IOException {
    List<Class> classes = new LinkedList<Class>();
    PathMatchingResourcePatternResolver scanner = new PathMatchingResourcePatternResolver();
    Resource[] resources = scanner.getResources(matchPattern);

    for (Resource resource : resources) {
        Class<?> clazz = getClassFromResource(resource);
        classes.add(clazz);
    }

    return classes;
}



public static Class getClassFromResource(Resource resource) {
    try {
        String resourceUri = resource.getURI().toString();
        resourceUri = resourceUri.replace(esourceUri.indexOf(".class"), "").replace("/", ".");
        // try printing the resourceUri before calling forName, to see if it is OK.
        return Class.forName(resourceUri);
    } catch (Exception ex) {
        ex.printStackTrace();
    }
    return null;
}

Comience con el público Classloader.getResources (nombre de cadena). Solicite al cargador de clases una clase correspondiente a cada nombre en el paquete que le interesa. Repita para todos los cargadores de clases de relevancia.

Sí, pero no es lo más fácil de hacer. Hay muchos problemas con esto. No todas las clases son fáciles de encontrar. Algunas clases pueden estar en: Jar, como archivo de clase, a través de la red, etc.

Echa un vistazo a en este hilo.

Para asegurarte de que fueran del tipo ICommand, tendrías que usar la reflexión para verificar la clase hereditaria.

Esta sería una herramienta muy útil que necesitamos, y JDK debería proporcionar algo de soporte.

Pero probablemente sea mejor hacerlo durante la compilación. Usted sabe dónde están todos los archivos de su clase y puede inspeccionarlos de forma estática y construir un gráfico. En el tiempo de ejecución, puede consultar este gráfico para obtener todos los subtipos. Esto requiere más trabajo, pero creo que realmente pertenece al proceso de compilación.

Usando ClasspathSuite de Johannes Link , pude hacerlo de esta manera:

import org.junit.extensions.cpsuite.ClassTester;
import org.junit.extensions.cpsuite.ClasspathClassesFinder;

public static List<Class<?>> getClasses(final Package pkg, final boolean includeChildPackages) {
    return new ClasspathClassesFinder(new ClassTester() {
        @Override public boolean searchInJars() { return true; }
        @Override public boolean acceptInnerClass() { return false; }
        @Override public boolean acceptClassName(String name) {
            return name.startsWith(pkg.getName()) && (includeChildPackages || name.indexOf(".", pkg.getName().length()) != -1);
        }
        @Override public boolean acceptClass(Class<?> c) { return true; }
    }, System.getProperty("java.class.path")).find();
}

El ClasspathClassesFinder busca archivos y archivos de clase en el classpath del sistema.

En su caso específico, podría modificar acceptClass de esta forma:

@Override public boolean acceptClass(Class<?> c) {
    return ICommand.class.isAssignableFrom(c);
}

Una cosa a tener en cuenta: tenga cuidado con lo que devuelve en acceptClassName, ya que lo siguiente que hace ClasspathClassesFinder es cargar la clase y llamar a acceptClass. Si acceptClassName siempre devuelve verdadero, terminará cargando cada clase en la ruta de clase y eso puede causar un OutOfMemoryError.

Puede usar OpenPojo y hacer esto:

final List<PojoClass> pojoClasses = PojoClassFactory.getPojoClassesRecursively("my.package.path", null);

Luego puedes revisar la lista y realizar cualquier funcionalidad que desees.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top