Введение
«Это первый день моего участия в ноябрьском испытании обновлений, ознакомьтесь с подробностями события:Вызов последнего обновления 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-компонентов друг в друга может привести к циклическим зависимостям.
Но я искал по всему миру @Aysnc в обоих bean-компонентах и тоже не нашел.
Потом тоже не видел сцены внедрения конструктора.
Итак, похоже, что вы можете настроить только исходный код.
3. Позиционирование ошибок
Увидев эту ошибку, перейдите непосредственно к строке ошибки, о которой сообщает стек.
Сообщения об ошибках относительно просты и интуитивны.Открытый объект не является тем же объектом, что и связанный с ним компонент, поэтому здесь срабатывает условная точка останова:
exposedObject != bean
Обнаружено, что bean-компонент является исходным объектом, а выставленный объект — прокси-объектом.
Одолжить фотографию брата А
Когда 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;
}
Точки останова здесь
можно увидеть две линии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;
}
дюжина точек останова
Проверяйте один за другим
обнаружил, что фасоль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
Общественный номер: дядя Бай Ян