Как Spring разрешает циклические зависимости?

Spring

Циклические зависимости — это циклические вложенные ссылки в классах N. Если мы используем новые объекты для создания таких циклических зависимостей в ежедневной разработке, программа будет вызываться циклически во время выполнения до тех пор, пока не будет сообщено об ошибке переполнения памяти. Давайте поговорим о том, как Spring решает эту проблему.

Прежде всего, должно быть ясно, что есть три ситуации, в которых Spring обрабатывает циклические зависимости: ① Циркулярная зависимость конструктора: этот вид зависимости Spring не может обрабатывать, и исключение BeanCurrentlylnCreationException вызывается напрямую. ②Установить циклическую зависимость в одноэлементном режиме: Циклическая зависимость обрабатывается «кэшем уровня 3». ③ Неодноэлементная циклическая зависимость: не может быть обработана.

Инициализация одноэлементного объекта spring примерно разделена на три этапа:

  1. createBeanInstance: создание экземпляра, по сути, заключается в вызове конструктора объекта для создания экземпляра объекта.
  2. populateBean: заполнение свойств, этот шаг в основном предназначен для заполнения свойств зависимостей нескольких bean-компонентов.
  3. 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 не Компонент с областью действия "прототип" сохраняется, поэтому создаваемый компонент не может быть раскрыт заранее.