Помните последнюю ноту, вbean
В процессе загрузки, в процессе создания происходит мониторинг цикла зависимости.Если эта зависимость цикла возникает и не разрешается, в коде будет сообщено об ошибке, а затемSpring
Ошибка инициализации контейнера.
Поскольку я чувствую, что круговая зависимость является относительно независимой точкой знаний, я напишу отдельную заметку для ее анализа,Давайте посмотрим, что такое циклическая зависимость и как ее исправить.
круговая зависимость
Циклическая зависимость — это циклическая ссылка, то есть две или болееbean
Прижмите друг друга друг к другу и, наконец, сформируйте кольцо. НапримерA
ЦитируетсяB
,B
ЦитируетсяC
,C
ЦитируетсяA
.
Это можно понять, обратившись к следующему рисунку (Взаимозависимость классов показана на рисунке, но вызов цикла относится к вызову цикла между методами, в следующем примере кода будет показан вызов цикла метода):
Если вы изучали базы данных, вы можете просто понимать круговые зависимости как взаимоблокировки, удерживать ресурсы друг друга, чтобы сформировать кольцо, а затем не освобождать ресурсы, что приводит к взаимоблокировкам.
При вызове цикла, если не возникнет терминальное условие, цикл будет бесконечным, что приведет к ошибке нехватки памяти. (Я также однажды столкнулся с OOM, который также был вызван бесконечным циклом.)
Пример в книге использует три класса для совершения кольцевых вызовов.Для простоты понимания и демонстрации я использую два класса для выполнения кольцевых вызовов:
существуетSpring
, циклические зависимости делятся на следующие три случая:
Циклическая зависимость конструктора
С помощью метода конфигурации, показанного на рисунке выше, он будет выбран во время инициализации.BeanCurrentlyInCreationException
аномальный
public static void main(String[] args) {
// 报错原因: Requested bean is currently in creation: Is there an unresolvable circular reference?
ApplicationContext context = new ClassPathXmlApplicationContext("circle/circle.xml");
}
Зная из предыдущей заметки,Spring
Контейнер будет создан для каждогоbean
Идентификатор помещается в «созданный в настоящее время пул компонентов (prototypesCurrentlyInCreation
)" середина,bean
Идентификаторы останутся в этом пуле во время создания.
Способы обнаружения циклических зависимостей:
Анализируя приведенный выше пример, в экземпляреcircleA
когда, ставь себяA
Поместить в пул из-за зависимостейcircleB
, поэтому для создания экземпляраcircleB
,B
Также помещается в пул из-за зависимостейA
, а затем хотите создать экземплярA
, нашел при созданииbean
В процессе я оказался в "созданном в данный моментbean
», поэтому он бросаетBeanCurrentlyInCreationException
аномальный.
Как показано на рисунке, этоЦиклические зависимости, введенные через конструкторы, не могут быть разрешены.
обработка зависимостей в области прототипа
prototype
Прототипы — это тип области, поэтому давайте сначала разберемся с областьюscope
Концепция чего-либо:
существуетSpring
В контейнере, в контейнере Spring, он ссылается на созданныйBean
объект относительно другихBean
Запрашиваемая дальность видимости объекта
Наиболее часто используется синглтонsingleton
ограниченныйbean
,Spring
В контейнере будет только один общий ресурсBean
экземпляр, так что мы получаем то же самое каждый разid
, будет возвращен только тот же самый экземпляр bean-компонента.
Есть два преимущества использования синглтона:
- Создать экземпляр заранее
bean
, который заранее выявляет проблемные проблемы с конфигурацией - будет
bean
Экземпляр помещается в кеш синглтонаsingletonFactories
, когда его нужно использовать снова, он напрямую извлекается из кеша, что повышает эффективность работы.
Синглтоны будут храниться в кэше синглтонов.singletonFactories
, который является областью действия Spring по умолчанию.
Прочитав область действия синглтона, давайте посмотримpropotype
Понятие объема: вSpring
вызвать прототипbean
, каждый раз возвращается новый объект, что эквивалентноnew Object()
.
так какSpring
Контейнер против области прототипаbean
не кешируется, поэтому заранее выставить творение невозможноbean
, поэтому в этом случае нет способа разрешить циклическую зависимость.
установка циклических зависимостей
заsetter
Зависимости, вызванные внедрением, могут быть переданы черезSpring
Контейнер заранее показывает, что он только что завершил внедрение конструктора, но не завершил другие шаги (такие какsetter
вводят) изbean
для завершения и может разрешать только одноэлементную областьbean
полагаться.
При загрузке класса основной методorg.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
, на этом шаге выполняется обработка контрольной суммы для циклических зависимостей.
Следуйте методу, чтобы узнать,bean
является синглтоном и допускает циклические зависимости, то вы можете заранее указать фабричный метод синглтона, чтобы другиеbean
Можно обратиться и, наконец, решить проблему циклических зависимостей.
Или в соответствии с двумя новыми классами выше,CircleA
иCircleB
, Давайте поговорим оsetter
Решение:
Конфигурация:
<!--注释 5.3 setter 方法注入-->
<bean id="circleA" class="base.circle.CircleA">
<property name="circleB" ref="circleB"/>
</bean>
<bean id="circleB" class="base.circle.CircleB">
<property name="circleA" ref="circleA"/>
</bean>
воплощать в жизньDemo
и вывод:
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("circle/circle.xml");
CircleA circleA = (CircleA) context.getBean("circleA");
circleA.a();
}
在 a 方法中,输出 A,在 b 方法中,输出B,下面是执行 demo 输出的结果:
错误提示是因为两个方法互相调用进行输出,然后打印到一定行数提示 main 函数栈溢出了=-=
A
B
A
B
*** java.lang.instrument ASSERTION FAILED ***: "!errorOutstanding" with message transform method call failed at JPLISAgent.c line: 844
*** java.lang.instrument ASSERTION FAILED ***: "!errorOutstanding" with message transform method call failed at JPLISAgent.c line: 844
*** java.lang.instrument ASSERTION FAILED ***: "!errorOutstanding" with message transform method call failed at JPLISAgent.c line: 844
Exception in thread "main" java.lang.StackOverflowError
можно увидеть сквозьsetter
Инъекция успешно решила проблему циклических зависимостей, как решить конкретный код для достижения, давайте проанализируем:
анализ кода
Чтобы лучше понять круговые зависимости, давайте сначала рассмотрим значение и использование этих трех переменных (также называемых кэшами, которые можно вызывать глобально):
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
Переменная | использовать |
---|---|
singletonObjects | для экономииBeanName и создатьbean отношения между экземплярами,bean-name --> instanct
|
singletonFactories | для экономииBeanName и создатьbean изфабрикаОтношения между,bean-name --> objectFactory
|
earlySingletonObjects | также сохранитьbeanName и создатьbean связь между экземплярами, сsingletonObjects изРазница в том,, когда синглтонbean После того, как его поместили сюда, то другиеbean В процессе создания вы можетеgetBean способ получения,Цель состоит в том, чтобы обнаружить циклические ссылки
|
Я говорил о механизме загрузки классов ранее, давайте найдем созданиеbean
Когда, где разрешается круговая зависимость:
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
// 是否需要提前曝光,用来解决循环依赖时使用
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
// 注释 5.2 解决循环依赖 第二个参数是回调接口,实现的功能是将切面动态织入 bean
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
// 判断 singletonObjects 不存在 beanName
if (!this.singletonObjects.containsKey(beanName)) {
// 注释 5.4 放入 beanName -> beanFactory,到时在 getSingleton() 获取单例时,可直接获取创建对应 bean 的工厂,解决循环依赖
this.singletonFactories.put(beanName, singletonFactory);
// 从提前曝光的缓存中移除,之前在 getSingleton() 放入的
this.earlySingletonObjects.remove(beanName);
// 往注册缓存中添加 beanName
this.registeredSingletons.add(beanName);
}
}
}
Первый взглядearlySingletonExposure
эта переменная:
буквально означаетСинглтоны, которые необходимо выставить заранее.
Есть три условия суждения:
mbd
Это синглтон- Разрешает ли контейнер циклические зависимости
- судить
bean
Создается ли.
Если все три условия соблюдены, выполнениеaddSingletonFactory
работать. Подумайте об этом, код, который вы пишете, полезен, поэтому давайте посмотрим, какую проблему решает эта операция и где она используется.
Решите сцену
создан в началеCircleA
иCircleB
Эти два класса с циклическими ссылками используются в качестве примеров:
A
класс содержит свойстваB
,B
класс содержит свойстваA
, эти два класса проходят следующие этапы при инициализации:
- Создайте
beanA
, сначала запишите соответствующийbeanName
потомbeanA
изСоздать фабрику beanFactoryAположить в кеш - правильно
beanA
Метод заполнения свойствpopulateBean
, проверьте зависимостиbeanB
, нет в кешеbeanB
Экземпляр или одноэлементный кеш, поэтому перейдите к созданию экземпляраbeanB
. - начать создание экземпляра
beanB
, проходя через созданиеbeanA
Процесс, к методу заполнения свойства, проверка зависимостиbeanA
. - перечислить
getBean(A)
метод в этой функции на самом деле не деинстанцируетсяbeanA
, но сначала проверьте, нет ли в кеше созданного соответствующегоbean
или уже созданbeanFactory
- обнаружен
beanFactoryA
был создан, но звоните напрямуюObjectFactory
создаватьbeanA
Комбинирование кодов клавиш для сортировки процесса
Создайте оригинальный бин
BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
// 原始 bean
final Object bean = instanceWrapper.getWrappedInstance();
На этом этапе исходныйbean
, так как последний шаг разбора атрибута не достигнут, в этом классе нет значения атрибута, вы можете представить его какnew ClassA
, и нет операций присваивания, таких как конструкторы, этот примитивbean
Информация будет использована на следующем шаге.
addSingleFactory
// 注释 5.2 解决循环依赖 第二个参数是回调接口,实现的功能是将切面动态织入 bean
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
Этот метод также упоминался ранее, он добавит синглтоны, которые должны быть выставлены заранее, в кеш, а синглтоны будут добавлены в кеш.beanName
иbeanFactory
Добавьте его в кеш и возьмите прямо из кеша, когда он понадобится позже.
populateBean заполняет свойство
Как я только что сказал в первом шаге, то, что было создано в начале, было лишь начальнымbean
, значения атрибута нет, поэтому на этом шаге анализируются атрибуты класса. Во время синтаксического анализа атрибута будет оцениваться тип атрибута, если он определен какRuntimeBeanReference
тип, ссылка будет разрешена.
Как и в примере, который мы написали,CircleA
ЦитируетсяCircleB
, при загрузкеCircleA
, нашелCircleB
Зависимость, поэтому необходимо загрузитьCircleB
.
Давайте посмотрим на конкретный процесс в коде:
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
...
if (pvs != null) {
// 将属性应用到 bean 中,使用深拷贝,将子类的属性一并拷贝
applyPropertyValues(beanName, mbd, bw, pvs);
}
}
protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) {
...
String propertyName = pv.getName();
Object originalValue = pv.getValue();
// 注释 5.5 解析参数,如果是引用对象,将会进行提前加载
Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
...
}
public Object resolveValueIfNecessary(Object argName, @Nullable Object value) {
// 我们必须检查每个值,看看它是否需要一个运行时引用,然后来解析另一个 bean
if (value instanceof RuntimeBeanReference) {
// 注释 5.6 在这一步中,如果判断是引用类型,需要解析引用,加载另一个 bean
RuntimeBeanReference ref = (RuntimeBeanReference) value;
return resolveReference(argName, ref);
}
...
}
Отслеживание здесь, процесс загрузки ссылок относительно ясен.Если обнаружится, что это ссылочный класс, он в конечном итоге будет делегированorg.springframework.beans.factory.support.BeanDefinitionValueResolver#resolveReference
Для эталонной обработки основные две строки кода выглядят следующим образом:
// 注释 5.7 在这里加载引用的 bean
bean = this.beanFactory.getBean(refName);
this.beanFactory.registerDependentBean(refName, this.beanName);
на этом этапеCircleB
, но в примере, который мы написали,CircleB
зависел отCircleA
, то как это обрабатывается, так что в это время мы простоCircleA
Информация, помещенная в кеш, делает свое дело.
getSingleton
Вы все еще помните, что вы узнали, загружая классы раньше? Режим синглтона извлекает один и тот же объект каждый раз, когда он загружается. Если он находится в кеше, его можно извлечь напрямую. Если его нет в кеше, он будет загружен , так что давайте ознакомимся с ним еще раз. Метод взятия единичного экземпляра:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
// 检查缓存中是否存在实例
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 记住,公共变量都需要加锁操作,避免多线程并发修改
synchronized (this.singletonObjects) {
// 如果此 bean 正在加载则不处理
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 当某些方法需要提前初始化,调用 addSingletonFactory 方法将对应的
// objectFactory 初始化策略存储在 earlySingletonObjects,并且从 singletonFactories 移除
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
Несмотря на то чтоCircleB
ЦитируетсяCircleA
, а в предыдущем способеaddSingletonFactory
час,CircleA
изbeanFactory
выставлены заранее.
такCircleB
Получение синглтонаgetSingleton()
может получитьCircleA
информация, поэтомуCircleB
Успешно загружено, добавьте свою информацию в кеш и реестр, а затем вернитесь, чтобы продолжить загрузкуCircleA
, так как его зависимости уже загружены в кеш, поэтомуCircleA
Он также может успешно завершить загрузку, и, наконец, вся операция загрузки завершена ~
В сочетании с блок-схемой решения сцены и процесса ключевого кода метод обработки циклической зависимости представлен относительно хорошо.Еще один ниже.debug
Блок-схема, надеюсь углубить ваше понимание ~
Суммировать
Цель написания этого резюме — заполнить дыру, потому что в предыдущей статье о разборе загрузки классов я лишь вкратце рассказал о концепции циклической зависимости, и я хочу заполнить дыру, оставшуюся в загрузке классов.
В процессе анализа циклических зависимостей обнаруживается, что объем предыдущейscope
Я не понял, поэтому я дополнил этот пункт знаний, а затем обнаружил, что не знаком с кешем и подробной обработкой, используемой в циклической зависимости, поэтому я проверил соответствующую информацию, отследил исходный код и проанализировал его шаг шаг за шагом, поэтому я обнаружил, что чем больше я писал, тем больше я писал.Решил путаницу и добавил несколько вопросов, поэтому в непрерывном исследовании и понимании я углубил пониманиеSpring
понимание.
Точно так же в работе мы часто сталкиваемся с кооперацией с другими командами, а также сталкиваемся с поддержкой новых интерфейсов, которые одновременно требуют друг друга.Например, вRPC
Если вы столкнулись с вызовом цикла в процессе, я предлагаю другое решение, например, разделение сообщений, чтобы избежать вызовов цикла. На самом деле нет никакого способа зациклить вызовы. Не забудьте добавить условие выхода в метод, чтобы избежать бесконечных циклов (> _
Из-за ограниченных личных технологий, если есть какие-либо недоразумения или ошибки, оставьте комментарий, и я исправлю его в соответствии с предложениями моих друзей.
Облако кода spring-analysis-note Адрес Gitee
spring-analysis-note Адрес Github
использованная литература
- Spring Learning (15) Введение в 5 областей действия Spring Beans
- Анализ исходного кода контейнера Spring IOC — решение циклических зависимостей
- Углубленный анализ исходного кода Spring», — Хао Цзя.
Портал:
-
Изучение исходного кода Spring (1) инфраструктуры контейнера
-
Изучение исходного кода Spring (2) анализ тегов по умолчанию
-
Функция расширения изучения исходного кода Spring (6), часть 1
-
Функция расширения изучения исходного кода Spring (семь), часть 2
-
Изучение исходного кода Spring (8) Принципы использования и реализации АОП
-
Изучение исходного кода Spring (9) Транзакционная транзакция