Введение
«Это первый день моего участия в ноябрьском испытании обновлений, ознакомьтесь с подробностями события:Вызов последнего обновления 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
Общественный номер: дядя Бай Ян