Весенние высокочастотные вопросы на собеседовании: как решить проблему циклической зависимости

Java

Панорама проблемы круговой зависимости

Что такое проблема циклической зависимости?

Зависимости между классами образуют замкнутый цикл, который приводит к циклическим зависимостям.

Например, на следующем рисунке класс A зависит от класса B, класс B зависит от класса C и, наконец, класс C зависит от класса A, что создает проблему циклической зависимости.

Анализ случая проблемы циклической зависимости

  • Демонстрационный код:
public class ClassA {
	private ClassB classB;

	public ClassB getClassB() {
		return classB;
	}

	public void setClassB(ClassB classB) {
		this.classB = classB;
	}
}
public class ClassB {
	private ClassA classA;

	public ClassA getClassA() {
		return classA;
	}

	public void setClassA(ClassA classA) {
		this.classA = classA;
	}
}
  • Конфигурационный файл:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="classA" class="ioc.cd.ClassA">
		<property name="classB" ref="classB"></property>
	</bean>
	<bean id="classB" class="ioc.cd.ClassB">
		<property name="classA" ref="classA"></property>
	</bean>
</beans>
  • Тестовый код:
	@Test
	public void test() throws Exception {
		// 创建IoC容器,并进行初始化
		String resource = "spring/spring-ioc-circular-dependency.xml";
		ApplicationContext context = new ClassPathXmlApplicationContext(resource);
		// 获取ClassA的实例(此时会发生循环依赖)
		ClassA classA = (ClassA) context.getBean(ClassA.class);
	}
  • Проанализируйте проблему циклической зависимости с помощью исходного кода процесса Spring IOC:

Сколько видов проблем с циклической зависимостью существует в приведенном выше случае?

В Spring есть три основных случая проблем с циклическими зависимостями:

  • Проблема циклической зависимости, вызванная внедрением зависимостей через конструктор.
  • Проблемы, зависящие от Refolio, в нескольких (прототипных) режимах выполняются с помощью метода установки.
  • Внедрение зависимостей через метод setter — это циклическая проблема зависимости, созданная в одноэлементном режиме.

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

На самом деле это довольно легко объяснить:

  • В случае внедрения первого метода построения он будет заблокирован при создании нового объекта, по сути, это историческая проблема «курицы или яйца».
  • В случае второго метода установки && несколько экземпляров новый Bean будет генерироваться каждый раз, когда вы getBean(), и если это повторяется, будут генерироваться бесконечные bean-компоненты, что в конечном итоге приведет к проблеме OOM.

Как решить проблему циклической зависимости?

Так как же Spring решает проблему циклической зависимости, вызванную внедрением зависимостей методов установки? Пожалуйста, посмотрите на следующий рисунок (на самом деле, это в основном решается двумя кэшами):

Введение в три основных кэша Spring

В Spring существует три кеша для хранения экземпляров одноэлементных компонентов Эти три кеша являются взаимоисключающими и не будут храниться для одного и того же экземпляра компонента в одно и то же время.

Если вы вызываете getBean, вам необходимо по очереди получить указанный экземпляр Bean из трех кэшей. Порядок чтения: кеш первого уровня --> кеш второго уровня --> кеш третьего уровня.

Кэш уровня 1: Map singletonObjects

Какова роль кэша первого уровня?

  • Используется для хранения экземпляров Bean, созданных в одноэлементном режиме (уже созданных).
  • Кэш предназначен для внешнего использования, что относится к программистам, использующим среду Spring.

Какие данные хранятся?

  • K: название боба
  • V: объект-экземпляр компонента (прокси-объект относится к прокси-объекту, который был создан)

Кэш второго уровня: Map EarlySingletonObjects

Какова роль кэша второго уровня?

  • Он используется для хранения экземпляра Bean, созданного в одноэлементном режиме (Bean выставляется заранее, и Bean все еще создается).
  • Кэш используется внутри, что означает, что внутренняя логика среды Spring использует кеш.
  • Чтобы решить проблему того, как первая ссылка classA в конечном итоге заменяется прокси-объектом (если есть прокси-объект), поднимитесь по лестнице и обратитесь к демонстрационному случаю.

Какие данные хранятся?

  • K: название боба
  • V: объект-экземпляр компонента (прокси-объект относится к прокси-объекту, и компонент все еще создается)

Кэш третьего уровня: Map> singletonFactories

Какова роль кэша третьего уровня?

  • Используйте объект ObjectFactory для хранения ссылки (в процессе создания) экземпляра Bean, выставленного заранее в одноэлементном режиме.
  • Кэш используется внутри, что означает, что внутренняя логика среды Spring использует кеш.
  • Этот кеш вносит наибольший вклад в решение циклических зависимостей.

Какие данные хранятся?

  • K: название боба
  • V: ObjectFactory, который содержит ссылку на предварительно предоставленный bean-компонент.

Почему кеш третьего уровня должен использовать ObjectFactory? Прокси-объекты должны быть сгенерированы заранее.

Когда ссылка на bean-компонент, доступный для ObjectFactory, заранее хранится в кэше третьего уровня? Время сразу после первого шага создания экземпляра и до второго шага внедрения зависимостей.

Суммировать

Вышеупомянутое является ключевым моментом для Spring для решения циклических зависимостей! Подводя итог, необходимо понимать следующие моменты:

  • Понимаете роль кэша третьего уровня Spring?
  • Выяснить роль ObjectFactory в кеше третьего уровня?
  • Выясните, зачем вам нужен кеш второго уровня?
  • Выясните, когда использовать кеш L3 (операции добавления и запроса)?
  • Выясните, когда использовать кеш второго уровня (операции добавления и запроса)?
  • Когда целевой объект генерирует прокси-объект, кто сохраняется в контейнере Spring (кэш первого уровня)?