Рубрики
Без рубрики

Решение предупреждения Spring “не подходит для автоматического проксирования”

Автор оригинала: Maciej Główka.

1. Обзор

В этом коротком уроке мы увидим, как отследить причину сообщения Spring ” не подходит для автоматического проксирования ” и как это исправить.

Во-первых, мы создадим простой пример кода в реальной жизни, который приведет к появлению сообщения во время запуска приложения. Затем мы объясним причину, по которой это происходит.

Наконец, мы представим решение проблемы, показав пример рабочего кода.

2. Причина сообщения “не подходит для автоматического проксирования”

2.1. Пример конфигурации

Прежде чем мы объясним причину появления сообщения, давайте построим пример, который приводит к появлению сообщения во время запуска приложения.

Сначала мы создадим пользовательскую аннотацию Random Int . Мы будем использовать его для аннотирования полей, в которые должно быть вставлено случайное целое число из указанного диапазона:

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

    int max();
}

Во-вторых, давайте создадим класс DataCache , который является простым компонентом Spring. Мы хотим назначить для кэша случайную группу, которая может использоваться, например, для поддержки сегментирования. Для этого мы снабдим это поле нашей пользовательской аннотацией:

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

Теперь давайте посмотрим на класс RandomIntGenerator . Это компонент Spring, который мы будем использовать для вставки случайных значений int в поля, аннотированные аннотацией Random 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 через инъекцию конструктора .

Наконец, давайте создадим класс randomInt Processor , который будет отвечать за поиск полей, аннотированных аннотацией 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 :

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)

Более того, мы видим, что наш Кэш данных компонент, который зависит от этого механизма, не был инициализирован, как мы предполагали:

@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. Анализ причины

Предупреждение вызвано классом randomInt Processor и его автоматическими зависимостями. Классы, реализующие BeanPostProcessor interface , создаются при запуске, как часть специальной фазы запуска ApplicationContext, перед любыми другими компонентами.

Кроме того, механизм автоматического проксирования AOP также является реализацией BeanPostProcessor interface . В результате ни реализации BeanPostProcessor , ни бобы, на которые они ссылаются напрямую, не имеют права на автоматическое проксирование. Это означает, что функции Spring, использующие AOP, такие как автоматическая проводка, безопасность или аннотации транзакций, не будут работать так, как ожидалось в этих классах.

В нашем случае мы смогли автоматически подключить экземпляр Data Cache в класс RandomIntGenerator без каких-либо проблем . Однако , поле группы не было заполнено случайным целым числом.

3. Как исправить ошибку

Чтобы избавиться от сообщения ” не подходит для автоматического проксирования” , нам нужно разорвать цикл между реализацией BeanPostProcessor и его зависимостями от компонентов . В нашем случае нам нужно сказать контейнеру 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 {
        //...
    }
}

Spring инициализирует компонент RandomIntGenerator , когда процессор randomInt запрашивает его в методе postProcessBeforeInitialization . В этот момент контейнер IoC Spring создает экземпляры всех существующих компонентов, которые также имеют право на автоматическое проксирование.

На самом деле, если мы запустим наше приложение, мы не увидим в журналах сообщение ” не подходит для автоматического проксирования” . Более того, Кэш данных 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_thenGroupFieldShouldBePopulated() {
        assertNotEquals(0, dataCache.getGroup());
    }
}

4. Заключение

В этой статье мы узнали, как отследить и устранить причину сообщения Spring “не подходит для автоматического проксирования” . Ленивая инициализация прерывает цикл зависимостей во время построения компонента.

Как всегда, пример кода доступен на GitHub .