Getting the classes of a package

For example, we may want to list the classes from one of the packages of the current application (for example, the modern.challenge package) or the classes from one of the packages from our compile-time libraries (for example, commons-lang-2.4.jar).

Classes are wrapped in packages that can be archived in JARs, though they don't have to be. In order to cover both cases, we need to discover whether the given package lives in a JAR or not. We can do this by loading the resource via ClassLoader.getSystemClassLoader().getResource(package_path) and checking the returned URL of the resource. If the package doesn't live in a JAR, then a resource will be a URL starting with the file: scheme, as in the following example (we are using modern.challenge):

file:/D:/Java%20Modern%20Challenge/Code/Chapter%207/Inspect%20packages/build/classes/modern/challenge

But if the package is inside a JAR (for example, org.apache.commons.lang3.builder), then the URL will start with the jar: scheme, as in the following example:

jar:file:/D:/.../commons-lang3-3.9.jar!/org/apache/commons/lang3/builder

If we take into consideration that a resource of a package from a JAR starts with the jar: prefix, then we can write a method to distinguish between them, as follows:

private static final String JAR_PREFIX = "jar:";

public static List<Class<?>> fetchClassesFromPackage(
String packageName) throws URISyntaxException, IOException {

List<Class<?>> classes = new ArrayList<>();
String packagePath = packageName.replace('.', '/');

URL resource = ClassLoader
.getSystemClassLoader().getResource(packagePath);

if (resource != null) {
if (resource.toString().startsWith(JAR_PREFIX)) {
classes.addAll(fetchClassesFromJar(resource, packageName));
} else {
File file = new File(resource.toURI());
classes.addAll(fetchClassesFromDirectory(file, packageName));
}
} else {
throw new RuntimeException("Resource not found for package: "
+ packageName);
}

return classes;
}

So, if the given package is in a JAR, then we call another helper method, fetchClassesFromJar(); otherwise, we call this helper method, fetchClassesFromDirectory(). As their names suggest, these helpers know how to extract the classes of the given package from a JAR or from a directory.

Mainly, these two methods are just some snippets of spaghetti code that are meant to identify the files that have the .class extension. Each class is passed through Class.forName() to ensure that it is returned as Class, not as String. Both methods are available in the code bundled with this book.

How about listing the classes from packages that are not in the system class loader, for example, a package from an external JAR? A convenient way to accomplish this relies on URLClassLoader. This class is used to load classes and resources from a search path of URLs that refer to both JAR files and directories. We will deal only with JARs, but it is pretty straightforward to do this for directories as well.

So, based on the given path, we need to fetch all the JARs and return them as URL[] (this array is needed to define URLClassLoader). For example, we can rely on the Files.find() method to traverse the given path and extract all the JARs, like so:

public static URL[] fetchJarsUrlsFromClasspath(Path classpath)
throws IOException {

List<URL> urlsOfJars = new ArrayList<>();
List<File> jarFiles = Files.find(
classpath,
Integer.MAX_VALUE,
(path, attr) -> !attr.isDirectory() &&
path.toString().toLowerCase().endsWith(JAR_EXTENSION))
.map(Path::toFile)
.collect(Collectors.toList());

for (File jarFile: jarFiles) {

try {
urlsOfJars.add(jarFile.toURI().toURL());
} catch (MalformedURLException e) {
logger.log(Level.SEVERE, "Bad URL for{0} {1}",
new Object[] {
jarFile, e
});
}
}

return urlsOfJars.toArray(URL[]::new);
}

Notice that we are scanning all the subdirectories, starting with the given path. Of course, this is a design decision and it is easy to parameterize the depth of searching. For now, let's fetch the JARs from the tomcat8/lib folder (there is no need to install Tomcat especially for this; just use any other local directory of JARs and do the proper modifications):

URL[] urls = Packages.fetchJarsUrlsFromClasspath(
Path.of("D:/tomcat8/lib"));

Now, we can instantiate URLClassLoader:

URLClassLoader urlClassLoader = new URLClassLoader(
urls, Thread.currentThread().getContextClassLoader());

This will construct a new URLClassLoader object for the given URLs and will use the current class loader for delegation (the second argument can be null as well). Our URL[] points only to JARs, but as a rule of thumb, any jar: scheme URL is assumed to refer to a JAR file, and any file: scheme URL that ends with / is assumed to refer to a directory.

One of the JARs that's present in the tomcat8/lib folder is called tomcat-jdbc.jar. In this JAR, there is a package called org.apache.tomcat.jdbc.pool. Let's list the classes of this package:

List<Class<?>> classes = Packages.fetchClassesFromPackage(
"org.apache.tomcat.jdbc.pool", urlClassLoader);

The fetchClassesFromPackage() method is a helper that simply scans the URL[] array of URLClassLoader and fetches the classes that are in the given package. Its source code is available with the code bundled with this book.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.170.187