Как вы думаете, проблема циклической зависимости Spring на плохой улице?

Spring
Как вы думаете, проблема циклической зависимости Spring на плохой улице?

Статья размещена на GitHubJavaKeeper, N-line развитие Интернета, необходимые навыки и оружие для интервью и конспектирования.

Поиск в WeChat »JavaKeeper«Программист выращивает зарядную станцию, поле для боевых искусств с использованием интернет-технологий. Получите более 500 электронных книг и более 30 видеоуроков и исходный код без каких-либо процедур.

предисловие

Проблема круговой зависимости — плохой вопрос для собеседования.Перед детоксикацией давайте рассмотрим два важных момента:

Когда мы впервые изучали Spring, мы знали об IOC, инверсии управления, которая будет передавать управление объектами, которые изначально были созданы вручную в программе, в структуру Spring, не требуя от нас ручного перехода к различнымnew XXX.

Хотя им управляет Spring, вам не нужно создавать объекты?Существует много шагов для создания объектов Java, вы можетеnew XXX,Сериализация,clone()Подождите, просто Spring создает объекты через рефлексию + фабрики и помещает их в контейнер.После создания объектов мы обычно присваиваем значения свойствам объекта перед их использованием.Можно понять, что есть два шага.

Ну, просто подумайте об этих двух шагах, затем мы входим в круговую зависимость, сначала поговорим о концепции круговой зависимости.

что такое циклическая зависимость

Так называемая круговая зависимость означает, что А зависит от В, а В зависит от А, образуя между собой круговую зависимость. Или А зависит от В, В зависит от С, а С зависит от А, образуя круговую зависимость. Или больше зависит от себя. Зависимости между ними следующие:

Вот пример двух классов, которые напрямую зависят друг от друга, коды их реализации могут быть такими:

public class BeanB {
    private BeanA beanA;
    public void setBeanA(BeanA beanA) {
		this.beanA = beanA;
	}
}

public class BeanA {
    private BeanB beanB;
    public void setBeanB(BeanB beanB) {
        this.beanB = beanB;
	}
}

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

<bean id="beanA" class="priv.starfish.BeanA">
  <property name="beanB" ref="beanB"/>
</bean>

<bean id="beanB" class="priv.starfish.BeanB">
  <property name="beanA" ref="beanA"/>
</bean>

После запуска Spring, прочитав приведенный выше файл конфигурации, он сначала создаст экземпляр A по порядку, но когда он будет создан, обнаружится, что он зависит от B, а затем он создаст экземпляр B, а также обнаружит, что он зависит от A. бесконечный цикл

Spring "точно" этого не допустит. Как мы говорили в предисловии, Spring инстанцирует объекты в два шага. Первый шаг - это создание объекта-примитива, но без задания свойств его можно понимать как "полуфабрикат" - официальный Он называется EarlyBeanReference объекта A, поэтому, когда создается экземпляр B и выясняется, что он зависит от A, B установит этот «полуфабрикат» для завершения создания экземпляра первым, поскольку B завершил создание экземпляра, поэтому A может получить B Ссылка также была создана, что на самом деле является идеей Spring по решению циклических зависимостей.

有点懵逼

Не беда, если вы не понимаете, сначала составьте общее впечатление, а потом посмотрим, как Spring решает это из исходников.

Детоксикация исходного кода

Версия кода: 5.0.16.RELEASE

Прежде чем контейнер Spring IOC прочитает конфигурацию компонента для создания экземпляра компонента, он должен быть создан. Только после создания экземпляра контейнера экземпляр компонента можно получить из контейнера IOC и использовать.Проблема циклической зависимости возникает во время создания экземпляра компонента, поэтому давайте сначала рассмотрим процесс получения компонента.

Получить процесс бина

Упрощенная версия процесса получения экземпляра бина в контейнере Spring IOC выглядит следующим образом (исключая различные процессы упаковки и проверки)

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

  1. процесс изgetBeanметод запускается,getBeanэто метод пустой оболочки, вся логика идет непосредственно кdoGetBeanметод
  2. transformedBeanNameПреобразуйте имя в настоящее beanName (имя может быть в том случае, когда FactoryBean начинается с символа & или имеет псевдоним, поэтому его необходимо преобразовать)
  3. затем пройтиgetSingleton(beanName)Метод пытается найти из кеша, существует ли sharedInstance экземпляра (один экземпляр в том же контейнере Spring будет создан только один раз, а затем для получения бина его можно получить прямо из кеша)
  4. Если есть, sharedInstance может быть полностью созданным bean-компонентом или необработанным bean-компонентом, поэтому послеgetObjectForBeanInstanceпроцесс возврата
  5. Конечно, sharedInstance также может быть нулевым, тогда будет выполнена логика создания бина и будет возвращен результат

На третьем шаге мы упомянули концепцию кэширования, разработанную Spring для решения проблемы циклической зависимости синглетонов.Кэш L3

/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

Функции трехуровневого кэша:

  • singletonObjects: Кэш одноэлементного объекта, завершившего инициализацию, бин здесь испытал实例化->属性填充->初始化и различная постобработка (кеш 1-го уровня)

  • earlySingletonObjects: содержит исходный объект компонента (Создание экземпляра завершено, но еще не заполнено и не инициализировано), который может быть представлен только заранее как указатель, на который ссылаются другие bean-компоненты, используемый для разрешения циклических зависимостей (кэш второго уровня).

  • singletonFactories: После создания экземпляра компонента и до того, как свойства будут заполнены и инициализированы, если разрешена ранняя экспозиция, Spring заранее выставит созданный экземпляр компонента, то есть преобразует компонент вbeanFactoryи добавлено вsingletonFactories(кеш L3)

Сначала мы пытаемся получить бин из кеша, который ищется из этого трехуровневого кеша.

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 从 singletonObjects 获取实例,singletonObjects 中的实例都是准备好的 bean 实例,可以直接使用
    Object singletonObject = this.singletonObjects.get(beanName);
    //isSingletonCurrentlyInCreation() 判断当前单例bean是否正在创建中
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            // 一级缓存没有,就去二级缓存找
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                // 二级缓存也没有,就去三级缓存找
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    // 三级缓存有的话,就把他移动到二级缓存,.getObject() 后续会讲到
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

Если кеша нет, создадим его, далее возьмем в качестве примера объект singleton, а затем посмотрим на логику создания бина (фигурные скобки означают, что внутренний класс вызывает метод):

  1. Создание bean-компонента начинается со следующего кода, анонимного параметра метода внутреннего класса (я всегда чувствую, что метод Lambda не так читаем, как внутренний класс)

    if (mbd.isSingleton()) {
        sharedInstance = getSingleton(beanName, () -> {
            try {
                return createBean(beanName, mbd, args);
            }
            catch (BeansException ex) {
                destroySingleton(beanName);
                throw ex;
            }
        });
        bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
    }
    

    getSingleton()Внутри метода есть два основных метода

    public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
        // 创建 singletonObject
    	singletonObject = singletonFactory.getObject();
        // 将 singletonObject 放入缓存
        addSingleton(beanName, singletonObject);
    }
    
  2. getObject()Реализация анонимного внутреннего класса действительно вызывает сноваcreateBean(beanName, mbd, args)

  3. Зайдя внутрь, основная логика реализации находится вdoCreateBeanметод, сначала черезcreateBeanInstanceСоздайте необработанный объект bean

  4. тогдаaddSingletonFactoryДобавить объект bean factory в кеш singletonFactories (кеш уровня 3)

  5. пройти черезpopulateBeanМетод заполняет свойства исходного объекта компонента и анализирует зависимости.Предположим, что зависимость B найдена, когда свойства заполнены после создания A, а затем зависимость A найдена, когда создан объект зависимости B. Это тот же процесс, а затем перейти кgetBean(A), в это время в кеше третьего уровня уже есть "полуфабрикат" bean A. В это время исходную ссылку объекта A можно внедрить в объект B (и переместить в кеш второго уровня) решить проблему циклической зависимости. В этот моментgetObject()Метод возвращает полностью созданный экземпляр компонента, даже если выполнение завершено.

  6. последний звонокaddSingletonПоместите полностью созданный объект bean-компонента в кеш singletonObjects (кэш первого уровня) и назовите его днем.

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

Рекомендуется посмотреть на логическую схему ниже с "исходным кодом" для лучшей еды

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

  1. Spring создает bean-компонент в основном в два этапа: создание исходного объекта bean-компонента, затем заполнение свойств объекта и инициализация.
  2. Перед созданием каждого бина мы будем проверять, есть ли бин в кеше, потому что это синглтон, может быть только один
  3. Когда мы создаем исходный объект beanA и помещаем его в кеш третьего уровня, пришло время заполнить свойства объекта.В это время мы обнаруживаем, что зависим от beanB, и затем переходим к созданию beanB. тот же процесс, beanB создается и заполняется. Когда атрибут найден, он зависит от beanA, и это тот же процесс. Разница в том, что в это время только что добавленный исходный объект beanA можно найти на третьем уровне cache, поэтому нет необходимости продолжать его создавать, использовать для внедрения beanB и завершать beanB созданием
  4. Теперь, когда beanB создан, beanA может выполнить этапы заполнения свойств, затем выполнить остальную часть логики, и замкнутый цикл завершен.

Вот как Spring разрешает циклические зависимости в одноэлементном режиме.

Но в этом месте, кто бы не посмотрел исходный код, возникнет небольшое сомнение, а зачем вообще нужен кеш третьего уровня?

Революция еще не удалась, товарищам еще надо потрудиться

Когда я следил за исходным кодом, я обнаружил, что когда beanB нужно сослаться на «полуфабрикат» beanA, он вызовет «предварительную ссылку», то есть следующий код:

ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
    // 三级缓存有的话,就把他移动到二级缓存
    singletonObject = singletonFactory.getObject();
    this.earlySingletonObjects.put(beanName, singletonObject);
    this.singletonFactories.remove(beanName);
}

singletonFactory.getObject()это метод интерфейса, конкретный метод реализации здесь находится в

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                // 这么一大段就这句话是核心,也就是当bean要进行提前曝光时,
                // 给一个机会,通过重写后置处理器的getEarlyBeanReference方法,来自定义操作bean
                // 值得注意的是,如果提前曝光了,但是没有被提前引用,则该后置处理器并不生效!!!
                // 这也正式三级缓存存在的意义,否则二级缓存就可以解决循环依赖的问题
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
    }
    return exposedObject;
}

Именно из-за этого метода Spring использует кеш третьего уровня вместо кеша второго уровня. Его назначение — постобработка. Если нет постобработки АОП, он не будет вводить оператор if и напрямую возвращает выставленный объект, который эквивалентно тому, что Nothing, достаточно кеша второго уровня.

Таким образом, делается вывод, что этот кэш L3 должен быть связан с АОП, продолжайте.

В исходном коде SpringgetEarlyBeanReferenceдаSmartInstantiationAwareBeanPostProcessorМетод интерфейса по умолчанию, единственная реальная реализация этого метода **AbstractAutoProxyCreator** Этот класс используется для прокси-серверов AOP для раннего раскрытия.

@Override
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
   Object cacheKey = getCacheKey(bean.getClass(), beanName);
   this.earlyProxyReferences.put(cacheKey, bean);
   // 对bean进行提前Spring AOP代理
   return wrapIfNecessary(bean, beanName, cacheKey);
}

Это немного суховато, давайте немного продемонстрируем, мы все знаемSpring АОП, транзакциии т.д. реализуются через прокси-объекты, аделаОбъект прокси создается автоматически создателем автоматического прокси. То есть то, что Spring наконец помещает для нас в контейнер, является прокси-объектом,а не исходный объект, предположим, что у нас есть следующий бизнес-код:

@Service
public class HelloServiceImpl implements HelloService {
   @Autowired
   private HelloService helloService;

   @Override
   @Transactional
   public Object hello() {
      return "Hello JavaKeeper";
   }
}

этоServiceКласс использует транзакции, поэтому в конечном итоге будет сгенерирован динамический прокси-объект JDK.Proxy. Просто так получилось, что он снова существуетцитировать себяКруговая зависимость идеально соответствует требованиям нашей сцены.

Давайте настроим постобработку, чтобы увидеть эффект:

@Component
public class HelloProcessor implements SmartInstantiationAwareBeanPostProcessor {

	@Override
	public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
		System.out.println("提前曝光了:"+beanName);
		return bean;
	}
}

Как видите, в стеке вызывающих методов есть собственная реализацияHelloProcessor, указывающее, что этот bean-компонент будет обрабатываться через прокси-сервер AOP.

Давайте посмотрим на процесс создания этого самозацикливающегося компонента из исходного кода:

protected Object doCreateBean( ... ){
	...
	
	boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
    // 需要提前暴露(支持循环依赖),就注册一个ObjectFactory到三级缓存
	if (earlySingletonExposure) { 
        // 添加 bean 工厂对象到 singletonFactories 缓存中,并获取原始对象的早期引用
		//匿名内部方法 getEarlyBeanReference 就是后置处理器	
		// SmartInstantiationAwareBeanPostProcessor 的一个方法,
		// 它的功效为:保证自己被循环依赖的时候,即使被别的Bean @Autowire进去的也是代理对象
		addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
	}

	// 此处注意:如果此处自己被循环依赖了  那它会走上面的getEarlyBeanReference,从而创建一个代理对象从		三级缓存转移到二级缓存里
	// 注意此时候对象还在二级缓存里,并没有在一级缓存。并且此时后续的这两步操作还是用的 exposedObject,它仍旧是原始对象~~~
	populateBean(beanName, mbd, instanceWrapper);
	exposedObject = initializeBean(beanName, exposedObject, mbd);

	// 因为事务的AOP自动代理创建器在getEarlyBeanReference 创建代理后,initializeBean 就不会再重复创建了,二选一的)
    	
	// 所以经过这两大步后,exposedObject 还是原始对象,通过 getEarlyBeanReference 创建的代理对象还在三级缓存呢
	
	...
	
	// 循环依赖校验
	if (earlySingletonExposure) {
        // 注意此处第二个参数传的false,表示不去三级缓存里再去调用一次getObject()方法了~~~,此时代理对象还在二级缓存,所以这里拿出来的就是个 代理对象
		// 最后赋值给exposedObject  然后return出去,进而最终被addSingleton()添加进一级缓存里面去  
		// 这样就保证了我们容器里 最终实际上是代理对象,而非原始对象~~~~~
		Object earlySingletonReference = getSingleton(beanName, false);
		if (earlySingletonReference != null) {
			if (exposedObject == bean) { 
				exposedObject = earlySingletonReference;
			}
		}
		...
	}
	
}

Самозамешательство:

В: Я так и не понял, почему так устроено, даже если есть прокси, его можно использовать в прокси второго уровня кеша | Зачем использовать кеш третьего уровня?

Давайте еще раз посмотрим на соответствующий код. Предположим, что сейчас мы находимся в архитектуре кеша второго уровня. Когда мы создавали A, мы не знали, существует ли круговая зависимость, поэтому мы поместили его в кеш второго уровня, чтобы раскрыть его. заранее, а затем создал B, который также был помещен в кеш второго уровня. Когда я обнаруживаю, что он снова циклически зависит от A, я иду в кеш второго уровня, чтобы найти его. Да, но если есть В настоящее время прокси-сервер AOP, мы хотим, чтобы прокси-объект не был исходным объектом. Что нам делать? Мы можем только изменить логику. За один шаг, независимо от 3721, все bean-компоненты завершат прокси-сервер AOP. Если это случае нет необходимости в кеше третьего уровня, но это не только не нужно, но и нарушает совмещение SpringAOPДизайн с жизненным циклом Bean.

Поэтому Spring «лишний» сначала инкапсулирует экземпляр в ObjectFactory (кеш уровня 3).Основные ключевые моменты:getObject()Метод не возвращает экземпляр напрямую, а использует экземплярSmartInstantiationAwareBeanPostProcessorизgetEarlyBeanReferenceМетод обрабатывает bean-компоненты, то есть при наличии постпроцессора в Spring все singleton-бины будут выставлены в кеш L3 заранее после инстанцирования, но не все bean-компоненты имеют циклические зависимости, то есть шаги из третьего- Кэш уровня в кеш второго уровня не может быть выполнен.Он может быть создан непосредственно после экспонирования, и он будет напрямую добавлен в кеш первого уровня без предварительной ссылки. Это гарантирует, что только предварительно представленные и ссылочные bean-компоненты будут выполнять эту постобработку.

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
             // 三级缓存获取,key=beanName value=objectFactory,objectFactory中存储					//getObject()方法用于获取提前曝光的实例
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    // 三级缓存有的话,就把他移动到二级缓存
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}


boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
      isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
   if (logger.isDebugEnabled()) {
      logger.debug("Eagerly caching bean '" + beanName +
            "' to allow for resolving potential circular references");
   }
   // 添加 bean 工厂对象到 singletonFactories 缓存中,并获取原始对象的早期引用
   //匿名内部方法 getEarlyBeanReference 就是后置处理器
   // SmartInstantiationAwareBeanPostProcessor 的一个方法,
   // 它的功效为:保证自己被循环依赖的时候,即使被别的Bean @Autowire进去的也是代理对象~~~~  AOP自动代理创建器此方法里会创建的代理对象~~~
   addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
Еще вопрос: АОП-прокси-объект помещается в кеш третьего уровня заранее, без заполнения свойств и инициализации, как этот прокси обеспечивает внедрение свойств-зависимостей?

Это снова включает в себя реализацию динамического прокси в Spring, будь тоcglibагент илиjdkКласс прокси, сгенерированный динамическим прокси.При проксировании целевой объект target будет сохранен в последнем сгенерированном прокси$proxy, при звонке$proxyметод будет вызван обратноh.invokeh.invokeОн вызовет исходный метод целевого объекта target. Все, по сути, при динамическом проксировании АОП исходный бин сохраняется вАгент раннего воздействияудар, после原始 beanпродолжать заканчивать属性填充и初始化работать. Поскольку прокси АОП$proxy хранится вtragetэто原始beanцитаты, поэтому последующие原始beanСовершенство, эквивалентное Spring AOPtargetСовершенство АОП гарантирует属性填充и初始化!

неодноэлементная круговая зависимость

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

<bean id="beanA" class="priv.starfish.BeanA" scope="prototype">
   <property name="beanB" ref="beanB"/>
</bean>

<bean id="beanB" class="priv.starfish.BeanB" scope="prototype">
   <property name="beanA" ref="beanA"/>
</bean>

Запустите Spring, и результат будет следующим:

Error creating bean with name 'beanA' defined in class path resource [applicationContext.xml]: Cannot resolve reference to bean 'beanB' while setting bean property 'beanB';

Error creating bean with name 'beanB' defined in class path resource [applicationContext.xml]: Cannot resolve reference to bean 'beanA' while setting bean property 'beanA';

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'beanA': Requested bean is currently in creation: Is there an unresolvable circular reference?

заprototypeКомпоненты с ограниченной областью действия, контейнер Spring не может выполнить внедрение зависимостей, поскольку контейнер Spring не кэшируетсяprototypeКомпонент с заданной областью действия, поэтому нет возможности заранее раскрыть создаваемый компонент.

Причина также очень понятна.Режим прототипа будет создавать объект экземпляра для каждого запроса.Даже если кеш будет добавлен, если будет слишком много циклических ссылок, это будет более проблематично, поэтому Spring не поддерживает этот метод и бросает исключение напрямую:

if (isPrototypeCurrentlyInCreation(beanName)) {
   throw new BeanCurrentlyInCreationException(beanName);
}

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

Выше мы говорили о проблеме циклической зависимости одноэлементных бинов, внедряемых с помощью метода Setter.Все друзья Spring знают, что есть и другие способы внедрения зависимостей.Внедрение конструктора, метод внедрения фабричного метода (используется редко), если метод внедрения конструктора также имеет циклические зависимости, можете ли вы это сделать?

Давайте снова изменим код и файл конфигурации

public class BeanA {
   private BeanB beanB;
   public BeanA(BeanB beanB) {
      this.beanB = beanB;
   }
}

public class BeanB {
	private BeanA beanA;
	public BeanB(BeanA beanA) {
		this.beanA = beanA;
	}
}
<bean id="beanA" class="priv.starfish.BeanA">
<constructor-arg ref="beanB"/>
</bean>

<bean id="beanB" class="priv.starfish.BeanB">
<constructor-arg ref="beanA"/>
</bean>

Результат выполнения снова является исключением

Ознакомьтесь с официальным заявлением

Circular dependencies

If you use predominantly constructor injection, it is possible to create an unresolvable circular dependency scenario.

For example: Class A requires an instance of class B through constructor injection, and class B requires an instance of class A through constructor injection. If you configure beans for classes A and B to be injected into each other, the Spring IoC container detects this circular reference at runtime, and throws a BeanCurrentlyInCreationException.

One possible solution is to edit the source code of some classes to be configured by setters rather than constructors. Alternatively, avoid constructor injection and use setter injection only. In other words, although it is not recommended, you can configure circular dependencies with setter injection.

Unlike the typical case (with no circular dependencies), a circular dependency between bean A and bean B forces one of the beans to be injected into the other prior to being fully initialized itself (a classic chicken-and-egg scenario).

Вероятно, имеется в виду:

Циклические сценарии зависимости не могут быть разрешены, если вы в основном используете внедрение конструктора. Рекомендуется использовать внедрение сеттера вместо внедрения конструктора.

На самом деле это не означает, что пока конструктор вводится, будет проблема циклической зависимости.Когда Spring создает bean-компонент, по умолчаниюСоздано в соответствии с естественным порядком, давайте назовем bean-компонент, созданный первым, основным bean-компонентом, A выше является основным bean-компонентом,Пока основной компонент внедряет зависимый компонент способом установки, не имеет значения, как внедряется зависимый компонент, его можно решить, и наоборот

Следовательно, у нас есть проблема циклической зависимости AB, описанная выше, пока метод внедрения A является сеттером, проблемы циклической зависимости не будет.

Интервьюер спросил: почему?

Решение Spring для циклических зависимостей основано на концепции «промежуточного состояния» Bean, которое относится к состоянию, которое было создано, но еще не инициализировано. Процесс инстанцирования создается через конструктор.Если A не создан, то как его можно выставить заранее, поэтому круговая зависимость конструктора не может быть решена.Я всегда думаю, что цыплята должны быть раньше яиц..

Небольшое резюме | вот такое интервью

Не будет ли проблемой заранее внедрить неинициализированный объект типа A?

Хотя неинициализированный объект A будет внедрен в B заранее при создании B, ссылка на объект A, внедренный в B, всегда используется в процессе создания A, и затем A будет инициализирован в соответствии с этой ссылкой. нет проблем.

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

Чтобы решить проблему циклической зависимости синглтона, Spring использует кеш третьего уровня. Кэш первого уровня представляет собой одноэлементный пул (singletonObjects), кэш второго уровня — это объект ранней экспозиции (earlySingletonObjects), кэш третьего уровня — это фабрика объектов предварительной экспозиции (singletonFactories).

Предполагая, что A и B имеют циклическую ссылку, когда создается экземпляр A, он помещается в кэш третьего уровня, а затем, когда атрибут заполняется, обнаруживается, что он зависит от B. Тот же самый процесс также создается и помещается в кеш третьего уровня, а затем атрибут заполняется. Когда я обнаруживаю, что полагаюсь на A, я нахожу из кеша ранее выставленный A. Если нет прокси-сервера AOP, напрямую внедряю исходный объект A в B. После завершения инициализация B, выполните заполнение атрибутов и инициализацию. В это время, после завершения B, перейдите к выполнению оставшихся шагов A. Если есть прокси-сервер AOP, выполните обработку AOP, чтобы получить прокси-объект A, внедрить его в B , и пройдите остальную часть процесса.

Зачем использовать кеш L3? Могут ли кэши L2 решать циклические зависимости?

Если нет прокси АОП, кэш второго уровня может решить проблему, но в случае прокси АОП использование только кеша второго уровня означает, что все bean-компоненты должны завершать прокси АОП после инстанцирования, что нарушает принцип Spring дизайн. разработан с самого началаAnnotationAwareAspectJAutoProxyCreatorЭтот постпроцессор завершает проксирование АОП на последнем этапе жизненного цикла компонента, а не сразу после создания экземпляра.

Ссылка и благодарность:

«Углубленный анализ исходного кода Spring» — Хао Цзя

developer.aliyun.com/article/766…

Woohoo Тянь Сяобо.com/2018/06/08/…

cloud.Tencent.com/developer/ ах…

blog.CSDN.net/Горячая шпилька…