A fast and flexible way to scan the class paths in Java
Tags: java, tutorial, classpath, scanner
Published: 2020-12-08 17:30:38

## **Overview**
In this tutorial we will talk about a new class paths scanning engine and how to use it: the **ClassHunter** of [**Burningwave Core**](https://burningwave.github.io/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_:

{% gist https://gist.github.com/Roberto-Gentili/536ee2d3f775030d46d82a008da10dbe.js %}

## **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:

```java
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();
}
```

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_](https://burningwave.github.io/core/#configuration-1) 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:

```java
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();
}
```

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

```java
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();
}
```

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_](https://burningwave.github.io/core/#configuration-1) file. A [**Java coded property**](https://dev.to/bw_software/executing-stringified-source-code-in-java-1aea) 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_](https://burningwave.github.io/core/#configuration-1) file, is the following:

```properties
class-hunter.default-path-scanner-class-loader=\
	(Supplier<PathScannerClassLoader>)() -> ((ComponentSupplier)parameter[0]).getPathScannerClassLoader()
```

… Which means that the default class loader used by the ClassHunter is the class loader supplied by the method ‘**getPathScannerClassLoader**’ of [**ComponentContainer**](https://burningwave.github.io/core/#dynamic-component-container). 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_](https://burningwave.github.io/core/#configuration-1) file that has the following default value:

```properties
paths.hunters.default-search-config.paths=\
	${paths.main-class-paths};\
	${paths.main-class-paths.extension};\
	${paths.main-class-repositories};
```

… And includes the following properties:

```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;
```

… 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](https://github.com/burningwave/core/tree/master/src/test/java/org/burningwave/core/examples/classhunter) along with other examples.