Видеть насквозь вопросы для интервью — Spring Circular Dependencies — Секрет позади

Java

Делай все хорошо, ты можешь многое

1. Секрет круговых вопросов о зависимости в вопросах на собеседовании2. Подготовка базовых знаний1. Java передается по ссылке или по значению?2. Несколько ключевых моментов создания Бина3. Принцип АОП4. getBean()5. Три тайника3. Анализ циклических зависимостей1. Может ли кеш это решить?2. Можно ли разрешить два кеша?3. Зачем нужны три тайникаприсутствие агентаВесна делает это4. Вопросы и ответы для дополнительного размышленияV. Резюме

1. Почему на собеседованиях так легко задавать вопросы о циклической зависимости?

Весна - мастер, и я думаю, что человек, который может досконально разобраться в ее деталях, должен быть на уровне бога.

На самом деле, я всегда задавался вопросом, почему это продолжает циркулировать в Интернете.Spring 循环依赖问题вопросы интервью. Я также наблюдал, как многие люди время от времени объясняли принцип круговой зависимости. Но мне все же кажется, что это какое-то смутное ощущение по этому поводу.

Какой смысл задавать этот вопрос в интервью?

Пока, после того как я несколько раз отвернулся от мира исходного кода, а затем снова посмотрел на эту проблему, я почувствовал просветление.

Из-за знаний, необходимых для этой проблемы круговой зависимости.

  • Вам необходимо иметь представление о жизненном цикле bean-компонента (то есть о процессе, с помощью которого Spring создает bean-компонент).
  • Вы должны понимать принципы АОП

Да, простая проблема циклической зависимости на самом деле содержит два основных момента Spring: жизненный цикл bean-компонентов и принцип АОП.

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

Исходя из такого мышления, я также расскажу о своем понимании циклических зависимостей.

2. Подготовка базовых знаний

1. Java передается по ссылке или по значению?

JAVA 里是值传递,值传递,值传递!!!

public class Test2 {
    public static void main(String[] args) {
        A a =  new A();
        System.out.println("(1)调用change前"+a);
        change(a);
        System.out.println("(3)调用change后"+a);
    }
    public static void change(A a){
        a= new A();
        System.out.println("(2)change方法内"+a);
    }
}
class A{
}
(1)调用change前com.wsjia.ms.controller.A@61064425
(2)change方法内com.wsjia.ms.controller.A@7b1d7fff
(3)调用change后com.wsjia.ms.controller.A@61064425

Я признаю, что JAVA полностью передается по значению.

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

Давай позвоним ему引用的传递【Я знаю то, что ты должен знать】

2. Несколько ключевых моментов создания Бина

Вот лишь несколько важных этапов Beans.Чтобы прояснить циклические зависимости, в дальнейшем мы специально поговорим о создании Beans.

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

  • createBeanInstance : создание экземпляра, акцент здесь на том, чтоBean的早期引用在此出现.
  • populateBean : заполнение свойств, с которыми мы здесь знакомы.@AutowiredЗдесь происходит внедрение свойства
  • initializeBean : вызвать некоторые методы инициализации, такие какinit ,afterPropertiesSet

также:BeanPostProcessorКак интерфейс расширения, он будет вкраплен в процесс создания Bean-компонента, оставляя множество хуков, чтобы мы могли влиять на процесс создания Bean-компонента. Основной из них — создание АОП-прокси.

3. Принцип АОП

AOP A.InstantiationAwareBeanPostProcessorТипBeanPostProcessor, Участвуйте в логике создания Bean и решайте, создавать ли прокси-объект в зависимости от того, должен ли текущий Bean быть прокси.

Основная логика в(BeanPostProcessor)AbstractAutoProxyCreator类中Есть три важных метода.

//早期bean创建代理用
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        this.earlyProxyReferences.put(cacheKey, bean);
        return wrapIfNecessary(bean, beanName, cacheKey);
}
//bean创建代理用
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean != null) {
            Object cacheKey = getCacheKey(bean.getClass(), beanName);
            if (this.earlyProxyReferences.remove(cacheKey) != bean) {
                return wrapIfNecessary(bean, beanName, cacheKey);
            }
        }
        return bean;
}
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    //创建代理的逻辑
}

Когда Bean создает прокси, то, что мы получаем от BeanFactory через имя bean-компонента, является прокси-объектом.

4. Что возвращает getBean()?

Когда мы пытаемся получить Bean из BeanFactory.getBean(beanname) по имени, должен ли быть возвращен экземпляр, соответствующий классу A?

Ответ — нет, когда A нужно создать прокси-объект, мы getBean получаем ссылку на прокси-объект.

5. Три тайника

В этой статье пока рассматривается только случай единичного экземпляра.

Кэшируйте созданный Bean, что является очень распространенной логикой.

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

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

    /** Cache of singleton factories: bean name --> ObjectFactory */
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
  • singletonObjects: кеш первого уровня, в котором хранятся все созданные成品Bean.
  • EarlySingletonObjects: кеш второго уровня, в котором хранятся все半成品的Bean.
  • singletonFactories: кеш третьего уровня, отличный от первых двух, в котором хранятся ссылки на объекты Bean.专门创建Bean的一个工厂对象. Этот кеш используется для разрешения циклических зависимостей

Вот момент: лично я считаю, что эти три кеша правильнее называть именно так.

  • singletonObjects:Кэш готовой продукции
  • ранниеSingletonObjects:Склад полуфабрикатов
  • singletonFactory:Фабричный кеш синглтона

Что касается почему, дайте мне немного личного понимания.

3. Анализ циклических зависимостей

Далее поговорим о циклических зависимостях

В этой статье обсуждается только случай внедрения свойства.

Предположим, что есть два таких класса с циклическими зависимостями. Как решить эту проблему?

public class A {
    B b;

    public A() {
    }
}

class B{
    @Autowired
    A a;

    public B() {
    }
}

1. Может ли кеш это решить?

Во-первых, давайте обсудим эту проблему циклической зависимости

在这里插入图片描述
вставьте сюда описание изображения
  • Начните с приобретения A, проверьте из кеша, не начинайте создавать экземпляр A, выполните метод построения и обнаружите, что он должен полагаться на B при заполнении свойств.
  • Попробуйте получить B из кеша.
  • Начните создавать экземпляр B, выполните метод построения и заполните свойства.Когда вы обнаружите, что вам нужно полагаться на A, возьмите кеш, чтобы найти A.
  • A создается и не завершается.
  • `мертвый узел`

2. Можно ли разрешить два кеша? ?

Не дожидаясь завершения создания, получив ссылку,提前放入半成品缓存

在这里插入图片描述
вставьте сюда описание изображения
  • После того, как ссылка создана, она заранее выставляется半成品缓存中
  • Зависимость B, создать B, B нашел зависимость A при заполнении свойств,先从成品缓存查找,没有,再从半成品缓存查找получить早期引用.
  • B顺利走完创建过程, будуB的早期引用从半成品缓存移动到成品缓存
  • B создается, A получает ссылку на B и продолжает создавать.
  • Создается, будетA的早期引用从半成品缓存移动到成品缓存
  • `Идеально разрешить циклические зависимости`

Хорошо? Могут ли два кеша решить эту проблему? ? ? Зачем вам три тайника? ?

3. Зачем нужны три тайника

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

В приведенных выше двух местах кеша мы просто не рассмотрели ситуацию с прокси.

присутствие агента

На заключительном этапе создания bean-компонента проверяется, нужно ли создавать прокси-сервер.Если прокси-сервер создан, окончательным возвратом является ссылка на экземпляр прокси-сервера. Получаем ссылку на экземпляр прокси через beanname

То есть: в вышеизложенном предполагается, что A в конечном итоге создаст прокси, предоставив ссылку A заранее и заполнив атрибут исходной ссылкой на объект A, когда B заполнит свойство. Наконец, в готовую библиотеку продукта помещается ссылка агента. Тогда B все еще является ранней ссылкой на A. Этот результат в конечном итоге будет сильно отличаться от того, что мы ожидали.

что делать? ? ?

Весна делает это
=======AbstractAutowireCapableBeanFactory.doCreateBean

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
            throws BeanCreationException {

        【1】Instantiate the bean.
        BeanWrapper instanceWrapper = null;
        if (mbd.isSingleton()) {
            instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
        }
        if (instanceWrapper == null) {
            instanceWrapper = createBeanInstance(beanName, mbd, args);
        }
        【早期引用】
        final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);

        【2】在需要暴露早期引用的条件下

        boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                isSingletonCurrentlyInCreation(beanName));
        if (earlySingletonExposure) {

            【2.1】绑定当前Bean引用到ObjectFactory,注册到三级singletonFactories 

            addSingletonFactory(beanName, new ObjectFactory<Object>() {
                【重写getObject】
                @Override
                public Object getObject() throws BeansException {
                    return getEarlyBeanReference(beanName, mbd, bean);
                }
            });
        }

}

===AbstractAutowireCapableBeanFactory.doCreateBean--->DefaultSingletonBeanRegistry.getSingleton

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {

        synchronized (this.singletonObjects) {
            if (!this.singletonObjects.containsKey(beanName)) {
                【3】放入到singletonFactories 缓存中,清除其他缓存
                this.singletonFactories.put(beanName, singletonFactory);
                this.earlySingletonObjects.remove(beanName);
                this.registeredSingletons.add(beanName);
            }
}

===========AbstractBeanFactory.doGetBean--->DefaultSingletonBeanRegistry.getSingleton

【4】按Beanname取Bean
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        【4.1】先尝试从成品缓存获取
        Object singletonObject = this.singletonObjects.get(beanName);
        【4.2】成品缓存没有,且正在创建,尝试从半成品缓存获取
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
                singletonObject = this.earlySingletonObjects.get(beanName);

                if (singletonObject == null && allowEarlyReference) {

                    【4.3】半成品缓存没有,且允许早期引用,尝试从工厂缓存中查找有么此Bean的工厂类存在

                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {

                        【4.4】存在,执行getObject获取早期引用,放入到半成品缓存,并将工厂类从工厂缓存中移除
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return (singletonObject != NULL_OBJECT ? singletonObject : null);
    }

регистрObjectFactory工厂类到工厂缓存:
singletonFactory.getObject(); вызовет перезаписьgetObject()перечислитьgetEarlyBeanReferenceпоследующие операции.

  • Если последующие операции не создают прокси,返回的依然是原始引用
  • Если требуется прокси, здесь返回就是代理的引用
早期的扩展处理
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
        Object exposedObject = bean;
        if (bean != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
            for (BeanPostProcessor bp : getBeanPostProcessors()) {
                if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                    SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                    exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
                    if (exposedObject == null) {
                        return null;
                    }
                }
            }
        }
        return exposedObject;
    }

Видно, что вот операция по выполнению расширения.

AbstractAutoProxyCreator

【1】针对提前创建代理,返回代理引用
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        this.earlyProxyReferences.put(cacheKey, bean);
        return wrapIfNecessary(bean, beanName, cacheKey);
}
【2】针对不是提前创建代理的情况
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean != null) {
            Object cacheKey = getCacheKey(bean.getClass(), beanName);
            if (this.earlyProxyReferences.remove(cacheKey) != bean) {
                return wrapIfNecessary(bean, beanName, cacheKey);
            }
        }
        return bean;
}

Как можно заметитьsingletonFactory Заводской кеш, ключ к решению проблемы с прокси

Общий процесс показан на рисунке

在这里插入图片描述
вставьте сюда описание изображения

ключевой момент:

  • A привязан к ObjectFactory, зарегистрированной для工厂缓存singletonFactoryсередина,
  • Когда B заполняет A,先查成品缓存У вас есть,再查半成品缓存У вас есть,最后看工厂缓存有没有单例工厂类, у которого есть ObjectFactory A. Вызвать getObject, выполнить логику расширения, может вернуть ссылку на прокси или вернуть исходную ссылку.
  • Успешно получена ранняя ссылка A, поместите A в半成品缓存, B заполняется A, и ссылка завершается.
  • Проблема прокси и проблема круговой зависимости решены.

4. Вопросы и ответы для дополнительного размышления

Помимо этого, у меня еще есть некоторые мысли и высказаны собственные мнения.

1: Почему бы не позвонить заранееObjectFactory.getObject ()Непосредственно выполните логику расширения, чтобы обработать раннюю ссылку A, получить полуфабрикатную ссылку экземпляра и поместить ее вearlySingletonObjects, вам нужно сначала поместить фабричный класс в фабричный кеш? Как насчет использования кеша L3?

Ответ: Предположим, что А просто зависит от В. Если операция расширения А выполняется заранее, на более позднем этапе создания А точка расширения будет пройдена снова, разве это не пустая трата времени?

2. Какое значение имеет наличие кэша второго уровня?

О: На самом деле, я думаю二级缓存earlySingletonObjectsа также三级缓存singletonFactories. все для分工明确и жить.

  • 一级缓存singletonObjects: это существует最终的成品
  • 二级缓存earlySingletonObjectsпросто чтобы спасти半成品Bean
  • 三级缓存singletonFactories: сохранитьbean工厂

Из-за раннего облучения от工厂里创建После завершения представляет собой полуфабрикат, заложенный в半成品缓存, когда весь процесс завершен, готовый продукт помещается в成品缓存.分工明确

Вот почему я думаю, что правильнее называть кеш готового продукта, кеш полуфабриката, кеш одноэлементной фабрики.

3. Если это доходит до крайности, позвольте мне спроектировать этот блок, я должен спроектировать его как единый кеш. Выполняйте последующие операции заблаговременно, посадите его вsingletonObjects.

Это не похоже на проблему, это просто очень грязно

V. Резюме

Вышеизложенное является частью моего понимания и размышлений о циклических зависимостях Spring.

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


Если в этой статье есть какие-либо ошибки, пожалуйста, критикуйте и советуйте, это очень ценится!
Если вы считаете, что статья хорошая,поставить лайкБар
Если вы считаете, что статья хорошая,поставить лайкБар
Если вы считаете, что статья хорошая,поставить лайкБар