Scanning for annotated classes with Spring

Java annotations can be used for many purposes, quite frequently to provide classes with extra information that is consumed at runtime. Thus it makes sense to have a facility capable to scan the classpath and return classes with a given annotation.

This task is harder than it might seem, so it's a good thing that Spring provides a facility for it: ClassPathScanningCandidateComponentProvider. I use it wrapped in another utility which provides some syntactic sugar:

                    package it.tidalwave.util.spring;

                    import java.lang.annotation.Annotation;
                    import javax.annotation.Nonnull;
                    import java.util.ArrayList;
                    import java.util.Collection;
                    import java.util.List;
                    import org.springframework.beans.factory.config.BeanDefinition;
                    import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
                    import org.springframework.core.type.filter.AnnotationTypeFilter;
                    import org.springframework.core.type.filter.TypeFilter;
                    import org.springframework.util.ClassUtils;

                    public class ClassScanner 
                      {
                        private final String basePackages = System.getProperty(ClassScanner.class.getCanonicalName()
                                                                             + ".basePackages", "com:org:it");
                        
                        private final ClassPathScanningCandidateComponentProvider scanner =
                                  new ClassPathScanningCandidateComponentProvider(false);

                        @Nonnull
                        public final Collection<Class<?>> findClasses() 
                          {
                            final List<Class<?>> classes = new ArrayList<Class<?>>();
                          
                            for (final String basePackage : basePackages.split(":"))
                              {
                                for (final BeanDefinition candidate : scanner.findCandidateComponents(basePackage)) 
                                  {
                                    classes.add(ClassUtils.resolveClassName(candidate.getBeanClassName(), 
                                                ClassUtils.getDefaultClassLoader()));
                                  }
                              }
                            
                            return classes;
                          }

                        @Nonnull
                        public ClassScanner withIncludeFilter (final @Nonnull TypeFilter filter)
                          {
                            scanner.addIncludeFilter(filter);
                            return this;
                          }
                        
                        @Nonnull
                        public ClassScanner withAnnotationFilter (final @Nonnull Class<? extends Annotation> annotationClass)
                          {
                            return withIncludeFilter(new AnnotationTypeFilter(annotationClass));
                          }
                      }
                    

It can be used with code such as:

                    final ClassScanner classScanner = new ClassScanner().withAnnotationFilter(MyAnnotation.class);

                    for (final Class<?> clazz : classScanner.findClasses())
                      {
                        final MyAnnotation annotation = clazz.getAnnotation(MyAnnotation.class);
                        // ...
                      }
                    

As a final remark, note that the scanning method of ClassPathScanningCandidateComponentProvider needs a basePackage: this is due to some quirks in the Java ClassLoader API that make it impossible to scan resources in the classpath without a folder. This is rather an annoyance because you can't know it in advance. My utility class searches for some common prefixes (com, org) plus the one I use for my code (it). But thanks to a system property this setting can be easily overridden.

Comments are managed by Disqus, which makes use of a few cookies. Please read their cookie policy for more details. If you agree to that policy, please click on the button below to accept it. If you don't, you can still enjoy this site without using Disqus cookies, but you won't be able to see and post comments. Thanks.