DEV Community

Cover image for A fast and flexible way to scan the class paths in Java
Roberto Gentili for Burningwave

Posted on • Updated on

A fast and flexible way to scan the class paths in Java

Overview

In this tutorial we will talk about a new class paths scanning engine and how to use it: the ClassHunter of Burningwave Core, a Java library useful for building frameworks. This class paths scanning engine gives the ability to search classes over paths or the runtime class paths by concatenable and nestable criteria using the lambda expressions on the native Java reflection elements such as Class, Field, Method, Constructor, Module, Package, Annotation.

Maven Dependencies

First, let’s add the Burningwave Core library to our pom.xml:

Usage

Now we are going to find all classes of a package and in particular all classes, in the runtime class paths, that have a package name that matches a regular expression. Here the code:

ComponentSupplier componentSupplier = ComponentContainer.getInstance();
ClassHunter classHunter = componentSupplier.getClassHunter();

SearchConfig searchConfig = SearchConfig.byCriteria(
    ClassCriteria.create().allThoseThatMatch((cls) -> {
        return cls.getPackage().getName().matches(".*springframework.*");
    })
);

try (ClassHunter.SearchResult searchResult = classHunter.findBy(searchConfig)) {
    return searchResult.getClasses();
}
Enter fullscreen mode Exit fullscreen mode

Let’s break down the example above:

  • we started by retrieving the ClassHunter through the ComponentContainer
  • we define a regular expression that we pass to the ClassCriteria object that will be injected into the SearchConfig object
  • finally we call the findBy method that loads in the cache all loadable classes of the default class paths indicated by the burningwave.properties file (in this case they are the runtime class paths: see the paragraph 5 for more informations), then applies the criteria filter and then returns the SearchResult object which contains the classes that match the criteria It is also possible to expressly indicate the paths on which to search (folders, zip, jar, ear and war will be recursively scanned), so the example above can also be written like this:
ComponentSupplier componentSupplier = ComponentContainer.getInstance();
PathHelper pathHelper = componentSupplier.getPathHelper();
ClassHunter classHunter = componentSupplier.getClassHunter();

SearchConfig searchConfig = SearchConfig.forPaths(
    pathHelper.getPaths(path -> path.contains("spring-core-4.3.4.RELEASE.jar"))
).by(
    ClassCriteria.create().allThoseThatMatch((cls) -> {
        return cls.getPackage().getName().matches(".*springframework.*");
    })
);

try (ClassHunter.SearchResult searchResult = classHunter.findBy(searchConfig)) {
    return searchResult.getClasses();
}
Enter fullscreen mode Exit fullscreen mode

And now let’s examine a more detailed example by finding all annotated class of a particular library:

ComponentSupplier componentSupplier = ComponentContainer.getInstance();
PathHelper pathHelper = componentSupplier.getPathHelper();
ClassHunter classHunter = componentSupplier.getClassHunter();

SearchConfig searchConfig = SearchConfig.forPaths(
    pathHelper.getPaths(path -> path.contains("spring-core-4.3.4.RELEASE.jar"))
).by(
    ClassCriteria.create().allThoseThatMatch((cls) -> {
        return cls.getAnnotations() != null && cls.getAnnotations().length > 0;
    }).or().byMembers(
        MethodCriteria.withoutConsideringParentClasses().allThoseThatMatch((method) -> {
            return method.getAnnotations() != null && method.getAnnotations().length > 0;
        })
    ).or().byMembers(
        FieldCriteria.withoutConsideringParentClasses().allThoseThatMatch((field) -> {
            return field.getAnnotations() != null && field.getAnnotations().length > 0;
        })
    ).or().byMembers(
        ConstructorCriteria.withoutConsideringParentClasses().allThoseThatMatch((ctor) -> {
            return ctor.getAnnotations() != null && ctor.getAnnotations().length > 0;
        })
    )
);

try (ClassHunter.SearchResult searchResult = classHunter.findBy(searchConfig)) {
    //If you need all annotaded methods, constructors and field unconment this line
    //searchResult.getMembersFlatMap().values();
    return searchResult.getClasses();
}
Enter fullscreen mode Exit fullscreen mode

In the example above unlike the previous example:

  • we are assuming that in the runtime class paths is present a jar named “spring-core-4.3.4.RELEASE.jar” that we set up as the unique resource to be scanned
  • we create a complex ClassCriteria with which we ask for all classes that have at least one annotation on class declaration or at least one annotation on the declaration of any of its members (fields, methods or constructors). Notice the call to the ‘byScanUpTo’ method: it receives a lambda expression as input through which, in this case, we ask the scanning engine to apply the filter of the criteria for member (ConstructorCriteria, FieldCriteria and MethodCriteria) only on the iterated class and not on its entire hierarchy because otherwise, by default, the filters injected in the member criterias, are applied to the entire hierarchy

Main component characteristics and configuration

The ClassHunter is a component that queries the classes present in the paths received in input and returns only the classes that match a chosen criterion. The searches performed by it are executed in a multithreaded context and recursively through folders and supported compressed files (zip, jar, jmod, war and ear) and even in nested compressed files and folders. This component can cache the classes found in the class paths in order to perform the next searches faster and uses a PathScannerClassLoader which is configurable through the Java coded property ‘class-hunter.default-path-scanner-class-loader’ of the burningwave.properties file. A Java coded property is a property made of Java code that will be resolved after its compilation at runtime. The default value of the ‘class-hunter.default-path-scanner-class-loader’ property, as you can see in the default burningwave.properties file, is the following:

class-hunter.default-path-scanner-class-loader=\
    (Supplier<PathScannerClassLoader>)() -> ((ComponentSupplier)parameter[0]).getPathScannerClassLoader()
Enter fullscreen mode Exit fullscreen mode

… Which means that the default class loader used by the ClassHunter is the class loader supplied by the method ‘getPathScannerClassLoader’ of ComponentContainer. The parent class loader of this class loader can be indicated through the Java coded property ‘path-scanner-class-loader.parent’ that has the following default value:

Search configuration components

The main search configuration object is represented by the SearchConfig class to which must be (optionally) passed the paths to be scanned and (optionally too) the query criteria represented by the ClassCriteria. If no path will be passed to SearchConfig, the scan will be executed on the paths indicated by the ‘paths.hunters.default-search-config.paths’ property of the burningwave.properties file that has the following default value:

paths.hunters.default-search-config.paths=\
    ${paths.main-class-paths};\
    ${paths.main-class-paths.extension};\
    ${paths.main-class-repositories};
Enter fullscreen mode Exit fullscreen mode

… And includes the following properties:

paths.main-class-paths=\
    ${system.properties:java.class.path}
paths.main-class-paths.extension=\
    //${system.properties:java.home}/lib//children:.*?\.jar;\
    //${system.properties:java.home}/lib/ext//children:.*?\.jar;
    //${system.properties:java.home}/../lib//children:.*?\.jar;
paths.main-class-repositories=\
    //${system.properties:java.home}/jmods//children:.*?\.jmod;
Enter fullscreen mode Exit fullscreen mode

… Which means that the scan will be executed through:

  • the runtime class paths (which is indicated by the system property ‘java.class.path’)
  • the direct children of the ‘lib’ folder of the jvm home that have ‘jar’ as extension
  • the direct children of the ‘jmods’ folder of the jvm (9 or later) home that have ‘jmod’ as extension

If no ClassCriteria will be passed to SearchConfig object the search will be executed with no filter and all loadable classes of the paths will be returned.

Conclusion

In this article, we learned how to effectively filter classes of some class paths for different criteria.
The complete source code of all the examples in this article is available on GitHub along with other examples.

Top comments (0)