Интерпретация Spring построчно (5) — никто не знает круговые зависимости лучше меня!

Java

Создание не простое, пожалуйста, указывайте автора в начале перепечатки: Nuggets@小西子 + ссылка на источник~

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

Введение

Этот пост в блоге в основном рассказывает о нашемspringКак решить проблему циклических зависимостей.

2. Что такое круговая зависимость

Прежде всего, нам нужно прояснить, что такое циклическая зависимость? Вот простой пример:

@Service
public class A {
    @Autowired
    private B b;
}
@Service
public class B {
    @Autowired
    private A a;
}

Взяв этот пример, мы объявляемa,bдваbeanaнеобходимо ввестиb,bнеобходимо ввестиa.

В сочетании с нашей предыдущей записью в блогеbeanЗнание жизненного цикла, давайте смоделируем эти дваbeanСозданный процесс:

循环依赖问题

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

Трехуровневая конструкция кэш-памяти

1. Решайте циклические зависимости самостоятельно

Теперь, если бы мы былиspringАрхитектор, как нам решить эту проблему циклической зависимости?

1.1. Дизайн процесса

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

循环依赖如何解决

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

иBПроцесс инициализации экземпляра находится вAПосле создания экземпляра вpopulateBeanЗапускается, когда в методе выполняется внедрение зависимостей. то если мыBВ процессе создания экземпляра вы хотите получитьAнапример, тогдаAэкземпляр должен быть вcreateBeanInstanceПосле создания экземпляра (ничего не говорите, если экземпляра нет),populateBeanПеред вызовом метода он выставляется, так чтоBможет пройтиgetBeanВозьми! (Студенты тщательно обдумывают этот процесс. Можно ли только преобразовать его в рамках существующего процесса? Сначала подумайте об этом процессе, а затем объедините его.springПроверка исходного кода, вы не можете забыть эту часть знаний, если вы хотите забыть ее позже)

Затем в сочетании с нашими идеями давайте изменим блок-схему:

使用缓存解决循环依赖

1.2 Реализация псевдокода

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

// 正真已经初始化完成的map
private Map<String, Object> singleMap = new ConcurrentHashMap<>(16);
// 缓存的map
private Map<String, Object> cacheMap = new ConcurrentHashMap<>(16);

protected Object getBean(final String beanName) {
    // 先看一下目标bean是否完全初始化完了,完全初始化完直接返回
    Object single = singleMap.get(beanName);
    if (single != null) {
        return single;
    }
    // 再看一下目标bean实例是否已经创建,已经创建直接返回
    single = cacheMap.get(beanName);
    if (single != null) {
        return single;
    }
    // 创建实例
    Object beanInstance = createBeanInstance(beanName);
    // 实例创建之后,放入缓存
    // 因为已经创建实例了,这个时候这个实例的引用暴露出去已经没问题了
	// 之后的属性注入等逻辑还是在这个实例上做的
    cacheMap.put(beanName, beanInstance);
    // 依赖注入,会触发依赖的bean的getBean方法
    populateBean(beanName, beanInstance);
    // 初始化方法调用
    initializeBean(beanName, beanInstance);
    // 从缓存移除,放入实例map
    singleMap.put(beanName, beanInstance);
    cacheMap.remove(beanName)
    
    return beanInstance;
}

Можно видеть, что если мы реализуем структуру кеша для решения проблемы циклических зависимостей, нам может понадобиться только два уровня структуры, ноspringОднако он использует кеш L3.

2. Springисходный код

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

Поскольку мы охватили всюbeanЖизненный цикл .Длинная статья в формате 4D объясняет жизненный цикл бобов.

2.1. SpringДизайн кэша L3

2.1.1 Исходный код кэша L3

Во-первых, в нашемAbstractBeanFactory#doGetBeanв логике:

// 初始化是通过getBean触发bean创建的,依赖注入最终也会使用getBean获取依赖的bean的实例
public Object getBean(String name) throws BeansException {
    return doGetBean(name, null, null, false);
}
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
                          @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {

    final String beanName = transformedBeanName(name);
    Object bean;

    // 获取bean实例
    Object sharedInstance = getSingleton(beanName);
    if (sharedInstance != null && args == null) {
        // beanFactory相关,之后再讲
        bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
    }
    else {
        // 跳过一些代码
        // 创建bean的逻辑
        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);
        }
        // 跳过一些代码
    }
    // 跳过一些代码
    // 返回bean实例
    return (T) bean;
}

Как видите, если мы используемgetSingleton(beanName)Если экземпляр бина получен напрямую, экземпляр бина будет возвращен напрямую.Давайте посмотрим на этот метод (этот метод принадлежитDefaultSingletonBeanRegistry):

// 一级缓存,缓存正常的bean实例
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

// 二级缓存,缓存还未进行依赖注入和初始化方法调用的bean实例
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

// 三级缓存,缓存bean实例的ObjectFactory
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

public Object getSingleton(String beanName) {
    return getSingleton(beanName, true);
}

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 先尝试中一级缓存获取
    Object singletonObject = this.singletonObjects.get(beanName);
    // 获取不到,并且当前需要获取的bean正在创建中
    // 第一次容器初始化触发getBean(A)的时候,这个isSingletonCurrentlyInCreation判断一定为false
    // 这个时候就会去走创建bean的流程,创建bean之前会先把这个bean标记为正在创建
    // 然后A实例化之后,依赖注入B,触发B的实例化,B再注入A的时候,会再次触发getBean(A)
    // 此时isSingletonCurrentlyInCreation就会返回true了
    
    // 当前需要获取的bean正在创建中时,代表出现了循环依赖(或者一前一后并发获取这个bean)
    // 这个时候才需要去看二、三级缓存
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        // 加锁了
        synchronized (this.singletonObjects) {
            // 从二级缓存获取
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                // 二级缓存也没有,并且允许获取早期引用的话 - allowEarlyReference传进来是true
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                // 从三级缓存获取ObjectFactory
                if (singletonFactory != null) {
                    // 通过ObjectFactory获取bean实例
                    singletonObject = singletonFactory.getObject();
                    // 放入二级缓存
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    // 从三级缓存删除
                    // 也就是说对于一个单例bean,ObjectFactory#getObject只会调用到一次
                    // 获取到早期bean实例之后,就把这个bean实例从三级缓存升级到二级缓存了
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    // 不管从哪里获取到的bean实例,都会返回
    return singletonObject;
}

Кэши первого и второго уровня понять несложно, фактически их можно понимать как два в нашем псевдокоде.Map, а как быть с этим кешем L3?ObjectFactoryЧто это? Давайте сначала посмотрим на этоObjectFactoryСтруктура:

@FunctionalInterface
public interface ObjectFactory<T> {
	// 好吧,就是简简单单的一个获取实例的函数接口而已
	T getObject() throws BeansException;
}

Вернемся к структуре этого кэша третьего уровня, кэш второго уровня находится вgetSingletonметодputВ, это то же самое, что мы анализировали ранее, создаваяbeanСтавил после экземпляра, вроде не то же самое? Итак, можем ли мы сделать вывод, что, по сути, создатьbeanПосле инстанса он помещается в кеш третьего уровня (короче его нужно класть в кеш после создания инстанса)? давайте следоватьbeanДля созданного кода давайте взглянем на места, которые были намеренно проигнорированы в предыдущей статье:

// 代码做了很多删减,只把主要的逻辑放出来的
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
    throws BeanCreationException {

    // 创建bean实例
    BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
    final Object bean = instanceWrapper.getWrappedInstance();
	// beanPostProcessor埋点调用
    applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
	
    // 重点是这里了,如果是单例bean&&允许循环依赖&&当前bean正在创建
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                                      isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
        // 加入三级缓存
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }

    Object exposedObject = bean;
    try {
        // 依赖注入
        populateBean(beanName, mbd, instanceWrapper);
        // 初始化方法调用
        exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
    catch (Throwable ex) {
        throw new BeanCreationException(...); 
    }

    if (earlySingletonExposure) {
        // 第二个参数传false是不会从三级缓存中取值的
        Object earlySingletonReference = getSingleton(beanName, false);
        if (earlySingletonReference != null) {
            // 如果发现二级缓存中有值了 - 说明出现了循环依赖
            if (exposedObject == bean) {
                // 并且initializeBean没有改变bean的引用
                // 则把二级缓存中的bean实例返回出去
                exposedObject = earlySingletonReference;
            }
        }
    }
    
    try {
        // 注册销毁逻辑
        registerDisposableBeanIfNecessary(beanName, bean, mbd);
    }
    catch (BeanDefinitionValidationException ex) {
        throw new BeanCreationException(...);
    }

    return exposedObject;
}

Как видите, инициализируйтеbeanДа, создатьbeanПосле, например, если этот компонент является синглтономbean&& разрешить циклические зависимости && текущийbeanсоздается, то вызоветaddSingletonFactoryДобавьте кеш L3:

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            // 加入三级缓存
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

То есть этот раздел в нашем псевдокоде имеет:

// 创建实例
Object beanInstance = createBeanInstance(beanName);
// 实例创建之后,放入缓存
// 因为已经创建实例了,这个时候这个实例的引用暴露出去已经没问题了
// 之后的属性注入等逻辑还是在这个实例上做的
cacheMap.put(beanName, beanInstance);

Затем завершается полное создание экземпляра.beanКогда пора подключать наш инстансMap(кэш 1-го уровня)singletonObjectsкак насчет?

В этот момент мы вернемся к вызовуcreateBeanЛогика этой части метода такова:

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);
}

Как видите, нашcreateBeanЛогика создания черезlamdbaГрамматика входящаяgetSingletonметод, давайте введем этот метод и посмотрим:

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null) {
            // 一级缓存拿不到
            // 注意一下这个方法,这里会标记这个bean正在创建
            beforeSingletonCreation(beanName);
            boolean newSingleton = false;
            try {
                // 调用外部传入的lamdba,即createBean逻辑
                // 获取到完全实例化好的bean
                // 需要注意的是,这个时候这个bean的实例已经在二级缓存或者三级缓存中了
                // 三级缓存:bean实例创建后放入的,如果没有循环依赖/并发获取这个bean,那会一直在三级缓存中
                // 二级缓存:如果出现循环依赖,第二次进入getBean->getSingleton的时候,会从三级缓存升级到二级缓存
                singletonObject = singletonFactory.getObject();
                // 标记一下
                newSingleton = true;
            }
            catch (IllegalStateException ex) {
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    throw ex;
                }
            }
            catch (BeanCreationException ex) {
                throw ex;
            }
            finally {
                // 这里是从正在创建的列表移除,到这里这个bean要么已经完全初始化完成了
                // 要么就是初始化失败,都需要移除的
                afterSingletonCreation(beanName);
            }
            if (newSingleton) {
                // 如果是新初始化了一个单例bean,加入一级缓存
                addSingleton(beanName, singletonObject);
            }
        }
        return singletonObject;
    }
}

хаха, добавь примерMap(кэш 1-го уровня)singletonObjectsЛогика явно в этомaddSingletonбинго:

protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
        // 这个逻辑应该一点也不意外吧
        // 放入一级缓存,从二、三级缓存删除,这里就用判断当前bean具体是在哪个缓存了
        // 反正都要删的
        this.singletonObjects.put(beanName, singletonObject);
        this.singletonFactories.remove(beanName);
        this.earlySingletonObjects.remove(beanName);
        this.registeredSingletons.add(beanName);
    }
}

То есть этот кусок нашего псевдокода находится вspringЕсть и соответствующие проявления, которые совершенны:

// 初始化方法调用
initializeBean(beanName, beanInstance);
// 从缓存移除,放入实例map
singleMap.put(beanName, beanInstance);
cacheMap.remove(beanName)

Таким образом, Spring решает проблему циклических зависимостей за счет дизайна кеша.

2.1.2. Трехуровневый кэш для решения круговой схемы зависимостей

Что, это все еще немного нечетко после прочтения кода? Затем снова измените нашу блок-схему в соответствии сspringПроцесс:

Spring使用三级缓存解决循环依赖

2.1.3 Кэш L3 решает псевдокод циклической зависимости

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

// 一级缓存
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
// 三级缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

protected Object getBean(final String beanName) {
    // !以下为getSingleton逻辑!
    // 先从一级缓存获取
    Object single = singletonObjects.get(beanName);
    if (single != null) {
        return single;
    }
    // 再从二级缓存获取
    single = earlySingletonObjects.get(beanName);
    if (single != null) {
        return single;
    }
    // 从三级缓存获取objectFactory
    ObjectFactory<?> objectFactory = singletonFactories.get(beanName);
    if (objectFactory != null) {
        single = objectFactory.get();
        // 升到二级缓存
        earlySingletonObjects.put(beanName, single);
        singletonFactories.remove(beanName);
        return single;
    }
    // !以上为getSingleton逻辑!
    
    // !以下为doCreateBean逻辑
    // 缓存完全拿不到,需要创建
    // 创建实例
    Object beanInstance = createBeanInstance(beanName);
    // 实例创建之后,放入三级缓存
    singletonFactories.put(beanName, () -> return beanInstance);
    // 依赖注入,会触发依赖的bean的getBean方法
    populateBean(beanName, beanInstance);
    // 初始化方法调用
    initializeBean(beanName, beanInstance);
    
    // 依赖注入完之后,如果二级缓存有值,说明出现了循环依赖
    // 这个时候直接取二级缓存中的bean实例
    Object earlySingletonReference = earlySingletonObjects.get(beanName);
    if (earlySingletonReference != null) {
        beanInstance = earlySingletonObject;
    }
    // !以上为doCreateBean逻辑
    
    // 从二三缓存移除,放入一级缓存
    singletonObjects.put(beanName, beanInstance);
    earlySingletonObjects.remove(beanName);
    singletonFactories.remove(beanName);
    
    return beanInstance;
}

Собрав всю логику воедино, будет намного понятнее, учащимся нужно только смоделировать ее самостоятельно, а затемpopulateBeanпозвони еще раз вgetBeanЛогика внедрения зависимостей должна быть прояснена.

2.1.4 Отметить текущий создаваемый компонент

в том, что мы только что виделиbeanЭкземпляр инкапсулирован какObjectFactoryИ в процессе помещения его в кеш третьего уровня происходит суждение о том, что текущий бин создается, и как судить об этом статусе:

// 重点是这里了,如果是单例bean&&允许循环依赖&&当前bean正在创建
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                                  isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
    // 加入三级缓存
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

давайте посмотрим на этоisSingletonCurrentlyInCreationЛогика:

private final Set<String> singletonsCurrentlyInCreation =
    Collections.newSetFromMap(new ConcurrentHashMap<>(16));
public boolean isSingletonCurrentlyInCreation(String beanName) {
    return this.singletonsCurrentlyInCreation.contains(beanName);
}

Вы можете видеть сумму, на самом деле, это судить о текущемbeanNameэто в этомsingletonsCurrentlyInCreationВ контейнере, когда манипулируют значением в этом контейнере?

надеюсь школьники запомнятgetSingleton(beanName, singletonFactory)называетсяbeforeSingletonCreationиafterSingletonCreation:

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null) {
            // 一级缓存拿不到
            // 注意一下这个方法,这里会标记这个bean正在创建
            beforeSingletonCreation(beanName);
            boolean newSingleton = false;
            try {
                // 调用外部传入的lamdba,即createBean逻辑
                singletonObject = singletonFactory.getObject();
                // 标记一下
                newSingleton = true;
            }
            catch (BeanCreationException ex) {
                throw ex;
            }
            finally {
                // 这里是从正在创建的列表移除,到这里这个bean要么已经完全初始化完成了
                // 要么就是初始化失败,都需要移除的
                afterSingletonCreation(beanName);
            }
            if (newSingleton) {
                // 如果是新初始化了一个单例bean,加入一级缓存
                addSingleton(beanName, singletonObject);
            }
        }
        return singletonObject;
    }
}

Давайте теперь посмотрим на логику этих двух методов:

protected void beforeSingletonCreation(String beanName) {
    // 加入singletonsCurrentlyInCreation,由于singletonsCurrentlyInCreation是一个set
    // 如果加入失败的话,说明在创建两次这个bean
    // 这个时候会抛出循环依赖异常
    if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
        throw new BeanCurrentlyInCreationException(beanName);
    }
}

protected void afterSingletonCreation(String beanName) {
    // 从singletonsCurrentlyInCreation中删除
    if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
        throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
    }
}

Можно видеть, что наши два метода в основном предназначены дляsingletonsCurrentlyInCreationконтейнер работает,inCreationCheckExclusionsЭтот контейнер можно игнорировать, имя выглядит как какая-то конфигурация, например, белый список.

Главное здесь нужноbeforeSingletonCreation, еслиsingletonsCurrentlyInCreation.add(beanName)Если это не удастся, он броситBeanCurrentlyInCreationExceptionДа, это представляетspringЕсли возникает неразрешимая проблема циклической зависимости, будет создано исключение, чтобы прервать процесс инициализации.beanНе допускается создание дважды.

2.2 Почему он разработан как третичная структура?

2.2.1 Что не так с двумя уровнями кеша?

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

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

На данный момент нам нужно более подробно рассмотреть процесс инициализации компонента:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
    throws BeanCreationException {
    // ...
    if (earlySingletonExposure) {
        // 放入三级缓存
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }
    Object exposedObject = bean;
    try {
        populateBean(beanName, mbd, instanceWrapper);
        // 这里这个引用被替换了
        exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
    // ...
    return exposedObject;
}

Внимательно наблюдайте,initializeBeanметод может возвращать новый объект, таким образом помещаяcreateBeanInstanceСозданный экземпляр компонента заменяется на:

protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
    // 调用aware接口
    invokeAwareMethods(beanName, bean);
    Object wrappedBean = bean;
    if (mbd == null || !mbd.isSynthetic()) {
        // 埋点
        wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
    }

    try {
        // 初始化方法
        invokeInitMethods(beanName, wrappedBean, mbd);
    }
    catch (Throwable ex) {
        throw new BeanCreationException(...);
    }
    if (mbd == null || !mbd.isSynthetic()) {
        // 埋点
        wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
    }

    return wrappedBean;
}

Как видите, нашpostProcessBeforeInitializationиpostProcessAfterInitializationСпособы захоронения точек все можно поставить нашимbeanзаменены.

Затем объединили со всем процессом, потому что после того, как мы поместили его в кеш,initializeBeanВ методе могут быть заменыbean, если есть только два уровня кеша:

Spring只使用二级缓存会出现的问题

Это приведет кBвведенный вAпример сsingletonObjectsсохранено вAAЭкземпляр несовместим, а затем внедряются другие экземплярыa, получитеsingletonObjectsсерединаAAНапример, это определенно не соответствует ожиданиям.

2.2.2 Как кэш L3 решает проблему

Так как же решить эту проблему?

В это время мы должны вернуться к тому месту, где мы добавили кеш L3.addSingletonFactoryВторой параметр — этоObjectFactory, и этоObjectFactoryВ конечном итоге он будет помещен в кеш L3, теперь давайте вернемся к вызовуaddSingletonFactoryМесто:

 // 加入三级缓存
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

Знаком сlamdbaГрамматики знают, чтоgetEarlyBeanReferenceФактически он помещается в кэш третьего уровня.ObjectFactoryиз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;
                // 调用了beanPostProcessor的一个埋点方法
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
    }
    // 返回的是埋点替换的bean
    return exposedObject;
}

Эй, здесь тоже есть закопанное место, которое можно заменитьbeanцитаты.

Это было решитьinitializeBeanвозможная заменаbeanЦитируя вопрос,springОн разработал этот трехуровневый тайник и сохранилObjectFactory, на самом деле конкретныйgetEarlyBeanReferenceвызов, который обеспечиваетgetEarlyBeanReferenceМетод скрытой точки, с помощью этого метода скрытой точки, он позволяет разработчикам заменитьbean, заменил рано.

Например, если вinitializeBeanВ методе предполагается, чтоAзаменитьAA(Эта логика должна быть через некоторыеbeanPostProcessorсделать это), то выbeanPostProcessorмогут быть предоставлены одновременноgetEarlyBeanReferenceметод, когда есть циклическая зависимость, можно поставить заранееA->AAЭта логика делает, иinitializeBeanметод больше не делает этогоA->AA, и, когда наша циклическая логика зависимостей будет завершена,AСоздать -> ВнедритьB-> триггерBИнициализировать -> ВнедритьA-> Выполнить выборку логики кэшаAAЭкземпляр и поместить в кеш L2 ->BИнициализация завершена -> вернутьсяAПри инициализации логики передайте следующий код:

protected Object doCreateBean(...) {
    populateBean(beanName, mbd, instanceWrapper);
    Object exposedObject = initializeBean(beanName, exposedObject, mbd);

    if (earlySingletonExposure) {
        Object earlySingletonReference = getSingleton(beanName, false);
        if (earlySingletonReference != null) {
            if (exposedObject == bean) {
                // 如果二级缓存存在,直接使用二级缓存
                exposedObject = earlySingletonReference;
            }
        }
    }
    return exposedObject;
}

Это гарантирует, что текущийbeanвведенный вAAиsingletonObjectsсерединаAAЭкземпляр теперь тот же объект.

будет хранить кеш второго уровняAAВозврат напрямую, это гарантированоBвведенный вAAпример сspringуправляемый финалAAЭкземпляр тот же.

Весь процесс резюмируется так:

Spring使用三级缓存解决循环依赖-最终版

2.2.3 Практическое применение кэша L3

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

мы все знаем,SpringизAOPЭта функция достигается за счет создания динамических прокси-классов, и, в конце концов, мы используем экземпляры прокси-классов вместо исходных экземпляров классов. иAOPСоздание прокси-класса находится вinitializeBeanметодpostProcessAfterInitializationПохоронен, давайте посмотрим прямоgetEarlyBeanReferenceиpostProcessAfterInitializationЗакопайте эти две точки (конкретный классAbstractAutoProxyCreator, тогда поговорим оAOPбудет подробно рассмотрено):

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
    implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
    
    private final Map<Object, Object> earlyProxyReferences = new ConcurrentHashMap<>(16);
    // 如果出现循环依赖,getEarlyBeanReference会先被调用到
    public Object getEarlyBeanReference(Object bean, String beanName) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        // 这个时候把当前类放入earlyProxyReferences
        this.earlyProxyReferences.put(cacheKey, bean);
        // 直接返回了一个代理实例
        return wrapIfNecessary(bean, beanName, cacheKey);
    }
    
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        if (bean != null) {
            Object cacheKey = getCacheKey(bean.getClass(), beanName);
            // 注意这个判断,如果出现了循环依赖,这个if块是进不去的
            if (this.earlyProxyReferences.remove(cacheKey) != bean) {
                // 如果没有出现循环依赖,会在这里创建代理类
                return wrapIfNecessary(bean, beanName, cacheKey);
            }
        }
        return bean;
    }
}

это все,SpringУмное использование кеша L3 решает эту проблему в разных случаях. Конечно, если нам нужно разработать свои собственные возможные изменения, такие как проксиbeanПри ссылке на функцию также необходимо следоватьgetEarlyBeanReferenceПогребенная логика метода, обучениеAbstractAutoProxyCreatorтаким образом, который позволяетspringРаботает так, как мы ожидаем.

В-четвертых, проблема, которую не может решить кеш третьего уровня

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

Я только что говорил о реализации большого количества кеша L3 и о том, как он решает проблему циклических зависимостей.

Однако возможно ли решить все циклические зависимости с помощью кэша L3?

Конечно нет, существует особая круговая зависимость, из-заjavaПричина языковой особенности, которая никогда не может быть решена, заключается в круговой зависимости конструктора.

Например, следующие два класса:

public class A {
    private final B b;
    public A(final B b) {
        this.b = b;
    }
}
public class B {
    private final A a;
    public B(final A a) {
        this.a = a;
    }
}

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

Не должно быть одноклассников, которые говорят, что мне так некрасиво:

// 你看,这样不行么~
final A a = new A(new B(a));

Извините, это действительно не работает, вы можете попробовать, если не верите. Грамматически говоря,javaЯзыковая особенность определяет, что неинициализированные переменные запрещены. Мы можем только бесконечно вкладывать куклы:

// 这样明显就没有解决问题,是个无限套娃的死循环
final A a = new A(new B(new A(new B(new A(new B(...))))));

Поэтому проблемы, которые даже мы не можем решить, не должны быть форсированы.springДавайте решим это~

@Service
public class A {
    private final B b;
    public A(final B b) {
        this.b = b;
    }
}
@Service
public class B {
    private final A a;
    public B(final A a) {
        this.a = a;
    }
}

После запуска действительно сообщил об ошибке:

Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

2. SpringНеужели вы ничего не можете сделать с циклическими зависимостями конструктора?

Это,springНеужели вы ничего не можете сделать с этой круговой зависимостью? Не совсем,springи@LazyЭтот большой убийца... просто нужно, чтобы мы сделали небольшое преобразование двух классов прямо сейчас:

@Service
public class A {
    private final B b;
    public A(final B b) {
        this.b = b;
    }
    public void prt() {
        System.out.println("in a prt");
    }
}
@Service
public class B {
    private final A a;
    public B(@Lazy final A a) {
        this.a = a;
    }
    public void prt() {
        a.prt();
    }
}
// 启动
@Test
public void test() {
    applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    B bean = applicationContext.getBean(B.class);
    bean.prt();
}

Было сказано, что она прошла успешно, и студенты могут угадать результат операции:

in a prt

(Студенты также могут попробовать сами~

3. @Lazyпринцип

В этот момент мы должны подумать оspringкак это прошло@LazyЧтобы обойти проблему бесконечной матрешки, которую мы просто не смогли решить.

Поскольку это связано с проблемой синтаксического анализа параметров во время внедрения параметров, которая ранее подробно не обсуждалась, я не буду вести вас шаг за шагом от начала здесь, а сразу перейду к целевому коду.DefaultListableBeanFactory#resolveDependency:

public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
                                @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
	// 跳过...
    // 这个地方是我们获取依赖的地方
    // 尝试获取一个懒加载代理
    Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
        descriptor, requestingBeanName);
    if (result == null) {
        // 如果没获取到懒加载代理,就直接去获取bean实例了,这里最终会调用getBean
        result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
    }
    return result;
}

Давайте посмотрим прямо на этоgetLazyResolutionProxyIfNecessary, этот метод должен получитьLazyProxyМесто это:

public class ContextAnnotationAutowireCandidateResolver extends QualifierAnnotationAutowireCandidateResolver {

    @Override
    @Nullable
    public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, @Nullable String beanName) {
        // 如果是懒加载的,就构建一个懒加载的代理
        return (isLazy(descriptor) ? buildLazyResolutionProxy(descriptor, beanName) : null);
    }
	// 判断是否是懒加载的,主要就是判断@Lazy注解,简单看下就好了
    protected boolean isLazy(DependencyDescriptor descriptor) {
        for (Annotation ann : descriptor.getAnnotations()) {
            Lazy lazy = AnnotationUtils.getAnnotation(ann, Lazy.class);
            if (lazy != null && lazy.value()) {
                return true;
            }
        }
        MethodParameter methodParam = descriptor.getMethodParameter();
        if (methodParam != null) {
            Method method = methodParam.getMethod();
            if (method == null || void.class == method.getReturnType()) {
                Lazy lazy = AnnotationUtils.getAnnotation(methodParam.getAnnotatedElement(), Lazy.class);
                if (lazy != null && lazy.value()) {
                    return true;
                }
            }
        }
        return false;
    }

    protected Object buildLazyResolutionProxy(final DependencyDescriptor descriptor, final @Nullable String beanName) {
        final DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) getBeanFactory();
        // 构造了一个TargetSource
        TargetSource ts = new TargetSource() {
            @Override
            public Class<?> getTargetClass() {
                return descriptor.getDependencyType();
            }
            @Override
            public boolean isStatic() {
                return false;
            }
            @Override
            public Object getTarget() {
                // 再对应的getTarget方法里,才会去正真加载依赖,进而调用getBean方法
                Object target = beanFactory.doResolveDependency(descriptor, beanName, null, null);
                if (target == null) {
                    Class<?> type = getTargetClass();
                    if (Map.class == type) {
                        return Collections.emptyMap();
                    }
                    else if (List.class == type) {
                        return Collections.emptyList();
                    }
                    else if (Set.class == type || Collection.class == type) {
                        return Collections.emptySet();
                    }
                    throw new NoSuchBeanDefinitionException(...);
                }
                return target;
            }
            @Override
            public void releaseTarget(Object target) {
            }
        };
        // 创建代理工厂ProxyFactory
        ProxyFactory pf = new ProxyFactory();
        pf.setTargetSource(ts);
        Class<?> dependencyType = descriptor.getDependencyType();
        if (dependencyType.isInterface()) {
            pf.addInterface(dependencyType);
        }
        // 创建返回代理类
        return pf.getProxy(beanFactory.getBeanClassLoader());
    }
}

одноклассники могутTargetSourceиProxyFactoryЭто незнакомо, ничего страшного, это не мешает нам понять логику.

Из исходного кода мы видим, что для@LazyЗависимость, мы фактически возвращаем прокси-класс (далееLazyProxy) вместо того, чтобы действительно пройтиgetBeanполучить цельbeanинъекция. настоящее приобретениеbeanЛогика заключена вTargetSourceКатегорияgetTargetметод, и этоTargetSourceВ конечном итоге класс используется для генерацииLazyProxy, то мы можем предположить,LazyProxyдолжен держать этоTargetSourceобъект.

Судя по семантике нашей ленивой загрузки, мы действительно используем этоbean(назовите этоbeanЭто свойство вводится/инициализируется только при использовании определенного метода).

Итак, для текущего примера это означает, чтоBКогда он был создан, он не называлсяgetBean("a")Чтобы получить параметры конструктора, но напрямую сгенерироватьLazyProxyсделатьBпараметры конструктора иBПосле этого он фактически вызываетсяAметод будет вызыватьсяTargetSourceсерединаgetTargetПолучатьAэкземпляр, вызовgetBean("a"),В настоящее времяAОн был создан давно, поэтому проблем с циклической зависимостью не будет.

4. Описание псевдокода

Опять же, мы можем использовать псевдокод для описания этого процесса, и псевдокод, который мы напрямую используем для описания статического прокси:

public class A {
    private final B b;
    public A(final B b) {
        this.b = b;
    }
    public void prt() {
        System.out.println("in a prt");
    }
}

public class B {
    private final A a;
    public B(final A a) {
        this.a = a;
    }
    public void prt() {
        a.prt();
    }
}
// A的懒加载代理类
public class LazyProxyA extends A {
    private A source;
    private final Map<String, Object> ioc;
    private final String beanName;
    public LazyProxyA(Map<String, Object> ioc, String beanName) {
        super(null);
        this.ioc = ioc;
        this.beanName = beanName;
    }
    @Override
    public void prt() {
        if (source == null) {
            source = (A) ioc.get(beanName);
        }
        source.prt();
    }
}

Тогда весь процесс инициализации просто описывается как:

Map<String, Object> ioc = new HashMap<>();
void init() {
    B b = new B(new LazyProxyA(ioc, "a"));
    ioc.put("b", b);
    A a = new A((B)ioc.get("b"));
    ioc.put("a", a);
}

Давайте также смоделируем бег:

void test() {
    // 容器初始化
    init();
    B b = (B)ioc.get("b");
    b.prt();
}

Конечно, его можно успешно напечатать:

in a prt

6. Резюме

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

Для круговой зависимости конструктора не может быть решена трехуровневая конструкция кэша, которая принадлежитjavaязыковые ограничения, ноspringобеспечивает использование@Lazyобразом, обойдя это ограничение, сделав циклическую зависимость конструктора в конкретном случае (внедрение в циклическую цепочку помечается@LazyПримечание) также может быть решена.

(Whisper BB, следующее обновление должно быть медленнее...

Создание не простое, пожалуйста, указывайте автора в начале перепечатки: Nuggets@小西子 + ссылка на источник~

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

٩(* ఠO ఠ)=3⁼³₌₃⁼³₌₃⁼³₌₃du ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла. . .

Вот блогер-новичок Сяо Сизи, большие парни видели это, пожалуйста, поставьте лайк в левом верхнем углу~~