Is it possible to find all classes or interfaces in a given package? (Quickly looking at e.g. Package
, it would seem like no.)
Due to the dynamic nature of class loaders, this is not possible. Class loaders are not required to tell the VM which classes it can provide, instead they are just handed requests for classes, and have to return a class or throw an exception.
However, if you write your own class loaders, or examine the classpaths and it's jars, it's possible to find this information. This will be via filesystem operations though, and not reflection. There might even be libraries that can help you do this.
If there are classes that get generated, or delivered remotely, you will not be able to discover those classes.
The normal method is instead to somewhere register the classes you need access to in a file, or reference them in a different class. Or just use convention when it comes to naming.
Addendum: The Reflections Library will allow you to look up classes in the current classpath. It can be used to get all classes in a package:
Reflections reflections = new Reflections("my.project.prefix");
Set<Class<? extends Object>> allClasses =
reflections.getSubTypesOf(Object.class);
You should probably take a look at the open source Reflections library. With it you can easily achieve what you want.
First, setup the reflections index (it's a bit messy since searching for all classes is disabled by default):
List<ClassLoader> classLoadersList = new LinkedList<ClassLoader>();
classLoadersList.add(ClasspathHelper.contextClassLoader());
classLoadersList.add(ClasspathHelper.staticClassLoader());
Reflections reflections = new Reflections(new ConfigurationBuilder()
.setScanners(new SubTypesScanner(false /* don't exclude Object.class */), new ResourcesScanner())
.setUrls(ClasspathHelper.forClassLoader(classLoadersList.toArray(new ClassLoader[0])))
.filterInputsBy(new FilterBuilder().include(FilterBuilder.prefix("org.your.package"))));
Then you can query for all objects in a given package:
Set<Class<?>> classes = reflections.getSubTypesOf(Object.class);
.addUrls(ClasspathHelper.forJavaClassPath())
instead of the above solved them for me. Less code as well!
Google Guava 14 includes a new class ClassPath
with three methods to scan for top level classes:
getTopLevelClasses()
getTopLevelClasses(String packageName)
getTopLevelClassesRecursive(String packageName)
See the ClassPath
javadocs for more info.
ClassPath
is tagged with @Beta
, so might not be a good idea for some...
You could use this method1 that uses the ClassLoader
.
/**
* Scans all classes accessible from the context class loader which belong to the given package and subpackages.
*
* @param packageName The base package
* @return The classes
* @throws ClassNotFoundException
* @throws IOException
*/
private static Class[] getClasses(String packageName)
throws ClassNotFoundException, IOException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
assert classLoader != null;
String path = packageName.replace('.', '/');
Enumeration<URL> resources = classLoader.getResources(path);
List<File> dirs = new ArrayList<File>();
while (resources.hasMoreElements()) {
URL resource = resources.nextElement();
dirs.add(new File(resource.getFile()));
}
ArrayList<Class> classes = new ArrayList<Class>();
for (File directory : dirs) {
classes.addAll(findClasses(directory, packageName));
}
return classes.toArray(new Class[classes.size()]);
}
/**
* Recursive method used to find all classes in a given directory and subdirs.
*
* @param directory The base directory
* @param packageName The package name for classes found inside the base directory
* @return The classes
* @throws ClassNotFoundException
*/
private static List<Class> findClasses(File directory, String packageName) throws ClassNotFoundException {
List<Class> classes = new ArrayList<Class>();
if (!directory.exists()) {
return classes;
}
File[] files = directory.listFiles();
for (File file : files) {
if (file.isDirectory()) {
assert !file.getName().contains(".");
classes.addAll(findClasses(file, packageName + "." + file.getName()));
} else if (file.getName().endsWith(".class")) {
classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
}
}
return classes;
}
__________
1 This method was taken originally from http://snippets.dzone.com/posts/show/4831, which was archived by the Internet Archive, as linked to now. The snippet is also available at https://dzone.com/articles/get-all-classes-within-package.
%20
, but the new File()
constructor treated that as a literal percent sign two zero. I fixed it by changing the dirs.add(...)
line to this: dirs.add(new File(resource.toURI()));
This also meant I had to add URISyntaxException
to the throws clause of getClasses
Spring
This example is for Spring 4, but you can find the classpath scanner in earlier versions as well.
// create scanner and disable default filters (that is the 'false' argument)
final ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
// add include filters which matches all the classes (or use your own)
provider.addIncludeFilter(new RegexPatternTypeFilter(Pattern.compile(".*")));
// get matching classes defined in the package
final Set<BeanDefinition> classes = provider.findCandidateComponents("my.package.name");
// this is how you can load the class type from BeanDefinition instance
for (BeanDefinition bean: classes) {
Class<?> clazz = Class.forName(bean.getBeanClassName());
// ... do your magic with the class ...
}
Google Guava
Note: In version 14, the API is still marked as @Beta, so beware in production code.
final ClassLoader loader = Thread.currentThread().getContextClassLoader();
for (final ClassPath.ClassInfo info : ClassPath.from(loader).getTopLevelClasses()) {
if (info.getName().startsWith("my.package.")) {
final Class<?> clazz = info.load();
// do something with your clazz
}
}
ClassPath
class in Guava is also marked with @Beta
: "APIs marked with the @Beta annotation at the class or method level are subject to change. They can be modified in any way, or even removed, in any major release. If your code is a library itself (i.e. it is used on the CLASSPATH of users outside your own control), you should not use beta APIs, unless you repackage them..." code.google.com/p/guava-libraries/#Important_Warnings
getAllClasses()
method can be used.
@RabbitListener(...) public @interface XxxListener
) because of the ClassPathScanningCandidateComponentProvider#isCandidateComponent
Hello. I always have had some issues with the solutions above (and on other sites). I, as a developer, am programming a addon for a API. The API prevents the use of any external libraries or 3rd party tools. The setup also consists of a mixture of code in jar or zip files and class files located directly in some directories. So my code had to be able to work arround every setup. After a lot of research I have come up with a method that will work in at least 95% of all possible setups.
The following code is basically the overkill method that will always work.
The code:
This code scans a given package for all classes that are included in it. It will only work for all classes in the current ClassLoader
.
/**
* Private helper method
*
* @param directory
* The directory to start with
* @param pckgname
* The package name to search for. Will be needed for getting the
* Class object.
* @param classes
* if a file isn't loaded but still is in the directory
* @throws ClassNotFoundException
*/
private static void checkDirectory(File directory, String pckgname,
ArrayList<Class<?>> classes) throws ClassNotFoundException {
File tmpDirectory;
if (directory.exists() && directory.isDirectory()) {
final String[] files = directory.list();
for (final String file : files) {
if (file.endsWith(".class")) {
try {
classes.add(Class.forName(pckgname + '.'
+ file.substring(0, file.length() - 6)));
} catch (final NoClassDefFoundError e) {
// do nothing. this class hasn't been found by the
// loader, and we don't care.
}
} else if ((tmpDirectory = new File(directory, file))
.isDirectory()) {
checkDirectory(tmpDirectory, pckgname + "." + file, classes);
}
}
}
}
/**
* Private helper method.
*
* @param connection
* the connection to the jar
* @param pckgname
* the package name to search for
* @param classes
* the current ArrayList of all classes. This method will simply
* add new classes.
* @throws ClassNotFoundException
* if a file isn't loaded but still is in the jar file
* @throws IOException
* if it can't correctly read from the jar file.
*/
private static void checkJarFile(JarURLConnection connection,
String pckgname, ArrayList<Class<?>> classes)
throws ClassNotFoundException, IOException {
final JarFile jarFile = connection.getJarFile();
final Enumeration<JarEntry> entries = jarFile.entries();
String name;
for (JarEntry jarEntry = null; entries.hasMoreElements()
&& ((jarEntry = entries.nextElement()) != null);) {
name = jarEntry.getName();
if (name.contains(".class")) {
name = name.substring(0, name.length() - 6).replace('/', '.');
if (name.contains(pckgname)) {
classes.add(Class.forName(name));
}
}
}
}
/**
* Attempts to list all the classes in the specified package as determined
* by the context class loader
*
* @param pckgname
* the package name to search
* @return a list of classes that exist within that package
* @throws ClassNotFoundException
* if something went wrong
*/
public static ArrayList<Class<?>> getClassesForPackage(String pckgname)
throws ClassNotFoundException {
final ArrayList<Class<?>> classes = new ArrayList<Class<?>>();
try {
final ClassLoader cld = Thread.currentThread()
.getContextClassLoader();
if (cld == null)
throw new ClassNotFoundException("Can't get class loader.");
final Enumeration<URL> resources = cld.getResources(pckgname
.replace('.', '/'));
URLConnection connection;
for (URL url = null; resources.hasMoreElements()
&& ((url = resources.nextElement()) != null);) {
try {
connection = url.openConnection();
if (connection instanceof JarURLConnection) {
checkJarFile((JarURLConnection) connection, pckgname,
classes);
} else if (connection instanceof FileURLConnection) {
try {
checkDirectory(
new File(URLDecoder.decode(url.getPath(),
"UTF-8")), pckgname, classes);
} catch (final UnsupportedEncodingException ex) {
throw new ClassNotFoundException(
pckgname
+ " does not appear to be a valid package (Unsupported encoding)",
ex);
}
} else
throw new ClassNotFoundException(pckgname + " ("
+ url.getPath()
+ ") does not appear to be a valid package");
} catch (final IOException ioex) {
throw new ClassNotFoundException(
"IOException was thrown when trying to get all resources for "
+ pckgname, ioex);
}
}
} catch (final NullPointerException ex) {
throw new ClassNotFoundException(
pckgname
+ " does not appear to be a valid package (Null pointer exception)",
ex);
} catch (final IOException ioex) {
throw new ClassNotFoundException(
"IOException was thrown when trying to get all resources for "
+ pckgname, ioex);
}
return classes;
}
These three methods provide you with the ability to find all classes in a given package. You use it like this:
getClassesForPackage("package.your.classes.are.in");
The explanation:
The method first gets the current ClassLoader
. It then fetches all resources that contain said package and iterates of these URL
s. It then creates a URLConnection
and determines what type of URl we have. It can either be a directory (FileURLConnection
) or a directory inside a jar or zip file (JarURLConnection
). Depending on what type of connection we have two different methods will be called.
First lets see what happens if it is a FileURLConnection
.
It first checks if the passed File exists and is a directory. If that's the case it checks if it is a class file. If so a Class
object will be created and put in the ArrayList
. If it is not a class file but is a directory, we simply iterate into it and do the same thing. All other cases/files will be ignored.
If the URLConnection
is a JarURLConnection
the other private helper method will be called. This method iterates over all Entries in the zip/jar archive. If one entry is a class file and is inside of the package a Class
object will be created and stored in the ArrayList
.
After all resources have been parsed it (the main method) returns the ArrayList
containig all classes in the given package, that the current ClassLoader
knows about.
If the process fails at any point a ClassNotFoundException
will be thrown containg detailed information about the exact cause.
sun.net.www.protocol.file.FileURLConnection
, which generates a warning at compile-time ("warning: sun.net.www.protocol.file.FileURLConnection is Sun proprietary API and may be removed in a future release"). Is there an alternative to using that class, or can the warning be suppressed using annotations?
if ( ... instanceof JarURLConnecton) { ... } else { // Asume that the Connection is valid and points to a File }
It's what I did on my code to search JPA annotated classes
The most robust mechanism for listing all classes in a given package is currently ClassGraph, because it handles the widest possible array of classpath specification mechanisms, including the new JPMS module system. (I am the author.)
List<String> classNames = new ArrayList<>();
try (ScanResult scanResult = new ClassGraph().acceptPackages("my.package")
.enableClassInfo().scan()) {
classNames.addAll(scanResult.getAllClasses().getNames());
}
Without using any extra libraries:
package test;
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
public class Test {
public static void main(String[] args) throws Exception{
List<Class> classes = getClasses(Test.class.getClassLoader(),"test");
for(Class c:classes){
System.out.println("Class: "+c);
}
}
public static List<Class> getClasses(ClassLoader cl,String pack) throws Exception{
String dottedPackage = pack.replaceAll("[/]", ".");
List<Class> classes = new ArrayList<Class>();
URL upackage = cl.getResource(pack);
DataInputStream dis = new DataInputStream((InputStream) upackage.getContent());
String line = null;
while ((line = dis.readLine()) != null) {
if(line.endsWith(".class")) {
classes.add(Class.forName(dottedPackage+"."+line.substring(0,line.lastIndexOf('.'))));
}
}
return classes;
}
}
upackage
is null
... :(
String pack = getPackage().getName();
, then you have to add pack = pack.replaceAll("[.]", "/");
In general class loaders do not allow for scanning through all the classes on the classpath. But usually the only used class loader is UrlClassLoader from which we can retrieve the list of directories and jar files (see getURLs) and open them one by one to list available classes. This approach, called class path scanning, is implemented in Scannotation and Reflections.
Reflections reflections = new Reflections("my.package");
Set<Class<? extends Object>> classes = reflections.getSubTypesOf(Object.class);
Another approach is to use Java Pluggable Annotation Processing API to write annotation processor which will collect all annotated classes at compile time and build the index file for runtime use. This mechanism is implemented in ClassIndex library:
// package-info.java
@IndexSubclasses
package my.package;
// your code
Iterable<Class> classes = ClassIndex.getPackageClasses("my.package");
Notice that no additional setup is needed as the scanning is fully automated thanks to Java compiler automatically discovering any processors found on the classpath.
What about this:
public static List<Class<?>> getClassesForPackage(final String pkgName) throws IOException, URISyntaxException {
final String pkgPath = pkgName.replace('.', '/');
final URI pkg = Objects.requireNonNull(ClassLoader.getSystemClassLoader().getResource(pkgPath)).toURI();
final ArrayList<Class<?>> allClasses = new ArrayList<Class<?>>();
Path root;
if (pkg.toString().startsWith("jar:")) {
try {
root = FileSystems.getFileSystem(pkg).getPath(pkgPath);
} catch (final FileSystemNotFoundException e) {
root = FileSystems.newFileSystem(pkg, Collections.emptyMap()).getPath(pkgPath);
}
} else {
root = Paths.get(pkg);
}
final String extension = ".class";
try (final Stream<Path> allPaths = Files.walk(root)) {
allPaths.filter(Files::isRegularFile).forEach(file -> {
try {
final String path = file.toString().replace('/', '.');
final String name = path.substring(path.indexOf(pkgName), path.length() - extension.length());
allClasses.add(Class.forName(name));
} catch (final ClassNotFoundException | StringIndexOutOfBoundsException ignored) {
}
});
}
return allClasses;
}
You can then overload the function:
public static List<Class<?>> getClassesForPackage(final Package pkg) throws IOException, URISyntaxException {
return getClassesForPackage(pkg.getName());
}
If you need to test it:
public static void main(final String[] argv) throws IOException, URISyntaxException {
for (final Class<?> cls : getClassesForPackage("my.package")) {
System.out.println(cls);
}
for (final Class<?> cls : getClassesForPackage(MyClass.class.getPackage())) {
System.out.println(cls);
}
}
If your IDE does not have import helper:
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.FileSystemNotFoundException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
It works:
from your IDE
for a JAR file
without external dependencies
I put together a simple github project that solves this problem:
https://github.com/ddopson/java-class-enumerator
It should work for BOTH file-based classpaths AND for jar files.
If you run 'make' after checking out the project it will print this out:
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.
See also my other answer
Here's how I do it. I scan all the subfolders (sub-packages) and I don't try to load anonymous classes:
/**
* 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;
}
Yeah using few API's you can, here is how I like doing it, faced this problem which I was using hibernate core & had to find classes which where annotated with a certain annotation.
Make these an custom annotation using which you will mark which classes you want to be picked up.
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 {
}
Then mark your class with it like
@EntityToBeScanned
public MyClass{
}
Make this utility class which has the following method
public class ClassScanner {
public static Set<Class<?>> allFoundClassesAnnotatedWithEntityToBeScanned(){
Reflections reflections = new Reflections(".*");
Set<Class<?>> annotated = reflections.getTypesAnnotatedWith(EntityToBeScanned.class);
return annotated;
}
}
Call the allFoundClassesAnnotatedWithEntityToBeScanned() method to get a Set of Classes found.
You will need libs given below
<!-- 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>
If you're in Spring-land you can use PathMatchingResourcePatternResolver
;
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources("classpath*:some/package/name/*.class");
Arrays.asList(resources).forEach(r->{
...
});
You need to look up every class loader entry in the class path:
String pkg = "org/apache/commons/lang";
ClassLoader cl = ClassLoader.getSystemClassLoader();
URL[] urls = ((URLClassLoader) cl).getURLs();
for (URL url : urls) {
System.out.println(url.getFile());
File jar = new File(url.getFile());
// ....
}
If entry is directory, just look up in the right subdirectory:
if (jar.isDirectory()) {
File subdir = new File(jar, pkg);
if (!subdir.exists())
continue;
File[] files = subdir.listFiles();
for (File file : files) {
if (!file.isFile())
continue;
if (file.getName().endsWith(".class"))
System.out.println("Found class: "
+ file.getName().substring(0,
file.getName().length() - 6));
}
}
If the entry is the file, and it's jar, inspect the ZIP entries of it:
else {
// try to open as ZIP
try {
ZipFile zip = new ZipFile(jar);
for (Enumeration<? extends ZipEntry> entries = zip
.entries(); entries.hasMoreElements();) {
ZipEntry entry = entries.nextElement();
String name = entry.getName();
if (!name.startsWith(pkg))
continue;
name = name.substring(pkg.length() + 1);
if (name.indexOf('/') < 0 && name.endsWith(".class"))
System.out.println("Found class: "
+ name.substring(0, name.length() - 6));
}
} catch (ZipException e) {
System.out.println("Not a ZIP: " + e.getMessage());
} catch (IOException e) {
System.err.println(e.getMessage());
}
}
Now once you have all class names withing package, you can try loading them with reflection and analyze if they are classes or interfaces, etc.
I've been trying to use the Reflections library, but had some problems using it, and there were too many jars I should include just to simply obtain the classes on a package.
I'll post a solution I've found in this duplicate question: How to get all classes names in a package?
The answer was written by sp00m; I've added some corrections to make it work:
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
public final class ClassFinder {
private final static char DOT = '.';
private final static char SLASH = '/';
private final static String CLASS_SUFFIX = ".class";
private final static String BAD_PACKAGE_ERROR = "Unable to get resources from path '%s'. Are you sure the given '%s' package exists?";
public final static List<Class<?>> find(final String scannedPackage) {
final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
final String scannedPath = scannedPackage.replace(DOT, SLASH);
final Enumeration<URL> resources;
try {
resources = classLoader.getResources(scannedPath);
} catch (IOException e) {
throw new IllegalArgumentException(String.format(BAD_PACKAGE_ERROR, scannedPath, scannedPackage), e);
}
final List<Class<?>> classes = new LinkedList<Class<?>>();
while (resources.hasMoreElements()) {
final File file = new File(resources.nextElement().getFile());
classes.addAll(find(file, scannedPackage));
}
return classes;
}
private final static List<Class<?>> find(final File file, final String scannedPackage) {
final List<Class<?>> classes = new LinkedList<Class<?>>();
if (file.isDirectory()) {
for (File nestedFile : file.listFiles()) {
classes.addAll(find(nestedFile, scannedPackage));
}
//File names with the $1, $2 holds the anonymous inner classes, we are not interested on them.
} else if (file.getName().endsWith(CLASS_SUFFIX) && !file.getName().contains("$")) {
final int beginIndex = 0;
final int endIndex = file.getName().length() - CLASS_SUFFIX.length();
final String className = file.getName().substring(beginIndex, endIndex);
try {
final String resource = scannedPackage + DOT + className;
classes.add(Class.forName(resource));
} catch (ClassNotFoundException ignore) {
}
}
return classes;
}
}
To use it just call the find method as sp00n mentioned in this example: I've added the creation of instances of the classes if needed.
List<Class<?>> classes = ClassFinder.find("com.package");
ExcelReporting excelReporting;
for (Class<?> aClass : classes) {
Constructor constructor = aClass.getConstructor();
//Create an object of the class type
constructor.newInstance();
//...
}
I just wrote a util class, it include test methods, you can have a check ~
IteratePackageUtil.java:
package eric.j2se.reflect;
import java.util.Set;
import org.reflections.Reflections;
import org.reflections.scanners.ResourcesScanner;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;
/**
* an util to iterate class in a package,
*
* @author eric
* @date Dec 10, 2013 12:36:46 AM
*/
public class IteratePackageUtil {
/**
* <p>
* Get set of all class in a specified package recursively. this only support lib
* </p>
* <p>
* class of sub package will be included, inner class will be included,
* </p>
* <p>
* could load class that use the same classloader of current class, can't load system packages,
* </p>
*
* @param pkg
* path of a package
* @return
*/
public static Set<Class<? extends Object>> getClazzSet(String pkg) {
// prepare reflection, include direct subclass of Object.class
Reflections reflections = new Reflections(new ConfigurationBuilder().setScanners(new SubTypesScanner(false), new ResourcesScanner())
.setUrls(ClasspathHelper.forClassLoader(ClasspathHelper.classLoaders(new ClassLoader[0])))
.filterInputsBy(new FilterBuilder().includePackage(pkg)));
return reflections.getSubTypesOf(Object.class);
}
public static void test() {
String pkg = "org.apache.tomcat.util";
Set<Class<? extends Object>> clazzSet = getClazzSet(pkg);
for (Class<? extends Object> clazz : clazzSet) {
System.out.println(clazz.getName());
}
}
public static void main(String[] args) {
test();
}
}
Almost all the answers either uses Reflections
or reads class files from file system. If you try to read classes from file system, you may get errors when you package your application as JAR or other. Also you may not want to use a separate library for that purpose.
Here is another approach which is pure java and not depends on file system.
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
public class PackageUtil {
public static Collection<Class> getClasses(final String pack) throws Exception {
final StandardJavaFileManager fileManager = ToolProvider.getSystemJavaCompiler().getStandardFileManager(null, null, null);
return StreamSupport.stream(fileManager.list(StandardLocation.CLASS_PATH, pack, Collections.singleton(JavaFileObject.Kind.CLASS), false).spliterator(), false)
.map(javaFileObject -> {
try {
final String[] split = javaFileObject.getName()
.replace(".class", "")
.replace(")", "")
.split(Pattern.quote(File.separator));
final String fullClassName = pack + "." + split[split.length - 1];
return Class.forName(fullClassName);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
})
.collect(Collectors.toCollection(ArrayList::new));
}
}
Java 8 is not a must. You can use for loops instead of streams. And you can test it like this
public static void main(String[] args) throws Exception {
final String pack = "java.nio.file"; // Or any other package
PackageUtil.getClasses(pack).stream().forEach(System.out::println);
}
ToolProvider.getSystemJavaCompiler()
, this code doesn't scan nested packages.
Aleksander Blomskøld's solution did not work for me for parameterized tests @RunWith(Parameterized.class)
when using Maven. The tests were named correctly and also where found but not executed:
-------------------------------------------------------
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
A similar issue has been reported here.
In my case @Parameters
is creating instances of each class in a package. The tests worked well when run locally in the IDE. However, when running Maven no classes where found with Aleksander Blomskøld's solution.
I did make it work with the following snipped which was inspired by David Pärsson's comment on Aleksander Blomskøld's answer:
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);
I couldn't find a short working snippet for something so simple. So here it is, I made it myself after screwing around for a while:
Reflections reflections =
new Reflections(new ConfigurationBuilder()
.filterInputsBy(new FilterBuilder().includePackage(packagePath))
.setUrls(ClasspathHelper.forPackage(packagePath))
.setScanners(new SubTypesScanner(false)));
Set<String> typeList = reflections.getAllTypes();
It uses org.reflections
.
Provided you are not using any dynamic class loaders you can search the classpath and for each entry search the directory or JAR file.
Worth mentioning
If you want to have a list of all classes under some package, you can use Reflection
the following way:
List<Class> myTypes = new ArrayList<>();
Reflections reflections = new Reflections("com.package");
for (String s : reflections.getStore().get(SubTypesScanner.class).values()) {
myTypes.add(Class.forName(s));
}
This will create a list of classes that later you can use them as you wish.
It is very possible, but without additional libraries like Reflections
it is hard...
It is hard because you haven't full instrument for get class name.
And, I take the code of my ClassFinder
class:
package play.util;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* Created by LINKOR on 26.05.2017 in 15:12.
* Date: 2017.05.26
*/
public class FileClassFinder {
private JarFile file;
private boolean trouble;
public FileClassFinder(String filePath) {
try {
file = new JarFile(filePath);
} catch (IOException e) {
trouble = true;
}
}
public List<String> findClasses(String pkg) {
ArrayList<String> classes = new ArrayList<>();
Enumeration<JarEntry> entries = file.entries();
while (entries.hasMoreElements()) {
JarEntry cls = entries.nextElement();
if (!cls.isDirectory()) {
String fileName = cls.getName();
String className = fileName.replaceAll("/", ".").replaceAll(File.pathSeparator, ".").substring(0, fileName.lastIndexOf('.'));
if (className.startsWith(pkg)) classes.add(className.substring(pkg.length() + 1));
}
}
return classes;
}
}
this scans the class loaders and all parent loaders for jar files and directories. the jar files and directories referred by the Class-Path of the jars are also loaded.
this code is testet with Java 8,11,18. on 8 everything works perfectly using the URLClassLoader and the getURLs() method. on 11 it works fine using reflections, but the JVM prints a warning on the stderr stream (not redirectible with System.setErr() with my JVM) on 18 the reflections are useless (throws NoSuchMethod/Field), and the only thing (where I know that it works) is to use the getResource() method. When the class loader loades the resources of the given package from the file system a simple path url is returned. When the class loader loades the resources from a jar a url like 'jar:file:[jar-path]!/[in-jar-path]' is returned.
I have used the answer https://stackoverflow.com/a/1157352/18252455 (from a duplicate question) and added the functionality to read the Class-Path and also search for directory URLs.
/**
* orig description:<br>
* Scans all classloaders for the current thread for loaded jars, and then scans
* each jar for the package name in question, listing all classes directly under
* the package name in question. Assumes directory structure in jar file and class
* package naming follow java conventions (i.e. com.example.test.MyTest would be in
* /com/example/test/MyTest.class)
* <p>
* in addition this method also scans for directories, where also is assumed, that the classes are
* placed followed by the java conventions. (i.e. <code>com.example.test.MyTest</code> would be in
* <code>directory/com/example/test/MyTest.class</code>)
* <p>
* this method also reads the jars Class-Path for other jars and directories. for the jars and
* directories referred in the jars are scanned with the same rules as defined here.<br>
* it is ensured that no jar/directory is scanned exactly one time.
* <p>
* if {@code bailError} is <code>true</code> all errors will be wrapped in a
* {@link RuntimeException}
* and then thrown.<br>
* a {@link RuntimeException} will also be thrown if something unexpected happens.<br>
*
* @param packageName
* the name of the package for which the classes should be searched
* @param allowSubPackages
* <code>true</code> is also classes in sub packages should be found
* @param loader
* the {@link ClassLoader} which should be used to find the URLs and to load classes
* @param bailError
* if all {@link Exception} should be re-thrown wrapped in {@link RuntimeException} and
* if a {@link RuntimeException} should be thrown, when something is not as expected.
* @see https://stackoverflow.com/questions/1156552/java-package-introspection
* @see https://stackoverflow.com/a/1157352/18252455
* @see https://creativecommons.org/licenses/by-sa/2.5/
* @see https://creativecommons.org/licenses/by-sa/2.5/legalcode
*/
public static Set <Class <?>> tryGetClassesForPackage(String packageName, boolean allowSubPackages, ClassLoader loader, boolean bailError) {
Set <URL> jarUrls = new HashSet <URL>();
Set <Path> directorys = new HashSet <Path>();
findClassPools(loader, jarUrls, directorys, bailError, packageName);
Set <Class <?>> jarClasses = findJarClasses(allowSubPackages, packageName, jarUrls, directorys, loader, bailError);
Set <Class <?>> dirClasses = findDirClasses(allowSubPackages, packageName, directorys, loader, bailError);
jarClasses.addAll(dirClasses);
return jarClasses;
}
private static Set <Class <?>> findDirClasses(boolean subPackages, String packageName, Set <Path> directorys, ClassLoader loader, boolean bailError) {
Filter <Path> filter;
Set <Class <?>> result = new HashSet <>();
for (Path startPath : directorys) {
String packagePath = packageName.replace(".", startPath.getFileSystem().getSeparator());
final Path searchPath = startPath.resolve(packagePath).toAbsolutePath();
if (subPackages) {
filter = p -> {
p = p.toAbsolutePath();
Path other;
if (p.getNameCount() >= searchPath.getNameCount()) {
other = searchPath;
} else {
other = searchPath.subpath(0, p.getNameCount());
}
if (p.startsWith(other)) {
return true;
} else {
return false;
}
};
} else {
filter = p -> {
p = p.toAbsolutePath();
if (p.getNameCount() > searchPath.getNameCount() + 1) {
return false;
} else if (p.toAbsolutePath().startsWith(searchPath)) {
return true;
} else {
return false;
}
};
}
if (Files.exists(searchPath)) {
findDirClassFilesRecursive(filter, searchPath, startPath, result, loader, bailError);
} // the package does not have to exist in every directory
}
return result;
}
private static void findDirClassFilesRecursive(Filter <Path> filter, Path path, Path start, Set <Class <?>> classes, ClassLoader loader, boolean bailError) {
try (DirectoryStream <Path> dirStream = Files.newDirectoryStream(path, filter)) {
for (Path p : dirStream) {
if (Files.isDirectory(p)) {
findDirClassFilesRecursive(filter, p, start, classes, loader, bailError);
} else {
Path subp = p.subpath(start.getNameCount(), p.getNameCount());
String str = subp.toString();
if (str.endsWith(".class")) {
str = str.substring(0, str.length() - 6);
String sep = p.getFileSystem().getSeparator();
if (str.startsWith(sep)) {
str = str.substring(sep.length());
}
if (str.endsWith(sep)) {
str = str.substring(0, str.length() - sep.length());
}
String fullClassName = str.replace(sep, ".");
try {
Class <?> cls = Class.forName(fullClassName, false, loader);
classes.add(cls);
} catch (ClassNotFoundException e) {
if (bailError) {
throw new RuntimeException(e);
}
}
}
}
}
} catch (IOException e) {
if (bailError) {
throw new RuntimeException(e);
}
}
}
private static Set <Class <?>> findJarClasses(boolean subPackages, String packageName, Set <URL> nextJarUrls, Set <Path> directories, ClassLoader loader, boolean bailError) {
String packagePath = packageName.replace('.', '/');
Set <Class <?>> result = new HashSet <>();
Set <URL> allJarUrls = new HashSet <>();
while (true) {
Set <URL> thisJarUrls = new HashSet <>(nextJarUrls);
thisJarUrls.removeAll(allJarUrls);
if (thisJarUrls.isEmpty()) {
break;
}
allJarUrls.addAll(thisJarUrls);
for (URL url : thisJarUrls) {
try (JarInputStream stream = new JarInputStream(url.openStream())) {
// may want better way to open url connections
readJarClassPath(stream, nextJarUrls, directories, bailError);
JarEntry entry = stream.getNextJarEntry();
while (entry != null) {
String name = entry.getName();
int i = name.lastIndexOf("/");
if (i > 0 && name.endsWith(".class")) {
try {
if (subPackages) {
if (name.substring(0, i).startsWith(packagePath)) {
Class <?> cls = Class.forName(name.substring(0, name.length() - 6).replace("/", "."), false, loader);
result.add(cls);
}
} else {
if (name.substring(0, i).equals(packagePath)) {
Class <?> cls = Class.forName(name.substring(0, name.length() - 6).replace("/", "."), false, loader);
result.add(cls);
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
entry = stream.getNextJarEntry();
}
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return result;
}
private static void readJarClassPath(JarInputStream stream, Set <URL> jarUrls, Set <Path> directories, boolean bailError) {
Object classPathObj = stream.getManifest().getMainAttributes().get(new Name("Class-Path"));
if (classPathObj == null) {
return;
}
if (classPathObj instanceof String) {
String[] entries = ((String) classPathObj).split("\\s+");// should also work with a single space (" ")
for (String entry : entries) {
try {
URL url = new URL(entry);
addFromUrl(jarUrls, directories, url, bailError);
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
} else if (bailError) {
throw new RuntimeException("the Class-Path attribute is no String: " + classPathObj.getClass().getName() + " tos='" + classPathObj + "'");
}
}
private static void findClassPools(ClassLoader classLoader, Set <URL> jarUrls, Set <Path> directoryPaths, boolean bailError, String packageName) {
packageName = packageName.replace('.', '/');
while (classLoader != null) {
if (classLoader instanceof URLClassLoader) {
for (URL url : ((URLClassLoader) classLoader).getURLs()) {
addFromUrl(jarUrls, directoryPaths, url, bailError);
System.out.println("rurl-class-loade.url[n]r->'" + url + "'");
}
} else {
URL res = classLoader.getResource("");
if (res != null) {
addFromUrl(jarUrls, directoryPaths, res, bailError);
}
res = classLoader.getResource("/");
if (res != null) {
addFromUrl(jarUrls, directoryPaths, res, bailError);
}
res = classLoader.getResource("/" + packageName);
if (res != null) {
res = removePackageFromUrl(res, packageName, bailError);
if (res != null) {
addFromUrl(jarUrls, directoryPaths, res, bailError);
}
}
res = classLoader.getResource(packageName);
if (res != null) {
res = removePackageFromUrl(res, packageName, bailError);
if (res != null) {
addFromUrl(jarUrls, directoryPaths, res, bailError);
}
}
addFromUnknownClass(classLoader, jarUrls, directoryPaths, bailError, 8);
}
classLoader = classLoader.getParent();
}
}
private static URL removePackageFromUrl(URL res, String packagePath, boolean bailError) {
packagePath = "/" + packagePath;
String urlStr = res.toString();
if ( !urlStr.endsWith(packagePath)) {
if (bailError) {
throw new RuntimeException("the url string does not end with the packagepath! packagePath='" + packagePath + "' urlStr='" + urlStr + "'");
} else {
return null;
}
}
urlStr = urlStr.substring(0, urlStr.length() - packagePath.length());
if (urlStr.endsWith("!")) {
urlStr = urlStr.substring(0, urlStr.length() - 1);
}
if (urlStr.startsWith("jar:")) {
urlStr = urlStr.substring(4);
}
try {
return new URL(urlStr);
} catch (MalformedURLException e) {
if (bailError) {
throw new RuntimeException(e);
} else {
return null;
}
}
}
private static void addFromUnknownClass(Object instance, Set <URL> jarUrls, Set <Path> directoryPaths, boolean bailError, int maxDeep) {
Class <?> cls = instance.getClass();
while (cls != null) {
Field[] fields = cls.getDeclaredFields();
for (Field field : fields) {
Class <?> type = field.getType();
Object value;
try {
value = getValue(instance, field);
if (value != null) {
addFromUnknownValue(value, jarUrls, directoryPaths, bailError, type, field.getName(), maxDeep - 1);
}
} catch (IllegalArgumentException | IllegalAccessException | SecurityException e) {
if (bailError) {
final String version = System.getProperty("java.version");
String vers = version;
if (vers.startsWith("1.")) {
vers = vers.substring(2);
}
int dotindex = vers.indexOf('.');
if (dotindex != -1) {
vers = vers.substring(0, dotindex);
}
int versNum;
try {
versNum = Integer.parseInt(vers);
} catch (NumberFormatException e1) {
throw new RuntimeException("illegal version: '" + version + "' lastError: " + e.getMessage(), e);
}
if (versNum <= 11) {
throw new RuntimeException(e);
}
}
}
}
cls = cls.getSuperclass();
}
}
private static Object getValue(Object instance, Field field) throws IllegalArgumentException, IllegalAccessException, SecurityException {
try {
boolean flag = field.isAccessible();
boolean newflag = flag;
try {
field.setAccessible(true);
newflag = true;
} catch (Exception e) {}
try {
return field.get(instance);
} finally {
if (flag != newflag) {
field.setAccessible(flag);
}
}
} catch (IllegalArgumentException | IllegalAccessException | SecurityException e) {
try {
Field override = AccessibleObject.class.getDeclaredField("override");
boolean flag = override.isAccessible();
boolean newFlag = flag;
try {
override.setAccessible(true);
flag = true;
} catch (Exception s) {}
override.setBoolean(field, true);
if (flag != newFlag) {
override.setAccessible(flag);
}
return field.get(instance);
} catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e1) {
e.addSuppressed(e1);
throw e;
}
}
}
private static void addFromUnknownValue(Object value, Set <URL> jarUrls, Set <Path> directoryPaths, boolean bailError, Class <?> type, String fieldName, int maxDeep) {
if (Collection.class.isAssignableFrom(type)) {
for (Object obj : (Collection <?>) value) {
URL url = null;
try {
if (obj instanceof URL) {
url = (URL) obj;
} else if (obj instanceof Path) {
url = ((Path) obj).toUri().toURL();
} else if (obj instanceof File) {
url = ((File) obj).toURI().toURL();
}
} catch (MalformedURLException e) {
if (bailError) {
throw new RuntimeException(e);
}
}
if (url != null) {
addFromUrl(jarUrls, directoryPaths, url, bailError);
}
}
} else if (URL[].class.isAssignableFrom(type)) {
for (URL url : (URL[]) value) {
addFromUrl(jarUrls, directoryPaths, url, bailError);
}
} else if (Path[].class.isAssignableFrom(type)) {
for (Path path : (Path[]) value) {
try {
addFromUrl(jarUrls, directoryPaths, path.toUri().toURL(), bailError);
} catch (MalformedURLException e) {
if (bailError) {
throw new RuntimeException(e);
}
}
}
} else if (File[].class.isAssignableFrom(type)) {
for (File file : (File[]) value) {
try {
addFromUrl(jarUrls, directoryPaths, file.toURI().toURL(), bailError);
} catch (MalformedURLException e) {
if (bailError) {
throw new RuntimeException(e);
}
}
}
} else if (maxDeep > 0) {
addFromUnknownClass(value, jarUrls, directoryPaths, bailError, maxDeep - 1);
}
}
private static void addFromUrl(Set <URL> jarUrls, Set <Path> directoryPaths, URL url, boolean bailError) {
if (url.getFile().endsWith(".jar") || url.getFile().endsWith(".zip")) {
// may want better way to detect jar files
jarUrls.add(url);
} else {
try {
Path path = Paths.get(url.toURI());
if (Files.isDirectory(path)) {
directoryPaths.add(path);
} else if (bailError) {
throw new RuntimeException("unknown url for class loading: " + url);
}
} catch (URISyntaxException e) {
if (bailError) {
throw new RuntimeException(e);
}
}
}
}
imports:
import java.io.File;
import java.io.IOException;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.DirectoryStream;
import java.nio.file.DirectoryStream.Filter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.Attributes.Name;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
Define classes to be scanning in the package test
package test;
public class A {
private class B {}
enum C {}
record D() {}
}
For org.reflections:reflections:0.10.2, it works for me as follows:
Use reflection lib to scan classes in package test
@Test
void t() {
final String packagePath = "test";
final Reflections reflections =
new Reflections(packagePath, Scanners.SubTypes.filterResultsBy(v -> true));
reflections.getAll(Scanners.SubTypes).forEach(System.out::println);
}
Output
java.lang.constant.Constable
java.lang.Enum
java.lang.Comparable
java.lang.Record
java.lang.Object
java.io.Serializable
test.A$C
test.A$D
test.A$B
test.A
For io.github.classgraph:classgraph:4.8.146, it works for me as follows:
@Test
void t() {
final String packagePath = "test";
try (ScanResult scanResult = new ClassGraph()
.enableClassInfo()
.ignoreClassVisibility()
.acceptPackages(packagePath)
.scan()) {
scanResult.getAllClasses()
.forEach(v -> {
System.out.println(v.getName());
});
}
}
Output
test.A
test.A$B
test.A$C
test.A$D
Based on @Staale's answer, and in an attempt not to rely on third party libraries, I would implement the File System approach by inspecting first package physical location with:
import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
...
Class<?>[] foundClasses = new Class<?>[0];
final ArrayList<Class<?>> foundClassesDyn = new ArrayList<Class<?>>();
new java.io.File(
klass.getResource(
"/" + curPackage.replace( "." , "/")
).getFile()
).listFiles(
new java.io.FileFilter() {
public boolean accept(java.io.File file) {
final String classExtension = ".class";
if ( file.isFile()
&& file.getName().endsWith(classExtension)
// avoid inner classes
&& ! file.getName().contains("$") )
{
try {
String className = file.getName();
className = className.substring(0, className.length() - classExtension.length());
foundClassesDyn.add( Class.forName( curPackage + "." + className ) );
} catch (ClassNotFoundException e) {
e.printStackTrace(System.out);
}
}
return false;
}
}
);
foundClasses = foundClassesDyn.toArray(foundClasses);
plain java: FindAllClassesUsingPlainJavaReflectionTest.java
@Slf4j
class FindAllClassesUsingPlainJavaReflectionTest {
private static final Function<Throwable, RuntimeException> asRuntimeException = throwable -> {
log.error(throwable.getLocalizedMessage());
return new RuntimeException(throwable);
};
private static final Function<String, Collection<Class<?>>> findAllPackageClasses = basePackageName -> {
Locale locale = Locale.getDefault();
Charset charset = StandardCharsets.UTF_8;
val fileManager = ToolProvider.getSystemJavaCompiler()
.getStandardFileManager(/* diagnosticListener */ null, locale, charset);
StandardLocation location = StandardLocation.CLASS_PATH;
JavaFileObject.Kind kind = JavaFileObject.Kind.CLASS;
Set<JavaFileObject.Kind> kinds = Collections.singleton(kind);
val javaFileObjects = Try.of(() -> fileManager.list(location, basePackageName, kinds, /* recurse */ true))
.getOrElseThrow(asRuntimeException);
String pathToPackageAndClass = basePackageName.replace(".", File.separator);
Function<String, String> mapToClassName = s -> {
String prefix = Arrays.stream(s.split(pathToPackageAndClass))
.findFirst()
.orElse("");
return s.replaceFirst(prefix, "")
.replaceAll(File.separator, ".");
};
return StreamSupport.stream(javaFileObjects.spliterator(), /* parallel */ true)
.filter(javaFileObject -> javaFileObject.getKind().equals(kind))
.map(FileObject::getName)
.map(fileObjectName -> fileObjectName.replace(".class", ""))
.map(mapToClassName)
.map(className -> Try.of(() -> Class.forName(className))
.getOrElseThrow(asRuntimeException))
.collect(Collectors.toList());
};
@Test
@DisplayName("should get classes recursively in given package")
void test() {
Collection<Class<?>> classes = findAllPackageClasses.apply(getClass().getPackage().getName());
assertThat(classes).hasSizeGreaterThan(4);
classes.stream().map(String::valueOf).forEach(log::info);
}
}
PS: to simplify boilerplates for handling errors, etc, I'm using here vavr
and lombok
libraries
other implementations could be found in my GitHub daggerok/java-reflection-find-annotated-classes-or-methods repo
As of org.reflections
version 0.10
:
org.reflections.scanners.SubTypesScanner
and
org.reflections.Reflections.getAllTypes()
are deprecated. I userd:
public Set<String> getEntityNamesInPackage(String packagePath) {
Reflections reflections = new Reflections(new ConfigurationBuilder()
.filterInputsBy(new FilterBuilder().includePackage(packagePath))
.setUrls(ClasspathHelper.forPackage(packagePath))
.setScanners(SubTypes.filterResultsBy(s -> true)));
return reflections.getAll(SubTypes).stream()
.filter(s -> s.startsWith(packagePath))
.collect(Collectors.toSet());
}
If you are merely looking to load a group of related classes, then Spring can help you.
Spring can instantiate a list or map of all classes that implement a given interface in one line of code. The list or map will contain instances of all the classes that implement that interface.
That being said, as an alternative to loading the list of classes out of the file system, instead just implement the same interface in all the classes you want to load, regardless of package and use Spring to provide you instances of all of them. That way, you can load (and instantiate) all the classes you desire regardless of what package they are in.
On the other hand, if having them all in a package is what you want, then simply have all the classes in that package implement a given interface.
Note that the interface itself doesn't have to declare any methods - it can be completely empty.
To inject a list of classes implementing a given interface, use the following lines of code...
@Autowired
private List<ISomeInterface> implementationList;
It is also possible to inject a Map of classes using Spring. Read the docs if interested to see how.
Finally, I will offer one other solution that is a bit more elegant than searching the entire file system tree.
Create a custom annotation that builds a catalog of the classes to which it is applied - something like @ClassCatalog.
It is not possible, since all classes in the package might not be loaded, while you always knows package of a class.
Success story sharing