Reputation: 71
trying to scan for return type objects that are annoated with a custom annoation but the ClassPathScanningCandidateComponentProvider does not find them even though package is correct and metadata shows the methods annoated with them.
I've added a filter to my existing ClassPathScanningCandidateComponentProvider to include return types on methods annotated with a custom annoation, i.e.
@Procedure(name="PROC_MAPPING_NAME")
@CursorList(cursorType=CursorObject.class)CursorHolder<CursorObject> getCursor(@Param("col_1") @NonNull String param);
Filter is added like so
ClassPathScanningCandidateComponentProvider scanner =
new ClassPathScanningCandidateComponentProvider(false);
//existing filters
....
//new filter
scanner.addIncludeFilter(new AnnotationTypeFilter(CursorList.class));
//scan packages
return Arrays.stream(packages)
.map(scanner::findCandidateComponents)
.flatMap(Collection::stream)
.map(BeanDefinition::getBeanClassName)
.map(name -> {
try {
return Class.forName(name);
}
catch (ClassNotFoundException e) {
//handle exception
}
}).collect(Collectors.toList());
Works fine for all the existing filters and the package where the new annotation is used is definitely included as I've stepped into the code. It just fails to meet the criteria required to be considered a match and returned.
I've also tested this annotation at class level and it is then found without issue. But, it does not seem to work at attribute level or method or param level.
So stupid question given the class name. Is the ClassPathScanningCandidateComponentProvider only suitable for scanning for annoation usage when the annotation is included at class level? i.e. only finds annoated components?
If that is the case has anyone any suggestions for finding annotations and the objects they annoate where the object is a return type? Or is this just not possible?
Thanks
Upvotes: 1
Views: 1323
Reputation: 638
It seems that AnnotationTypeFilter
matches only classes as far as I investigated its javadoc and implementation.
Fortunately, we can customize AnnotationTypeFilter
in order to make it match methods.
The following is an example.
public class CustomAnnotationTypeFilter extends AnnotationTypeFilter {
public CustomAnnotationTypeFilter(
Class<? extends Annotation> annotationType) {
super(annotationType);
}
@Override
protected boolean matchSelf(MetadataReader metadataReader) {
AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
return metadata.hasAnnotatedMethods(getAnnotationType().getName());
}
}
....
ClassPathScanningCandidateComponentProvider scanner =
new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new CustomAnnotationTypeFilter(MyAnnotationA.class));
....
Our CustomAnnotationTypeFilter
matches a class like:
public class Hoge {
@MyAnnotationA String doSomething() {
return "";
}
}
NOTE
Declaring a method with annotations in front of it means to annotate the method (not the return type) with the annotations.
@Procedure(name="PROC_MAPPING_NAME")
@CursorList(cursorType=CursorObject.class)CursorHolder<CursorObject> getCursor(...) {
....
};
The example above means that you annotate getCursor
method with Procedure
and CursorList
. In such cases, you need to check whether methods have an annotation when you create a custom filter.
EDIT_1 (2022/3/16)
I added some examples considering the comments below. The following CustomAnnotationTypeFilter
matches Baz
, Foo
, Fuga
and Hoge
.
public interface SomeInterface {
@MyAnnotationA
void doSomething();
}
public class Baz {
public SomeInterface doSomething() {
return null;
}
}
public class Foo implements SomeInterface {
@Override
public void doSomething() {
}
}
public class Fuga implements IFuga {
@Override
public SomeInterface doSomething() {
return null;
}
}
public class Hoge {
@MyAnnotationA String doSomething() {
return "";
}
}
public class CustomAnnotationTypeFilter extends AnnotationTypeFilter {
public CustomAnnotationTypeFilter(
Class<? extends Annotation> annotationType) {
super(annotationType);
}
@Override
protected boolean matchSelf(MetadataReader metadataReader) {
if (super.matchSelf(metadataReader)) {
return true;
}
String className = metadataReader.getClassMetadata().getClassName();
try {
Class<?> clazz = ClassUtils.forName(className, getClass().getClassLoader());
if (annotationExistsOnAMethod(clazz)) {
System.out.printf("1: matches %s\n", className);
return true; // matches 'Hoge.java' and 'Foo.java'
}
for (Method m : clazz.getDeclaredMethods()) {
if (annotationExistsOnAMethod(m.getReturnType())) {
System.out.printf("2: matches %s\n", className);
return true; // matches 'Baz.java' and 'Fuga.java'
}
}
} catch (ClassNotFoundException e) {
// ignore
}
return false;
}
private boolean annotationExistsOnAMethod(Class<?> clazz) {
for (Method m : clazz.getDeclaredMethods()) {
if (AnnotationUtils.findAnnotation(m, this.getAnnotationType()) != null) {
return true;
}
}
return false;
}
}
Scanning components to find annotations is a complex problem. It is not equivalent to / similar to searching with grep command.
You have to explicitly specify the location that the target annotation is placed on. For example, if methods can be annotated with the annotation, you have to check methods explicitly like AnnotationUtils.findAnnotation(method, this.getAnnotationType())
(as the example above shows). Also you may have to check the return types like the example above in a certain situation.
It is helpful to use the utility classes like org.springframework.core.annotation.AnnotationUtils
when searching for annotations. Because some methods of such utilities can find
annotations even when they are not directly present on the given target itself(See also its javadoc).
Upvotes: 2