Um flexibel und dynamisch zur Laufzeit Klassen mit bestimmten Eigenschaften (z.B. bestimmter Package-Pfad oder bestimmtes implementiertes Interface)
verwenden zu können, muss der Classpath und/oder müssen .jar-Libs durchsucht werden.
Das vorgestellte Beispiel kann entweder eine Liste der gefundenen Klassen oder wahlweise eine Liste instanziierter Objekte returnieren.
Erzeugen Sie folgende Projektverzeichnisstruktur:
[MeinWorkspace] `- [ClassFinder] |- [bin] |- [lib] `- [src] `- [classfinder]
Speichern Sie im Verzeichnis <Projektverzeichnis>\src\classfinder die folgende Klasse ClassFinder.java:
package classfinder; import java.io.File; import java.lang.reflect.Modifier; import java.util.*; import java.util.zip.*; /** * Test-Aufruf-Beispiele (der letzte Aufruf mit junit-...jar im Classpath): * java classfinder.ClassFinder * java classfinder.ClassFinder classfinder * java classfinder.ClassFinder "" classfinder.MeinInterface * java classfinder.ClassFinder "" classfinder.MeineAbstrakteKlasse * java classfinder.ClassFinder "" org.junit.runner.Result */ public class ClassFinder { // Die main()-Methode ist hauptsächlich für Tests: public static void main( String[] args ) throws Exception { String packageName = ( args.length > 0 ) ? args[0] : null; String classNameSearched = ( args.length > 1 ) ? args[1] : null; System.out.println( "\n---- Gefundene Klassen:" ); List<Class<?>> classes = getClasses( packageName, classNameSearched ); for( Class<?> clazz : classes ) System.out.println( clazz ); System.out.println( "\n---- Instanziierte Objekte:" ); List<Object> objects = getInstances( packageName, classNameSearched ); for( Object obj : objects ) System.out.println( obj.getClass() ); } // Finde Klassen und instanziiere sie: public static List<Object> getInstances( String packageName, String classNameSearched ) throws ClassNotFoundException { List<Class<?>> classes = ClassFinder.getClasses( packageName, classNameSearched ); List<Object> objects = new ArrayList<Object>(); for( Class<?> clazz : classes ) { if( !clazz.isInterface() && (clazz.getModifiers() & Modifier.ABSTRACT) == 0 ) { try { objects.add( clazz.newInstance() ); } catch( Exception ex ) { // nur instanziierbare Klassen sind interessant } } } return objects; } // Finde Klassen (über Interface- oder Klassennamen bzw. Package-Namen): public static List<Class<?>> getClasses( String packageName, String classNameSearched ) throws ClassNotFoundException { Class<?> classSearched = ( classNameSearched != null ) ? Class.forName( classNameSearched ) : null; return getClasses( packageName, classSearched ); } // Finde Klassen (über Interface oder Klasse bzw. Package-Namen): public static List<Class<?>> getClasses( String packageName, Class<?> classSearched ) { List<Class<?>> classes = new ArrayList<Class<?>>(); for( String path : getPathesFromClasspath() ) { File fileOrDir = new File( path ); if( fileOrDir.isDirectory() ) classes.addAll( getClassesFromDir( fileOrDir, packageName, classSearched ) ); if( fileOrDir.isFile() && (fileOrDir.getName().toLowerCase().endsWith( ".jar" ) || fileOrDir.getName().toLowerCase().endsWith( ".zip" )) ) classes.addAll( getClassesFromJar( fileOrDir, packageName, classSearched ) ); } return Collections.unmodifiableList( classes ); } public static List<String> getPathesFromClasspath() { String classpath = System.getProperty( "java.class.path" ); String pathseparator = System.getProperty( "path.separator" ); StringTokenizer tokenizer = new StringTokenizer( classpath, pathseparator ); List<String> pathes = new ArrayList<String>(); while( tokenizer.hasMoreElements() ) pathes.add(tokenizer.nextToken()); return Collections.unmodifiableList( pathes ); } public static List<Class<?>> getClassesFromJar( File file, String packageName, Class<?> classSearched ) { if( packageName == null ) packageName = ""; List<Class<?>> classes = new ArrayList<Class<?>>(); String dirSearched = packageName.replace( ".", "/" ); ZipFile zipFile = null; try { zipFile = new ZipFile( file ); } catch( Exception ex ) { // nur Dateien, die gezippt sind und geöffnet werden können, sind interessant return classes; } for( Enumeration<? extends ZipEntry> zipEntries = zipFile.entries(); zipEntries.hasMoreElements(); ) { String entryName = zipEntries.nextElement().getName(); if( !entryName.startsWith( dirSearched ) || !entryName.toLowerCase().endsWith( ".class" ) ) continue; entryName = entryName.substring( 0, entryName.length() - ".class".length() ); entryName = entryName.replace( "/","." ) ; try { Class<?> clazz = Class.forName( entryName ); if( classSearched == null || classSearched.isAssignableFrom( clazz ) ) classes.add( clazz ); } catch( Throwable ex ) { // nur 'verwendbare' Klassen sind interessant } } try { zipFile.close(); } catch( Exception ex ) { /* wird ignoriert */ } return Collections.unmodifiableList( classes ); } public static List<Class<?>> getClassesFromDir( File dir, String packageName, Class<?> classSearched ) { if( packageName == null ) packageName = ""; List<Class<?>> classes = new ArrayList<Class<?>>(); File dirSearched = new File( dir.getPath() + File.separator + packageName.replace( ".", "/" ) ); if( dirSearched.isDirectory() ) getClassesFromFileOrDirIntern( true, dirSearched, packageName, classSearched, classes ); return Collections.unmodifiableList( classes ); } private static void getClassesFromFileOrDirIntern( boolean first, File fileOrDir, String packageName, Class<?> classSearched, List<Class<?>> classes ) { if( fileOrDir.isDirectory() ) { if( !first ) packageName = (packageName + "." + fileOrDir.getName()).replaceAll( "^\\.", "" ); for( String subFileOrDir : fileOrDir.list() ) getClassesFromFileOrDirIntern( false, new File( fileOrDir, subFileOrDir ), packageName, classSearched, classes ); } else { if( fileOrDir.getName().toLowerCase().endsWith( ".class" ) ) { String classFile = fileOrDir.getName(); classFile = packageName + "." + classFile.substring( 0, classFile.length() - ".class".length() ); try { Class<?> clazz = Class.forName( classFile ); if( classSearched == null || classSearched.isAssignableFrom( clazz ) ) classes.add( clazz ); } catch( Throwable ex ) { // nur 'verwendbare' Klassen sind interessant } } } } }
Speichern Sie für Tests im Verzeichnis <Projektverzeichnis>\src\classfinder die folgende Klasse TestKlassen.java (fügen sie darin oder zusätzlich weitere Klassen hinzu):
package classfinder; public class TestKlassen { } interface MeinInterface { String execute(); } class MeineKlasse1 implements MeinInterface { public String execute() { return this.getClass().getName(); } } abstract class MeineAbstrakteKlasse implements MeinInterface { public String execute() { return this.getClass().getName(); } } class MeineKlasse2 extends MeineAbstrakteKlasse {}
Speichern Sie für Tests im lib-Verzeichnis eine beliebige .jar-Lib, zum Beispiel junit-...jar.
Ihr Projektverzeichnis sieht jetzt so aus (überprüfen Sie es mit "tree /F"):
[MeinWorkspace] `- [ClassFinder] |- [bin] |- [lib] | `- ... [z.B. junit-...jar] `- [src] `- [classfinder] |- ClassFinder.java `- TestKlassen.java
Öffnen Sie ein Kommandozeilenfenster ('Windows-Taste' + 'R', 'cmd') und führen Sie folgende Kommandos aus:
cd \MeinWorkspace\ClassFinder
tree /F
javac -d bin -cp bin src\classfinder\*.java
java -cp bin;lib/* classfinder.ClassFinder classfinder
java -cp bin;lib/* classfinder.ClassFinder "" classfinder.MeinInterface
java -cp bin;lib/* classfinder.ClassFinder "" classfinder.MeineAbstrakteKlasse
java -cp bin;lib/* classfinder.ClassFinder "" classfinder.MeineKlasse2
java -cp bin;lib/* classfinder.ClassFinder "" org.junit.runner.Result
Falls Sie eine andere als eine junit-...jar-Lib in das lib-Verzeichnis kopiert haben, müssen Sie die Klasse im letzten Kommando entsprechend anpassen.