2009-11-27 5 views
11

Sagen wir, ich habe ein Java-Paket commands, das Klassen enthält, die alle von ICommand erben, kann ich alle diese Klassen irgendwie bekommen? Ich sperre für etwas zwischen den Zeilen:Hol dir alle Klassen aus einem Paket

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

Ist so etwas möglich?

+0

Bitte bearbeiten Sie dies, um lesbar zu sein. Was ist ein "Java-Paket"?Was ist ein "Befehlswunsch"? – bmargulies

+0

Das Thema ist perfekt: http://google.com/search?q=Alle+Klassen+aus+einem+Paket =) – BalusC

+0

@balusc, aber die/correct/-Antwort ist schwer. –

Antwort

14

Hier ist ein einfaches Beispiel unter der Annahme gefunden werden, dass Klassen nicht JAR-verpackt:

// 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, obwohl eine kleine Verbesserung: 'root.getFile()' sollte 'URLDecoder.decode (root.getFile()," UTF-8 ")' im Falle von Leerzeichen im 'classloader' Pfad sein. 'getResource()' konvertiert dies in '% 20' zusammen mit anderen Zeichen, die ich vermute. – Aquillo

1

Starten Sie mit öffentlichen Classloader.getResources (String-Name). Fragen Sie den Klassenlader nach einer Klasse, die jedem Namen in dem Paket entspricht, an dem Sie interessiert sind. Wiederholen Sie dies für alle relevanten Klassenlader.

1

Ja, aber es ist nicht die einfachste Sache zu tun. Es gibt viele Probleme damit. Nicht alle Klassen sind leicht zu finden. Einige Klassen könnten in einem: Glas, als Klassendatei über das Netzwerk usw.

Werfen Sie einen Blick at this thread.

Um sicherzustellen, dass sie der ICommand Typ dann müßten Sie Reflexion verwenden, um das zu überprüfen Klasse erben.

3

Hier ist eine Hilfsmethode, die Spring verwendet.

Details zum Muster können 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

Dies ist ein sehr nützliches Werkzeug, das wir brauchen würde, und JDK sollte etwas Unterstützung bieten.

Aber es ist wahrscheinlich besser während des Builds getan. Sie wissen, wo sich alle Ihre Klassendateien befinden, und Sie können sie statisch untersuchen und ein Diagramm erstellen. Zur Laufzeit können Sie dieses Diagramm abfragen, um alle Subtypen zu erhalten. Das erfordert mehr Arbeit, aber ich glaube, es gehört wirklich zum Build-Prozess.

+0

Etwas wie die JSR-199 API? Siehe http://stackoverflow.com/questions/1810614/getting-all-classes-from-a-package/1811120#1811120 –

6

Im Folgenden eine Implementierung der JSR-199 API dh Klassen von javax.tools.* mit:

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

Interessant, aber 'ToolProvider.getSystemJavaCompiler()' gibt 'null' sowohl in Eclipse als auch in CLI zurück? Wird etwas mehr benötigt? – BalusC

+1

Nach dem Graben benötigt dies anscheinend ein JDK anstelle von JRE (was ich jedoch in CLI habe, ich werde später nachlesen, warum das nicht funktioniert). Das würde bedeuten, dass Sie ein JDK in der endgültigen/prod-Umgebung installieren müssen, um es zum Laufen zu bringen. Das kann ein Showstopper sein. – BalusC

+2

Sie benötigen ein JDK, um ein Compiler-Objekt zu erhalten, das nicht 'null' ist (siehe http://bit.ly/89HtA0), damit diese API nicht wirklich auf den Desktop ausgerichtet ist. Es sollte jedoch auf der Serverseite in Ordnung sein (die meisten Anwendungsserver verwenden schließlich ein JDK, beispielsweise für die JSP-Kompilierung). Aber natürlich könnte es Ausnahmen geben. Eigentlich wollte ich nur zeigen, dass es Unterstützung im JDK gab, auch wenn dies kein perfekter Anwendungsfall für die Compiler-API ist (hier ist es eine schlechte Verwendung). Diese API kann mehr, viel mehr, zum Beispiel das Kompilieren von Java-Quellcode, der dynamisch im Speicher generiert wird (was schon interessanter ist). –

0

Johannes Link's ClasspathSuite Verwendung, konnte ich es, wie dies tun:

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

Die ClasspathClassesFinder für Klasse sieht Dateien und Gläser im Systemklassenpfad.

In Ihrem speziellen Fall könnten Sie acceptClass wie folgt ändern:

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

Eine Sache zu beachten: Seien Sie vorsichtig, was Sie in acceptClassName zurückkehren, wie das nächste, was ClasspathClassesFinder tut, ist die Klasse zu laden und rufen acceptClass . Wenn acceptClassName immer true zurückgibt, wird am Ende jede Klasse im Klassenpfad geladen, was zu einem OutOfMemoryError führen kann.

0

könnten Sie OpenPojo verwenden und dies tun:

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

Dann können Sie die Liste durchgehen und führen Sie alle Funktionen, die Sie sich wünschen.

Verwandte Themen