ClassIndex is an index of classes which you can query for:
ClassIndex:
Add dependency
<dependency>
<groupId>org.atteo.classindex</groupId>
<artifactId>classindex</artifactId>
<version>3.4</version>
</dependency>
compile('org.atteo.classindex:classindex:3.4')
annotationProcessor('org.atteo.classindex:classindex:3.4') // for gradle 5.0+
libraryDependencies ++= Seq(
"org.atteo.classindex" % "classindex" % "3.4"
)
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:
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()](https://docs.oracle.com/javase/10/docs/api/java/net/URLClassLoader.html#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:
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:
ClassFilter.only()
.topLevel()
.from(ClassIndex.getAnnotated(SomeAnnotation.class));
ClassFilter.only()
.topLevel()
.withModifiers(Modifier.PUBLIC)
.from(ClassIndex.getAnnotated(SomeAnnotation.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:
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: