Hi Yunior,
I dropped back to JDK 8 (indeed your code does not work on JDK 10), and I was able to reproduce the problem.
Here is the issue: in your code, you do:
.matchClassesWithAnnotation(AnnotationWithClassValue.class, classAnnotationMatchProcessor)
However, using the class reference in red means that this annotation class is already loaded into the context classloader for the "scanner" project. In the ClassAnnotationMatchProcessor, you do:
AnnotationWithClassValue a = classWithAnnotation.getAnnotation(AnnotationWithClassValue.class);
This is the line that throws the exception. Note that the .getAnnotation(clazz) method does not take a ClassLoader parameter, the way that Class.forName() does in one of its forms. That means that .getAnnotation(clazz) can only find annotations that are loaded into the current classloader.
At that point though, classWithAnnotation has already been loaded by FCS' own custom classloader -- a custom classloader had to be created using .createClassLoaderForMatchingClasses(), since you are adding a Spring-Boot jar to the classpath, and the scanner is not running within that jar. So now you are dealing with two different classloaders: the annotated class was loaded by the custom FCS classloader, and the annotation class was already loaded (and cached) by the context classloader. You cannot have classes linked to each other that span more than one classloader.
The solution here is to get FCS to do all your classloading. That means you should never reference any of the classes in your scanned packages using a class reference of the form AnnotationWithClassValue.class, but instead, only ever reference this class by name, until scanning is complete. Once scanning is complete, you can use the FCS classloader to get class references using methods like classInfo.getClassRef(), or annotationClassRef.getType().
Here is how you would do the scanning without doing any classloading until you can do so safely through the FCS custom classloader. (This is FCS' own API that sort of mimics the Java reflection API.)
ScanResult scanResult = new FastClasspathScanner("com.foo").overrideClassLoaders(loader) .createClassLoaderForMatchingClasses() .ignoreParentClassLoaders() .scan(); List<String> annotatedClasses = scanResult .getNamesOfClassesWithAnnotation("com.lib.externalLib.AnnotationWithClassValue"); for (String annotatedClass : annotatedClasses) { ClassInfo annotatedClassInfo = scanResult.getClassNameToClassInfo().get(annotatedClass); // You can safely load the annotated class here using: Class<?> annotatedClass = annotatedClassInfo.getClassRef(); for (AnnotationInfo annotationInfo : annotatedClassInfo.getAnnotationInfo()) { if (annotationInfo.getAnnotationName().equals("com.lib.externalLib.AnnotationWithClassValue")) { AnnotationClassRef clazz = null; String name = null; List<AnnotationParamValue> annotationParamValues = annotationInfo.getAnnotationParamValues(); for (AnnotationParamValue annotationParamValue : annotationParamValues) { String paramName = annotationParamValue.getParamName(); if (paramName.equals("clazz")) { clazz = (AnnotationClassRef) annotationParamValue.getParamValue(); } else if (paramName.equals("name")) { name = (String) annotationParamValue.getParamValue(); } } System.out.println(annotatedClassInfo.getClassName() + " has annotation " + annotationInfo.getAnnotationName() + " with params: clazz = " + clazz.getType() + " ; name = \"" + name + "\""); // You can safely load the annotation class parameter here using: Class<?> annotationClassParam = clazz.getType(); } } }
This should solve your problem. Basically avoid using MatchProcessors altogether, and never use class references to look up classes in the ScanResult. (In fact, I will probably remove both of those from a future release, they cause a lot of problems like this.)
Thanks for your patience while I figured out what was going on here!
Luke