Spring Top

Get started with Spring 5 and Spring Boot 2, through the reference Learn Spring course:

>> LEARN SPRING

1. Overview

In this short tutorial, we'll see how to track down the cause of Spring's “not eligible for auto-proxying” message and how to fix it. 

First, we'll create a simple real-life code example that causes the message to appear during an application startup. Then, we'll explain the reason why this happens.

Finally, we'll present a solution to the problem by showing a working code example.

2. Cause of the “not eligible for auto proxying” Message

2.1. Example Configuration

Before we explain the cause of the message, let's build an example that causes the message to appear during the application startup.

First, we'll create a custom RandomInt annotation. We'll use it to annotate fields that should have a random integer from a specified range inserted into them:

@Retention(RetentionPolicy.RUNTIME)
public @interface RandomInt {
    int min();

    int max();
}

Second, let's create a DataCache class that is a simple Spring component. We want to assign to cache a random group that might be used, for example, to support sharding. To do that, we'll annotate that field with our custom annotation:

@Component
public class DataCache {
    @RandomInt(min = 2, max = 10)
    private int group;
    private String name;
}

Now, let's look at the RandomIntGenerator class. It's a Spring component that we'll use to insert random int values into fields annotated by the RandomInt annotation:

@Component
public class RandomIntGenerator {
    private Random random = new Random();
    private DataCache dataCache;

    public RandomIntGenerator(DataCache dataCache) {
        this.dataCache = dataCache;
    }

    public int generate(int min, int max) {
        return random.nextInt(max - min) + min;
    }
}

It's important to notice that we're autowiring the DataCache class into the RandomIntGenerator via constructor injection.

Finally, let's create a RandomIntProcessor class that will be responsible for finding fields annotated with the RandomInt annotation and inserting random values into them:

public class RandomIntProcessor implements BeanPostProcessor {
    private final RandomIntGenerator randomIntGenerator;

    public RandomIntProcessor(RandomIntGenerator randomIntGenerator) {
        this.randomIntGenerator = randomIntGenerator;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        Field[] fields = bean.getClass().getDeclaredFields();
        for (Field field : fields) {
            RandomInt injectRandomInt = field.getAnnotation(RandomInt.class);
            if (injectRandomInt != null) {
                int min = injectRandomInt.min();
                int max = injectRandomInt.max();
                int randomValue = randomIntGenerator.generate(min, max);
                field.setAccessible(true);
                ReflectionUtils.setField(field, bean, randomValue);
            }
        }
        return bean;
    }
}

It uses an implementation of the org.springframework.beans.factory.config.BeanPostProcessor interface to access annotated fields right before class initialization.

2.2. Testing Our Example

Even though everything compiles correctly, when we run our Spring application and watch its logs, we'll see a “not eligible for auto proxying” message generated by Spring's BeanPostProcessorChecker class:

INFO org.springframework.context.support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker - Bean 'randomIntGenerator' of type [com.baeldung.autoproxying.RandomIntGenerator] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)

What's more, we see that our DataCache bean that depends on this mechanism has not been initialized as we intended:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RandomIntProcessor.class, DataCache.class, RandomIntGenerator.class})
public class NotEligibleForAutoProxyingIntegrationTest {

    private RandomIntProcessor randomIntProcessor;

    @Autowired
    private DataCache dataCache;

    @Test
    public void givenAutowireInBeanPostProcessor_whenSpringContextInitialize_thenNotEligibleLogShouldShow() {
        assertEquals(0, dataCache.getGroup());
    }
}

However, it's worth mentioning that even though the message shows up, the application does not crash.

2.3. Analyzing the Cause

The warning is caused by the RandomIntProcessor class and its autowired dependencies. Classes that implement the BeanPostProcessor interface are instantiated on startup, as part of the special startup phase of the ApplicationContext, before any other beans.

Moreover, the AOP auto-proxying mechanism is also the implementation of a BeanPostProcessor interface. As a result, neither BeanPostProcessor implementations nor the beans they reference directly are eligible for auto-proxying. What that means is that Spring's features that use AOP, such as autowiring, security, or transactional annotations, won't work as expected in those classes.

In our case, we were able to autowire the DataCache instance into the RandomIntGenerator class without any problems. However, the group field was not populated with a random integer.

3. How to Fix the Error

In order to get rid of the “not eligible for auto proxying” message, we need to break the cycle between the BeanPostProcessor implementation and its bean dependencies. In our case, we need to tell the IoC container to initialize the RandomIntGenerator bean lazily. We can use Spring's Lazy annotation:

public class RandomIntProcessor implements BeanPostProcessor {
    private final RandomIntGenerator randomIntGenerator;

    @Lazy
    public RandomIntProcessor(RandomIntGenerator randomIntGenerator) {
        this.randomIntGenerator = randomIntGenerator;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        //...
    }
}

Spring initializes the RandomIntGenerator bean when the RandomIntProcessor requests it in the postProcessBeforeInitialization method. At that moment, Spring's IoC container instantiates all existing beans that are also eligible for auto-proxying.

In fact, if we run our application, we won't see a “not eligible for auto proxying” message in the logs. What's more, the DataCache bean will have a group field populated with a random integer:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RandomIntProcessor.class, DataCache.class, RandomIntGenerator.class})
public class NotEligibleForAutoProxyingIntegrationTest {

    private RandomIntProcessor randomIntProcessor;

    @Autowired
    private DataCache dataCache;

    @Test
    public void givenAutowireInBeanPostProcessor_whenSpringContextInitialize_thenGroupFieldShouldBePopulated() {
        assertNotEquals(0, dataCache.getGroup());
    }
}

4. Conclusion

In this article, we learned how to track down and fix the cause of Spring's “not eligible for auto-proxying” message. Lazy initialization breaks the cycle of dependencies during bean construction.

As always, the example code is available over on GitHub.

Spring bottom

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> THE COURSE
Generic footer banner
Comments are closed on this article!