ClassIndex is an index of classes which you can query for:
- the list of classes annotated by a given annotation (see: getAnnotated()),
- the list of classes implementing a given interface (see: getSubclasses()),
- the list of subclasses of a given class (see: getSubclasses()),
- the list of classes from a given package (see: getPackageClasses()).
- Javadoc summary (see: getClassSummary())
- and more
ClassIndex:
- is as fast as reading a file, it is not impacted by the usual performance penalty of the classpath scanning,
- is based on standard APIs provided by Java, like annotation processing, it does not assume any inner workings of the ClassLoaders, it does not analyse bytecode of a compiled classes,
- supports incremental compilation in IntelliJ, NetBeans and Eclipse,
- is compatible with Java modules,
- is compatible with ServiceLoader,
- is compatible with jaxb.index,
-
Add dependency
- Maven
<dependency> <groupId>org.atteo.classindex</groupId> <artifactId>classindex</artifactId> <version>3.4</version> </dependency>
- Gradle
compile('org.atteo.classindex:classindex:3.4') annotationProcessor('org.atteo.classindex:classindex:3.4') // for gradle 5.0+
- For other see: Maven Central
- Maven
-
Annotate your annotation with @IndexAnnotated
@IndexAnnotated public @interface Entity {}
-
Retrieve a list of annotated classes at run-time
Iterable<Class<?>> klasses = ClassIndex.getAnnotated(Entity.class);
ClassIndex indexes your classes at compile time by providing the implementation of the standard annotation processor. Adding ClassIndex to your compile classpath is sufficient to trigger indexing process thanks to automatic discovery of annotation processors by 'javac'.
ClassIndex provides a convenient API to read the indexes generated by the annotation processor.
Traditional classpath scanning is a very slow process. Replacing it with compile-time indexing speeds Java applications bootstrap considerably.
Here are the results of the benchmark comparing ClassIndex with various scanning solutions.
Library | Application startup time |
---|---|
None - hardcoded list | 0:00.18 |
Scannotation | 0:05.11 |
Reflections | 0:05.37 |
Reflections Maven plugin | 0:00.52 |
Corn | 0:24.60 |
ClassIndex | 0:00.18 |
Notes: benchmark was performed on Intel i5-2520M CPU @ 2.50GHz, classpath size was set to 121MB.
The traditional approach to retrieve a list of classes annotated with a given annotation is a process called classpath scanning. This involves:
- finding out the application run-time classpath and
- analysing the bytecode of the classes located there.
The first part is not really possible in a general case, because ClassLoader API does not provide a method to retrieve its classpath. Well, the ClassLoader can even generate the classes on the fly. Usually, though, the specific classloader implementation used is a URLClassLoader from which the classpath can be retrieved using its getURLs() method.
ClassIndex, on the other hand, uses annotation processing to generate the index of classes at compile-time ant put it with the rest of the compiled .class files. This does not slow the compilation process in any measurable way. The index is then available at run-time using the getResource() method which must be implemented by all classloaders.
There are two annotations which trigger compile-time indexing:
- @IndexSubclasses
- when placed on interface makes an index of all classes implementing the interface, the classes can then be retrieved using getSubclasses() method.
- when placed on a class makes an index of its subclasses
- and finally when placed in package-info.java it creates an index of all classes inside that package (directly - without subpackages), the classes can then be retrieved using getPackageClasses() method. .
- @IndexAnnotated when placed on an annotation makes an index of all classes marked with that annotation, the classes can then be retrieved using getAnnotated() method.
Here is an example:
@IndexAnnotated
public @interface Entity {
}
@Entity
public class Car {
}
...
for (Class<?> klass : ClassIndex.getAnnotated(Entity.class)) {
System.out.println(klass.getName());
}
You can also find the complete example as a Maven project on Github: https://github.com/atteo/classindex-maven-example
For subclasses of the given class the index file name and format is compatible with what ServiceLoader expects.
This means that if you annotate your service interface with @IndexSubclasses, the files in META-INF/services/ will be generated automatically for you.
Keep in mind that ServiceLoader also requires for the classes to have a zero-argument default constructor.
For classes inside given package the index file is named "jaxb.index", it is located inside the package folder and it's format is compatible with what JAXBContext.newInstance(String) expects.
This means that if you annotate your package in package-info.java file with @IndexSubclasses, jaxb.index file will be generated automatically for you.
From version 2.0 @IndexAnnotated and @IndexSubclasses allow to specify storeJavadoc attribute. When set to true Javadoc comment for the indexed classes will be stored. You can retrieve first sentence of the Javadoc using ClassIndex.getClassSummary().
@IndexAnnotated(storeJavadoc = true)
public @interface Entity {
}
/**
* This is car.
* Detailed car description follows.
*/
@Entity
public class Car {
}
...
assertEquals("This is car", ClassIndex.getClassSummary(Car.class));
Filtering allows you to select only classes with desired characteristics. Here are some basic samples:
- Selecting only top-level classes
ClassFilter.only()
.topLevel()
.from(ClassIndex.getAnnotated(SomeAnnotation.class));
- Selecting only classes which are top level and public at the same time
ClassFilter.only()
.topLevel()
.withModifiers(Modifier.PUBLIC)
.from(ClassIndex.getAnnotated(SomeAnnotation.class));
- Selecting classes which are top-level or enclosed in given class:
ClassFilter.any(
ClassFilter.only().topLevel(),
ClassFilter.only().enclosedIn(WithInnerClassesInside.class)
).from(ClassIndex.getAnnotated(SomeAnnotation.class);
Sometimes you cannot easily use annotations to trigger compile time indexing because you don't control the source code of the classes which should be annotated. For instance you cannot add @IndexAnnotated meta-annotation to @Entity annotation. Although not so straightforward, it is still possible to use ClassIndex in this case.
There are two steps necessary:
First create a custom annotation processor by extending ClassIndexProcessor
public class MyImportantClassIndexProcessor extends ClassIndexProcessor {
public MyImportantClassIndexProcessor() {
indexAnnotations(Entity.class);
}
}
In the constructor specify what indexes should be created by calling appropriate methods:
- indexAnnotations(...) - to create index of classes annotated with given annotations
- indexSubclasses(...) - to create index of subclasses of given parent classes
- indexPackages(...) - to create index of classes inside given packages.
Finally register the processor by creating the file 'META-INF/services/javax.annotation.processing.Processor' in your classpath with the full class name of your processor, see the example here
Important note: you also need to ensure that your custom processor is always available on the classpath when compiling indexed classes. When that is not the case there will not be any error - those classes will be missing in the index.
During compilation ClassIndex writes index files. When creating a shaded jar those index files get overwritten by default. To not lost any indexed classes ClassIndex provides special transformer for Maven which merges the index files instead of overwriting them. To use it add the configuration below to your POM file:
<build>
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>@maven-shade-plugin.version@</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.atteo.classindex.ClassIndexTransformer"/>
</transformers>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.atteo.classindex</groupId>
<artifactId>classindex-transformer</artifactId>
<version>@class index version@</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
Eclipse uses its own Java compiler which is not strictly standard compliant and requires extra configuration. In Java Compiler -> Annotation Processing -> Factory Path you need to add ClassIndex jar file. See the screenshot.
ClassIndex is available under Apache License 2.0.
You can download the library from here or use the following Maven dependency: