Панорама проблемы круговой зависимости
Что такое проблема циклической зависимости?
Зависимости между классами образуют замкнутый цикл, который приводит к циклическим зависимостям.
Например, на следующем рисунке класс 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 (кэш первого уровня)?