Custom scanners in Spring Framework with example

With the 2.5 version of Spring Framework [1] we don’t need to implicitly declare beans in application context’s xml. Simply put, adding this entry to xml

<context:component-scan base-package="org.example.package" />

will instruct Spring to scan specified base package for beans that have special annotations. It could be @Component, @Controller, etc…

But what if we need to declare our own custom scanner, that searches for specific type of classes and initialize them as a beans and apply additional logic. Spring comes in very handy.

Let’s consider following example. We’re developing an application that need to have cron style functionality among others. Why would we develop something from the scratch when we can use Quartz [2] library. Unfortunately simple bean initialization provided by Spring is not enough to achieve easy use. 

First we need to create scanner. We can base our own scanner on the Spring classes. Best candidat for that is ClassPathScanningCandidateComponentProvider class. To be able to scan after beans initialization we need to implement BeanFactoryPostProcessor interface. In our example class signature looks like this:

@Component
public class CronTasksScanner
    extends ClassPathScanningCandidateComponentProvider
    implements BeanFactoryPostProcessor

BeanFactoryPostProcessor interface force us to implement postProcessBeanFactory method which will be invoked after beans initialization. In this method we will start scanning provided package.

for (String basePackage : basePackages)
{
    Set candidates = findCandidateComponents(basePackage);
    for (BeanDefinition candidate : candidates)
    {
        scanBean(candidate);
    }
}

Method findCandidateComponents is provided by ClassPathScanningCandidateComponentProvider class, it finds classes that matches special filter. This filter by default finds only Spring’s annotations. We need to include filter that will searches for our annotation. We can also suppress searching default annotation by simply calling super interface with useDefaultFilters parameter set to false. Here is our constructor:

public CronTasksScanner()
{
    this(false);
    addIncludeFilter(new AnnotationTypeFilter(CronTask.class));
}

CronTask is our custom annotation that can accept string value.

scanBean method is responsible for logic behind managing classes that matches our filter. In our example we will start from resolving class name (taking into account class loader), create spring bean from provided class name and add created task to Quartz.

private void scanBean(BeanDefinition bean)
{
    Class<?> clazz = ClassUtils.resolveClassName(bean.getBeanClassName(), ClassUtils.getDefaultClassLoader());

    Object cronJob = getBeanFromClass(clazz, clazz);
    String cronTime = cronJob.getClass().getAnnotation(CronTask.class).value();
    String taskDetailName = firstLetterToLowerCase(cronJob.getClass().getSimpleName());

    …
}

Most important part of this method is invocation to getBeanFromClass method. It’s based on internal parts of the Spring but since I haven’t found a simple solution to initialize bean from code I wrote my own method.

private <T> getBeanFromClass(Class<?> beanClass, Class<T> returnType)
{
    T result = null;
    String beanName = "";

    try
    {
        Class<?> clazz = Class.forName(beanClass.getName());

        for (String b : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(factory, clazz))
        {
            beanName = b;
        }

        if (beanName.equals(""))
        {
            beanName = firstLetterToLowerCase(clazz.getSimpleName());

            XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader((DefaultListableBeanFactory) factory);

            RootBeanDefinition beanDefinition = new RootBeanDefinition(clazz);
            beanDefinition.applyDefaults(new BeanDefinitionDefaults());
            beanDefinition.setSingleton(true);

            BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry();
            registry.registerBeanDefinition(beanName, beanDefinition);
        }

        result = (T) factory.getBean(beanName);
    }
    catch (Exception e)
    {
        throw new IllegalStateException(e.getMessage(), e);
    }

    return (T) result;
}

I found that if we want to create a bean from a factory, result type need to be different than bean (factory) class itself, hence two parameters.

In the next article I will try to fill gaps related to handling quartz jobs with spring beans.

1. http://www.springsource.org/
2. https://quartz.dev.java.net/

3 Responses to “Custom scanners in Spring Framework with example”

  1. Thomas said:

    Jan 08, 09 at 14:57

    Hi,

    Thanks for your post, that’s exactly what I need.
    I have just a question about the “basePackages” variable, where does it come from ? I didn’t manage to find where..?

    Thanks a lot

    Thomas

  2. Thomas said:

    Jan 08, 09 at 19:33

    Also, how I declare this new scanner in the application context file ?
    Thanks in advance

  3. Łukasz Milewski said:

    Jan 08, 09 at 21:54

    There’s no way (at least right now) to access base-package variable from context:component-scan. You have two options, either specify base package in source code or in bean definition. In my projects, I’m doing the first version with slight modification - in source code I have specified default base package that can be overwritten by using setBasePackage(String).

    As for 2nd question, if you’re using context:component-scan you don’t need to do anything except adding @Component to the class that implement custom scanner.

    Thanks for reading ;-)


Leave a Reply