Sunday, February 10, 2013

How to: scan interfaces in a custom Spring namespace

This article does not aim to address how to build a custom Spring namespace, to do so, Spring documentation integrates Extensible XML authoring. But a way to create a proxy based on a interface.
My first idea was to use Spring component-scan to find my annotated interfaces, and to proxy it via a BeanPostProcessor or something similar, but it cannot works because component-scan can only be used to load implementations and BeanPostProcessor as well.
As I'm using a custom namespace, so a parser, I made some research through Spring namespace elements and I found the attribute: base-package. And one of its associated class: ClassPathScanningCandidateComponentProvider. By extending this class, my annotated interfaces can be found, and the parser will be in charge to create the proxy around these interfaces.
The scanner implementation is quiet simple:

static class MyAnnotComponentProvider extends ClassPathScanningCandidateComponentProvider {

        public MyAnnotComponentProvider() {
            super(false);

            addIncludeFilter(new AnnotationTypeFilter(MyAnnot.class, false));
        }

        @Override
        protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
            return beanDefinition.getMetadata().isInterface();
        }
    }

  1. The constructor defines a scanner without any filter by calling the super constructor with false
  2. Add a filter on the annotation to found in the classpath by using AnnotationTypeFilter. The second argument specifies only the search must include meta annotation or not.
  3. Based on the BeanDefinition, isCandidateComponent must must check if the found bean is elligible
Next, use it as follow:

ClassPathScanningCandidateComponentProvider componentProvider = new MyAnnotComponentProvider();
componentProvider.setResourceLoader(parserContext.getReaderContext().getResourceLoader());

final String basePackage = element.getAttribute("base-package");
Set < BeanDefinition > beans = componentProvider.findCandidateComponents(basePackage);
  1. Instanciate the scanner, and give it a ResourceLoader. In a Spring XML parser, it can be reached by ParserContext
  2. Get the package to parse by, for example, get the value of the base-package attribute
  3. Call the method findCandidateComponents to get a Set of BeanDefinition.