Создание не простое, пожалуйста, указывайте автора в начале перепечатки: Nuggets@小西子 + ссылка на источник~
Если вы хотите узнать больше об исходном коде Spring,Нажмите, чтобы перейти к остальной части построчного анализа серии Spring
Введение
Этот пост в блоге в основном рассказывает о нашемspring
Как решить проблему циклических зависимостей.
2. Что такое круговая зависимость
Прежде всего, нам нужно прояснить, что такое циклическая зависимость? Вот простой пример:
@Service
public class A {
@Autowired
private B b;
}
@Service
public class B {
@Autowired
private A a;
}
Взяв этот пример, мы объявляемa
,b
дваbean
,иa
необходимо ввестиb
,b
необходимо ввестиa
.
В сочетании с нашей предыдущей записью в блогеbean
Знание жизненного цикла, давайте смоделируем эти дваbean
Созданный процесс:
Без кешированного дизайна наша пунктирная ветвь никогда не будет достигнута, что приведет к неразрешимой проблеме циклической зависимости....
Трехуровневая конструкция кэш-памяти
1. Решайте циклические зависимости самостоятельно
Теперь, если бы мы былиspring
Архитектор, как нам решить эту проблему циклической зависимости?
1.1. Дизайн процесса
Прежде всего, если мы хотим решить эту проблему, наша цель должна заключаться в том, чтобы сократить предыдущий каскадный процесс бесконечного создания, то есть наш процесс должен стать следующим:
То есть нам нужноB
После создания экземпляра введитеA
когда ты сможешь получитьA
instance, чтобы прервать бесконечное создание экземпляров.
и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
Процесс:
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
, если есть только два уровня кеша:
Это приведет к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
Экземпляр тот же.
Весь процесс резюмируется так:
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 ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла ла. . .
Вот блогер-новичок Сяо Сизи, большие парни видели это, пожалуйста, поставьте лайк в левом верхнем углу~~