2009-11-27 20 views
11

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

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

¿Es posible algo así?

+0

por favor edítalo para que sea legible. ¿Qué es un 'javapackage'?¿Qué es un "deseo de comando"? – bmargulies

+0

El tema del tema es perfecto: http://google.com/search?q=Getting+all+Classes+from+a+Package =) – BalusC

+0

@balusc, pero la/correcta/respuesta es difícil. –

Respuesta

14

Aquí hay un ejemplo básico, en el supuesto de que las clases no son JAR-envasados:

// 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$", ""); 
    Class<?> cls = Class.forName(packageName + "." + className); 
    if (ICommand.class.isAssignableFrom(cls)) { 
     commands.add((Class<ICommand>) cls); 
    } 
} 
+3

+1, aunque una pequeña mejora: 'root.getFile()' debe ser 'URLDecoder.decode (root.getFile()," UTF-8 ")' en el caso de cualquier espacio en la ruta 'classloader'. 'getResource()' convertirá esto en '% 20', junto con otros caracteres, supongo. – Aquillo

1

Comience con public Classloader.getResources (String name). Pídale al cargador de clases una clase correspondiente a cada nombre en el paquete que le interese. Repita para todos los clasificadores de relevancia.

1

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 podrían estar en una: Tarro, como un archivo de clase, etc. través de la red

Tome una mirada at this thread.

Para asegurarse de que eran del tipo ICommand entonces usted tendría que utilizar la reflexión para comprobar la clase heredera

3

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

Los detalles sobre el patrón se pueden encontrar 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; 
} 
1

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

Pero probablemente sea mejor hacerlo durante la compilación. Usted sabe dónde están todos sus archivos de clase y puede inspeccionarlos estáticamente y crear un gráfico. En 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.

+0

Algo así como la API JSR-199? Ver http://stackoverflow.com/questions/1810614/getting-all-classes-from-a-package/1811120#1811120 –

6

A continuación, una implementación usando los JSR-199 clases API es decir, desde 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()); 
} 
+0

Interesante, pero 'ToolProvider.getSystemJavaCompiler()' devuelve 'null' tanto en Eclipse como en CLI? ¿Se necesita algo más? – BalusC

+1

Después de excavar, esto aparentemente requiere un JDK en lugar de JRE (que, sin embargo, tengo en CLI, voy a investigar más tarde por qué esto no funciona). Eso significa que debe instalar un JDK en el entorno final/prod para que funcione. Eso puede ser un disparate. – BalusC

+2

De hecho, necesitas un JDK para obtener un objeto compilador que no sea 'null' (mira http://bit.ly/89HtA0) por lo que esta API no se dirige realmente al escritorio. Sin embargo, debería estar bien en el lado del servidor (la mayoría de los servidores de aplicaciones usan un JDK después de todo, por ejemplo, para la compilación de JSP). Pero, por supuesto, puede haber excepciones. En realidad, solo quería mostrar que había algo de soporte en el JDK, incluso si este no es un caso de uso perfecto para la API del compilador (es un uso pobre aquí). Esta API puede hacer más, mucho más, por ejemplo compilando código fuente Java generado dinámicamente en la memoria (que ya es más interesante). –

0

Usando Johannes Link's ClasspathSuite, yo era capaz de 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 de clases archivos y jar en classpath del sistema.

En su caso específico, usted podría modificar acceptClass así:

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

Una cosa a tener en cuenta: cuidado con lo que regresa en acceptClassName, como el siguiente que ClasspathClassesFinder hace 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.

0

Usted podría utilizar OpenPojo y hacer esto:

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

entonces usted puede ir sobre la lista y realizar cualquier funcionalidad que usted desea.

Cuestiones relacionadas