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.