Ist es möglich, alle Klassen oder Schnittstellen in einem bestimmten Paket zu finden? (Ein schneller Blick auf z.B. Package
scheint das nicht der Fall zu sein.)
Antworten
Zu viele Anzeigen?Ich mache es folgendermaßen. Ich scanne alle Unterordner (Unterpakete) und versuche nicht, anonyme Klassen zu laden:
/**
* Attempts to list all the classes in the specified package as determined
* by the context class loader, recursively, avoiding anonymous classes
*
* @param pckgname
* the package name to search
* @return a list of classes that exist within that package
* @throws ClassNotFoundException
* if something went wrong
*/
private static List<Class> getClassesForPackage(String pckgname) throws ClassNotFoundException {
// This will hold a list of directories matching the pckgname. There may be more than one if a package is split over multiple jars/paths
ArrayList<File> directories = new ArrayList<File>();
String packageToPath = pckgname.replace('.', '/');
try {
ClassLoader cld = Thread.currentThread().getContextClassLoader();
if (cld == null) {
throw new ClassNotFoundException("Can't get class loader.");
}
// Ask for all resources for the packageToPath
Enumeration<URL> resources = cld.getResources(packageToPath);
while (resources.hasMoreElements()) {
directories.add(new File(URLDecoder.decode(resources.nextElement().getPath(), "UTF-8")));
}
} catch (NullPointerException x) {
throw new ClassNotFoundException(pckgname + " does not appear to be a valid package (Null pointer exception)");
} catch (UnsupportedEncodingException encex) {
throw new ClassNotFoundException(pckgname + " does not appear to be a valid package (Unsupported encoding)");
} catch (IOException ioex) {
throw new ClassNotFoundException("IOException was thrown when trying to get all resources for " + pckgname);
}
ArrayList<Class> classes = new ArrayList<Class>();
// For every directoryFile identified capture all the .class files
while (!directories.isEmpty()){
File directoryFile = directories.remove(0);
if (directoryFile.exists()) {
// Get the list of the files contained in the package
File[] files = directoryFile.listFiles();
for (File file : files) {
// we are only interested in .class files
if ((file.getName().endsWith(".class")) && (!file.getName().contains("$"))) {
// removes the .class extension
int index = directoryFile.getPath().indexOf(packageToPath);
String packagePrefix = directoryFile.getPath().substring(index).replace('/', '.');;
try {
String className = packagePrefix + '.' + file.getName().substring(0, file.getName().length() - 6);
classes.add(Class.forName(className));
} catch (NoClassDefFoundError e)
{
// do nothing. this class hasn't been found by the loader, and we don't care.
}
} else if (file.isDirectory()){ // If we got to a subdirectory
directories.add(new File(file.getPath()));
}
}
} else {
throw new ClassNotFoundException(pckgname + " (" + directoryFile.getPath() + ") does not appear to be a valid package");
}
}
return classes;
}
Ich habe ein einfaches Github-Projekt zusammengestellt, das dieses Problem löst:
https://github.com/ddopson/java-class-enumerator
Es sollte für BEIDE dateibasierten Klassenpfade UND für JAR-Dateien funktionieren.
Wenn Sie nach dem Auschecken des Projekts "make" ausführen, wird dies ausgedruckt:
Cleaning...
rm -rf build/
Building...
javac -d build/classes src/pro/ddopson/ClassEnumerator.java src/test/ClassIShouldFindOne.java src/test/ClassIShouldFindTwo.java src/test/subpkg/ClassIShouldFindThree.java src/test/TestClassEnumeration.java
Making JAR Files...
jar cf build/ClassEnumerator_test.jar -C build/classes/ .
jar cf build/ClassEnumerator.jar -C build/classes/ pro
Running Filesystem Classpath Test...
java -classpath build/classes test.TestClassEnumeration
ClassDiscovery: Package: 'test' becomes Resource: 'file:/Users/Dopson/work/other/java-class-enumeration/build/classes/test'
ClassDiscovery: Reading Directory '/Users/Dopson/work/other/java-class-enumeration/build/classes/test'
ClassDiscovery: FileName 'ClassIShouldFindOne.class' => class 'test.ClassIShouldFindOne'
ClassDiscovery: FileName 'ClassIShouldFindTwo.class' => class 'test.ClassIShouldFindTwo'
ClassDiscovery: FileName 'subpkg' => class 'null'
ClassDiscovery: Reading Directory '/Users/Dopson/work/other/java-class-enumeration/build/classes/test/subpkg'
ClassDiscovery: FileName 'ClassIShouldFindThree.class' => class 'test.subpkg.ClassIShouldFindThree'
ClassDiscovery: FileName 'TestClassEnumeration.class' => class 'test.TestClassEnumeration'
Running JAR Classpath Test...
java -classpath build/ClassEnumerator_test.jar test.TestClassEnumeration
ClassDiscovery: Package: 'test' becomes Resource: 'jar:file:/Users/Dopson/work/other/java-class-enumeration/build/ClassEnumerator_test.jar!/test'
ClassDiscovery: Reading JAR file: '/Users/Dopson/work/other/java-class-enumeration/build/ClassEnumerator_test.jar'
ClassDiscovery: JarEntry 'META-INF/' => class 'null'
ClassDiscovery: JarEntry 'META-INF/MANIFEST.MF' => class 'null'
ClassDiscovery: JarEntry 'pro/' => class 'null'
ClassDiscovery: JarEntry 'pro/ddopson/' => class 'null'
ClassDiscovery: JarEntry 'pro/ddopson/ClassEnumerator.class' => class 'null'
ClassDiscovery: JarEntry 'test/' => class 'null'
ClassDiscovery: JarEntry 'test/ClassIShouldFindOne.class' => class 'test.ClassIShouldFindOne'
ClassDiscovery: JarEntry 'test/ClassIShouldFindTwo.class' => class 'test.ClassIShouldFindTwo'
ClassDiscovery: JarEntry 'test/subpkg/' => class 'null'
ClassDiscovery: JarEntry 'test/subpkg/ClassIShouldFindThree.class' => class 'test.subpkg.ClassIShouldFindThree'
ClassDiscovery: JarEntry 'test/TestClassEnumeration.class' => class 'test.TestClassEnumeration'
Tests Passed.
Siehe auch meine andere Antwort
Ja mit wenigen APIs können Sie, hier ist, wie ich es gerne tun, konfrontiert dieses Problem, das ich war mit Hibernate Kern & hatte, um Klassen zu finden, die wo mit einer bestimmten Annotation kommentiert.
Machen Sie daraus eine benutzerdefinierte Anmerkung, mit der Sie die Klassen markieren, die Sie aufnehmen möchten.
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface EntityToBeScanned {
}
Dann markieren Sie Ihre Klasse damit wie
@EntityToBeScanned
public MyClass{
}
Erstellen Sie diese Hilfsklasse, die folgende Methode hat
public class ClassScanner {
public static Set<Class<?>> allFoundClassesAnnotatedWithEntityToBeScanned(){
Reflections reflections = new Reflections(".*");
Set<Class<?>> annotated = reflections.getTypesAnnotatedWith(EntityToBeScanned.class);
return annotated;
}
}
Rufen Sie die allFoundClassesAnnotatedWithEntityToBeScanned() Methode zum Erhalten einer Satz der gefundenen Klassen.
Sie benötigen die folgenden Bibliotheken
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>21.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.javassist/javassist -->
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.22.0-CR1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.reflections/reflections -->
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.10</version>
</dependency>
Wenn Sie im Frühlingsland sind, können Sie PathMatchingResourcePatternResolver
;
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources("classpath*:some/package/name/*.class");
Arrays.asList(resources).forEach(r->{
...
});
Aleksander Blomskølds Lösung funktionierte bei mir nicht für parametrisierte Tests @RunWith(Parameterized.class)
wenn Sie Maven verwenden. Die Tests waren korrekt benannt und wurden auch gefunden, aber nicht ausgeführt:
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running some.properly.named.test.run.with.maven.SomeTest
Tests run: 0, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.123 sec
Über ein ähnliches Problem wurde berichtet aquí .
In meinem Fall @Parameters
ist die Erstellung von Instanzen für jede Klasse in einem Paket. Die Tests funktionierten gut, wenn sie lokal in der IDE ausgeführt wurden. Beim Ausführen von Maven wurden jedoch keine Klassen mit der Lösung von Aleksander Blomskøld gefunden.
Ich habe es mit dem folgenden Ausschnitt geschafft, der von David Pärssons Kommentar zu Aleksander Blomskølds Antwort inspiriert wurde:
Reflections reflections = new Reflections(new ConfigurationBuilder()
.setScanners(new SubTypesScanner(false /* don't exclude Object.class */), new ResourcesScanner())
.addUrls(ClasspathHelper.forJavaClassPath())
.filterInputsBy(new FilterBuilder()
.include(FilterBuilder.prefix(basePackage))));
Set<Class<?>> subTypesOf = reflections.getSubTypesOf(Object.class);
2 Stimmen
FYI die Lösung Amit Links zu funktioniert, obwohl es einen Fehler hat, wenn der Klassenpfad ein Leerzeichen in es hat (und wahrscheinlich für andere nicht-alphanumerische Zeichen zu). wenn Sie es in jeder Art von Produktionscode verwenden, siehe mein Kommentar zu seiner Antwort für eine Problemumgehung.
2 Stimmen
Beachten Sie auch diese Stelle .
1 Stimmen
Siehe dazugehörige Antwort: stackoverflow.com/a/30149061/4102160
1 Stimmen
Beachten Sie auch diese Stelle .
2 Stimmen
Siehe meine Antwort unten über ClassGraph, es ist derzeit die robusteste Methode zum Scannen der Klassenpfad und Modulpfad.
0 Stimmen
Dies ist ein sehr schwieriges Problem, vor allem wenn man bedenkt, dass nicht alle Classloader müssen dateibasiert sein (ich habe gesehen, wie sie Klassen aus der Datenbank laden) Ihre beste Wette ist wahrscheinlich die Kompilierzeit Annotation Verarbeitung verwenden, um eine Datei von Klassen zur Laufzeit zu verarbeiten. Die meisten Leute, die denken, dass es einfach ist, iterieren über Klassendateien auf Ihrem Klassenpfad - das ist nicht ein großer Ansatz im Allgemeinen, da, wie ich sagte, nicht alle Classloader auf den Klassenpfad beschränkt sind.