Изучение исходного кода Spring (5) циклические зависимости

Spring

Засыпать яму, проанализировать круговые зависимости

Помните последнюю ноту, вbeanВ процессе загрузки, в процессе создания происходит мониторинг цикла зависимости.Если эта зависимость цикла возникает и не разрешается, в коде будет сообщено об ошибке, а затемSpringОшибка инициализации контейнера.

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


круговая зависимость

Циклическая зависимость — это циклическая ссылка, то есть две или болееbeanПрижмите друг друга друг к другу и, наконец, сформируйте кольцо. НапримерAЦитируетсяB,BЦитируетсяC,CЦитируетсяA.

Это можно понять, обратившись к следующему рисунку (Взаимозависимость классов показана на рисунке, но вызов цикла относится к вызову цикла между методами, в следующем примере кода будет показан вызов цикла метода):

circle_use

Если вы изучали базы данных, вы можете просто понимать круговые зависимости как взаимоблокировки, удерживать ресурсы друг друга, чтобы сформировать кольцо, а затем не освобождать ресурсы, что приводит к взаимоблокировкам.

При вызове цикла, если не возникнет терминальное условие, цикл будет бесконечным, что приведет к ошибке нехватки памяти. (Я также однажды столкнулся с OOM, который также был вызван бесконечным циклом.)


Пример в книге использует три класса для совершения кольцевых вызовов.Для простоты понимания и демонстрации я использую два класса для выполнения кольцевых вызовов:

существуетSpring, циклические зависимости делятся на следующие три случая:

Циклическая зависимость конструктора

circle_method

С помощью метода конфигурации, показанного на рисунке выше, он будет выбран во время инициализации.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-компонента.

Есть два преимущества использования синглтона:

  1. Создать экземпляр заранееbean, который заранее выявляет проблемные проблемы с конфигурацией
  2. будет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Эти два класса с циклическими ссылками используются в качестве примеров:

deal_with_circle_depend

Aкласс содержит свойстваB,Bкласс содержит свойстваA, эти два класса проходят следующие этапы при инициализации:

  1. СоздайтеbeanA, сначала запишите соответствующийbeanNameпотомbeanAизСоздать фабрику beanFactoryAположить в кеш
  2. правильно beanAМетод заполнения свойствpopulateBean, проверьте зависимостиbeanB, нет в кешеbeanBЭкземпляр или одноэлементный кеш, поэтому перейдите к созданию экземпляраbeanB.
  3. начать создание экземпляраbeanB, проходя через созданиеbeanAПроцесс, к методу заполнения свойства, проверка зависимостиbeanA.
  4. перечислитьgetBean(A)метод в этой функции на самом деле не деинстанцируетсяbeanA, но сначала проверьте, нет ли в кеше созданного соответствующегоbeanили уже созданbeanFactory
  5. обнаружен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


использованная литература

  1. Spring Learning (15) Введение в 5 областей действия Spring Beans
  2. Анализ исходного кода контейнера Spring IOC — решение циклических зависимостей
  3. Углубленный анализ исходного кода Spring», — Хао Цзя.

Портал: