解决 Spring 中 “not eligible for auto-proxying” 警告

1、概览

本文将带你了解出现 “not eligible for auto-proxying” 警告的原因以及如何修复它。

2、“not eligible for auto proxying” 的原因

2.1、配置示例

首先,创建一个自定义 RandomInt 注解,使用它来注解应该插入指定范围内的随机整数的字段。

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

    int max();
}

其次,创建一个简单的 Spring 组件 DataCache 类。将一个可能被使用的随机 Group 分配给缓存,例如用于支持分片。为了实现这一点,使用自定义的注解来注解该字段:

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

现在,来看看 RandomIntGenerator 类。它是一个 Spring 组件,用于在 RandomInt 注解注解的字段中插入随机 int 值:

@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;
    }
}

注意,DataCache 是通过构造函数注入到 RandomIntGenerator 中的。

最后,创建一个 RandomIntProcessor 类,它将负责查找注解为 RandomInt 的字段,并向其中插入随机值:

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;
    }
}

它使用 org.springframework.beans.factory.config.BeanPostProcessor 接口的实现,在类初始化之前访问注解字段。

2.2、测试示例

尽管编译一切正常,但当运行 Spring 应用并查看其日志时,会发现 Spring 的 BeanPostProcessorChecker 类生成了一条 “not eligible for auto proxying”的消息:

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)

此外,还发现依赖于该机制的 DataCache Bean 并没有按照预期进行初始化:

@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());
    }
}

尽管有警告日志,但是程序不会崩溃。

2.3、原因分析

该警告由 RandomIntProcessor 类及其自动装配的依赖引起。作为 ApplicationContext 特殊启动阶段的一部分,实现 BeanPostProcessor 接口的类会在启动时实例化,比其他 Bean 更早。

此外,AOP 自动代理机制也是 BeanPostProcessor 接口的实现。因此,BeanPostProcessor 实现和它们直接引用的 Bean 都不符合自动代理的条件。这意味着,Spring 使用 AOP 的功能(如自动装配、Security 或 Transactional 注解)将无法在这些类中正常工作。

在本例中,可以将 DataCache 实例自动装配到 RandomIntGenerator 类,不会出现任何问题。但是,group 字段没有填充随机整数。

3、修复这个问题

为了消除 “not eligible for auto proxying” 警告,需要打破 BeanPostProcessor 实现与其 Bean 依赖之间的循环。在本例中,需要告诉 IoC 容器以懒加载的方式初始化 RandomIntGenerator Bean。

可以使用 Spring 的 @Lazy 注解:

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 {
        //...
    }
}

RandomIntProcessorpostProcessBeforeInitialization 方法中请求 RandomIntGenerator Bean 时,Spring 就会初始化 RandomIntGenerator Bean。此时,Spring 的 IoC 容器会实例化所有符合自动代理条件的现有 Bean。

现在,运行应用,就不会在日志中看到 “not eligible for auto proxying” 警告。更重要的是,DataCache Bean 的 group 字段填充了随机整数:

@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、总结

本文介绍了 Spring 中出现 “not eligible for auto-proxying” 警告的原因,以及如何通过 @Lazy 注解以延迟初始化的方法来解决这个问题。


Ref:https://www.baeldung.com/spring-not-eligible-for-auto-proxying