Spring Circular Dependency — не только Baguwen

Spring Boot задняя часть

Введение

«Это первый день моего участия в ноябрьском испытании обновлений, ознакомьтесь с подробностями события:Вызов последнего обновления 2021 г."

всем привет.

Проблема циклической зависимости Spring часто встречается на собеседованиях. Я полагаю, что многие из моих друзей читали соответствующие сообщения в блогах о spring, использующем кеш L3 для решения циклических зависимостей. Когда интервьюер спрашивает вас, помимо понимания того, что вы знакомы с фреймворком Spring, он также хочет понять, что вы думаете о циклических зависимостях Spring.

Вы подходите и говорите, что spring использует кэш третьего уровня для решения циклической зависимости, тогда вам нужно идти домой и ждать уведомления.

Когда я написал требования несколько дней назад, когда я интегрировал несколько логик методов, я столкнулся с ошибкой циклической зависимости.

Благодаря этой идее исследования ошибок я расскажу вам о нескольких небольших ямах в круговой зависимости Spring.

В центре внимания этой статьи не интерпретация исходного кода циклических зависимостей Spring По умолчанию у вас есть простое понимание циклических зависимостей Spring.

Позвольте мне быть более интимным и опубликовать блог великого бога А:В этой статье рассказывается, как Spring использует «трехуровневый кеш» для умного решения проблемы циклической зависимости bean-компонентов.

2. Причина бага

Когда блогер разрабатывает требование, ему нужно вызвать логику нескольких существующих интерфейсов, но их исходные методы являются приватными, а несколько логик определены в слое контроллера.【历史原因,强烈谴责此种做法!】.

Для повторного использования методов я удаляю и синхронизирую соответствующую общую логику в контроллере с соответствующей службой, а также ввожу некоторые bean-компоненты связанных зависимостей.

Тогда структура кода становится такой: сервис А внедряет сервис Б, а сервис Б внедряет сервис А.

Затем при запуске проекта сообщается об ошибке

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'appManagerServiceImpl': Bean with name 'appManagerServiceImpl' has been injected into other beans [deviceManagerServiceImpl] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.
​
​
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:623)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:516)
  at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:324)
  at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
  at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322)
  at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
  at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1307)
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1227)
  at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:657)
  ... 26 common frames omitted

На самом деле, я был очень удивлен в то время, я был вНе смотрите подробный анализ абсолютного сожаления @Async [не такой простой, как исходный код]4.3 этой статьи упоминается, что при использовании @Async внедрение bean-компонентов друг в друга может привести к циклическим зависимостям.

image-20211108195622193.png

Но я искал по всему миру @Aysnc в обоих bean-компонентах и ​​тоже не нашел.

Потом тоже не видел сцены внедрения конструктора.

Итак, похоже, что вы можете настроить только исходный код.

3. Позиционирование ошибок

Увидев эту ошибку, перейдите непосредственно к строке ошибки, о которой сообщает стек.

image-20211108194749448.pngСообщения об ошибках относительно просты и интуитивны.Открытый объект не является тем же объектом, что и связанный с ним компонент, поэтому здесь срабатывает условная точка останова:

exposedObject != bean

image-20211108195849145.png

image-20211108200421743.png

Обнаружено, что bean-компонент является исходным объектом, а выставленный объект — прокси-объектом.

Одолжить фотографию брата А

2019061918335746.png

Когда Spring разрешает круговую зависимость, когда beanB переходит к получению beanA, если beanA обрабатывает аспект, то когда beanB связывается с beanA, он вызывает

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
   Object exposedObject = bean;
   if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
      for (BeanPostProcessor bp : getBeanPostProcessors()) {
         if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
            SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
            exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
         }
      }
   }
   return exposedObject;
}

Сгенерируйте прокси-объект, удалите beanA из кеша третьего уровня, сгенерируйте прокси-объект и поместите его в кеш второго уровня.

Но поскольку метод getEarlyBeanReference имеет только типSmartInstantiationAwareBeanPostProcessorпостпроцессор для обработки прокси. Если это другой типBeanPostProcessor, здесь не будет улучшено.

хорошо, давайте вернемся и посмотрим на приведенную выше блок-схему, окончательная логика загрузки бина находится в

exposedObject = initializeBean(beanName, exposedObject, mbd);

Эта строка, последняя логика для обработки bean-компонента

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
   if (System.getSecurityManager() != null) {
      AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
         invokeAwareMethods(beanName, bean);
         return null;
      }, getAccessControlContext());
   }
   else {
      invokeAwareMethods(beanName, bean);
   }
​
   Object wrappedBean = bean;
   //后置处理器前置处理
   if (mbd == null || !mbd.isSynthetic()) {
      wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
   }
​
   try {
      invokeInitMethods(beanName, wrappedBean, mbd);
   }
   catch (Throwable ex) {
      throw new BeanCreationException(
            (mbd != null ? mbd.getResourceDescription() : null),
            beanName, "Invocation of init method failed", ex);
   }
   //后置处理器后置处理
   if (mbd == null || !mbd.isSynthetic()) {
      wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
   }
​
   return wrappedBean;
}

Точки останова здесь

image-20211108202317887.png

можно увидеть две линииwrappedBeanОбъекты разные, один является исходным объектом, а другой является прокси-объектом.

Увидишь свет, зайди снова

@Override
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
      throws BeansException {
​
   Object result = existingBean;
   for (BeanPostProcessor processor : getBeanPostProcessors()) {
      Object current = processor.postProcessAfterInitialization(result, beanName);
      if (current == null) {
         return result;
      }
      result = current;
   }
   return result;
}

дюжина точек останова

image-20211108202940526.png

Проверяйте один за другим

image.png

обнаружил, что фасольMethodValidationPostProcessorУлучшенная обработка! ! ! !

Нажмите на этот класс, чтобы войти, он отмечен@ValidatedКласс будет усилен прокси.

4. Исправление ошибок

Ошибка решается очень просто, и обнаруживается, что она отмечена на сервисе.@Validated, чтобы проверить ввод метода.

Я был простым и грубым и написал метод для обработки логики проверки отдельно.

5. Резюме

На самом деле идея исследования в этой статье точно такая же, как и в статье @Async, но я добавил много размышлений в ходе расследования.

1. Сам Spring помогает нам решить круговую зависимость метода внедрения свойств. Но если циклически зависимый компонент удаляетсяSmartInstantiationAwareBeanPostProcessorЕсли постпроцессор проксирован, все равно будет генерироваться ошибка циклической зависимости.

2. Spring не может разрешить циклическую зависимость конструктора за нас, потому что начальная операция кеша третьего уровня заключается в помещении экземпляра бина в кеш третьего уровня.

3. Используйте@LazyРешить такую ​​​​ошибку можно, как в этой статье. Например, B хочет полагаться на конечный прокси-объект, поэтому B можно добавить, а A добавлять не нужно. Но на самом деле в этом случае B содержит ссылку на A и A в контейнере Spring.并不是同一个.[В глазах этого пациента с обсессивно-компульсивным расстройством нет лекарства от симптомов, но не от первопричины]

4. На самом деле кеш второго уровня тоже может решить внедрение циклических зависимостей, но зачем использовать кеш третьего уровня? Spring по-прежнему ожидает, что цикл объявления bean-компонентов будет соответствовать спецификациям дизайна Spring, аналогично тому, как прокси-серверы генерируются заранее при раннем раскрытии кэша второго уровня ради надежности системы.

5. Используйте с осторожностью:allowRawInjectionDespiteWrapping, После установки значения true компонент в цикле не будет проверен, но прокси-сервер будет недействительным.

6. Свяжитесь со мной

Если есть неточности в тексте, поправьте меня, текст писать непросто, ставьте лайк, ладно~

Диндин: louyanfeng25

WeChat: baiyan_lou

Общественный номер: дядя Бай Ян

image.png