Циклические зависимости — это циклические вложенные ссылки в классах N. Если мы используем новые объекты для создания таких циклических зависимостей в ежедневной разработке, программа будет вызываться циклически во время выполнения до тех пор, пока не будет сообщено об ошибке переполнения памяти. Давайте поговорим о том, как Spring решает эту проблему.
Прежде всего, должно быть ясно, что есть три ситуации, в которых Spring обрабатывает циклические зависимости: ① Циркулярная зависимость конструктора: этот вид зависимости Spring не может обрабатывать, и исключение BeanCurrentlylnCreationException вызывается напрямую. ②Установить циклическую зависимость в одноэлементном режиме: Циклическая зависимость обрабатывается «кэшем уровня 3». ③ Неодноэлементная циклическая зависимость: не может быть обработана.
Инициализация одноэлементного объекта spring примерно разделена на три этапа:
- createBeanInstance: создание экземпляра, по сути, заключается в вызове конструктора объекта для создания экземпляра объекта.
- populateBean: заполнение свойств, этот шаг в основном предназначен для заполнения свойств зависимостей нескольких bean-компонентов.
- InitializeBean: вызовите метод инициализации в spring xml.
Из шагов инициализации одноэлементного компонента, описанных выше, мы можем знать, что циклические зависимости в основном возникают на первом и втором шагах. То есть круговые зависимости конструктора и круговые зависимости поля. Далее давайте посмотрим, как Spring обрабатывает три циклические зависимости.
1. Круговая зависимость конструктора
этот .singletonsCurrentlylnCreation.add(beanName) записывает bean-компонент, который в данный момент создается в кеше Контейнер Spring помещает каждый создаваемый идентификатор компонента в «текущий созданный пул компонентов», идентификатор компонента Босс: Он останется в этом пуле при создании, так что если вы окажетесь в "текущем" при создании бина При создании пула компонентов будет выброшено исключение BeanCurrentlylnCreationException, указывающее на циклическую зависимость; Готовые компоненты будут удалены из «созданного в настоящее время пула компонентов».
2, установка круговой зависимости
Чтобы решить проблему циклической зависимости синглтона, Spring использует кеш третьего уровня.
/** Cache of singleton objects: bean name –> bean instance */
private final Map singletonObjects = new ConcurrentHashMap(256);
/** Cache of singleton factories: bean name –> ObjectFactory */
private final Map> singletonFactories = new HashMap>(16);
/** Cache of early singleton objects: bean name –> bean instance */
private final Map earlySingletonObjects = new HashMap(16);
Функции трехуровневого кэша:
singletonFactories: кэш фабрики одноэлементных объектов, вступающий в фазу создания экземпляра (кэш уровня 3).
EarlySingletonObjects : Кэш (кэш второго уровня) одноэлементных объектов, которые были созданы, но еще не инициализированы и не выставлены заранее.
singletonObjects: Кэш одноэлементного объекта, завершившего инициализацию (кэш первого уровня).
Когда мы создаем bean-компонент, мы сначала получаем bean-компонент из кеша, то есть sigletonObjects. Основной метод вызова:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
//isSingletonCurrentlyInCreation()判断当前单例bean是否正在创建中
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
//allowEarlyReference 是否允许从singletonFactories中通过getObject拿到对象
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
//从singletonFactories中移除,并放入earlySingletonObjects中。
//其实也就是从三级缓存移动到了二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
Из приведенного выше анализа трехуровневого кеша мы можем узнать, что хитрость Spring для решения циклических зависимостей заключается в трехуровневом кеше singletonFactories. Тип этого кеша — ObjectFactory, который определяется следующим образом:
public interface ObjectFactory<T> {
T getObject() throws BeansException;
}
Этот интерфейс реализован в AbstractBeanFactory и ссылается на следующие методы в основном методе doCreateBean():
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
Этот код появляется после createBeanInstance и перед populateBean(), что означает, что в это время был создан одноэлементный объект (вызван конструктор). Этот объект был создан, и в настоящее время он заранее выставлен на всеобщее обозрение для использования.
Какая польза от этого? Давайте проанализируем ситуацию циклической зависимости «поле или установщик A зависит от экземпляра объекта B, а поле или установщик B зависит от экземпляра объекта A». Первый завершил первый шаг инициализации и заранее представил себя singletonFactory.В это время второй шаг инициализации и обнаружил, что он зависит от объекта B. В это время он попытался получить(B) и обнаружил что B не был создан. , поэтому в процессе создания B обнаружил, что зависит от объекта A при инициализации первого шага, поэтому он попытался получить (A), попробовал кэш первого уровня singletonObjects (конечно, нет, потому что A не была полностью инициализирована) и попробовал кэш второго уровня EarlySingletonObjects (Нет), попробуйте трехуровневый кэш singletonFactory, так как A заранее раскрывает себя через ObjectFactory, B может получить объект A через ObjectFactory.getObject (хотя A не был полностью инициализирован, но это лучше, чем ничего), B принимает После достижения объекта A успешно завершены этапы инициализации 1, 2 и 3. После завершения инициализации он поместит себя в кэш первого уровня singletonObjects. В это время, возвращаясь к A, A может получить объект B и успешно завершить этапы 2 и 3 своей инициализации. Наконец, A также завершает инициализацию и входит в кеш первого уровня singletonObjects, и даже более удачлив, потому что B получил объект A ссылка, поэтому объект A, который теперь содержит B, инициализируется.
3. Неодноэлементные циклические зависимости
Для bean-компонентов с областью действия «прототип» контейнер Spring не может выполнить внедрение зависимостей, поскольку контейнер Spring не Компонент с областью действия "прототип" сохраняется, поэтому создаваемый компонент не может быть раскрыт заранее.