В этот период времени друзья, ушедшие на собеседование, часто встречаются с интервьюером и спрашивают:
1. Можете ли вы кратко объяснить, как решаются весенние круговые зависимости?
2. Нельзя использовать кеш первого уровня?
3. Нельзя использовать кеш второго уровня?
Многие мелкие партнеры тоже полупоняты, причина в том, что они сами не корректировали исходный код, и не анализировали его шаг за шагом, сегодня мы объясним эту проблему понятно. Код в этом разделе также будет отправлен на github, чтобы все могли отладить его, используяgradle
О, я надеюсь, что вы, ребята, обратите внимание~
1. Причины циклических зависимостей
Кратко опишите что такое круговая зависимость.Короче это русская матрешка.У вас есть я,а у меня есть вы.
Круг и треугольник соответственно представляют два объекта-бина. Круг относится к треугольнику, а треугольник относится к кругу. Это результат круговой зависимости.
В реальных бизнес-сценариях это довольно распространено, например, две службы, одна для пользователей, а другая для заказов.
1. Пользователи будут запрашивать информацию о своих заказах через свою учетную запись.В настоящее время решение должно вызывать службу заказов в пользовательской службе, чтобы найти список заказов конкретного пользователя.
2. Продавцы будут группировать всю информацию о заказах в соответствии с пользователями.В это время им необходимо вызвать службу пользователей в службе заказов, чтобы найти подробную информацию о пользователях, соответствующих пакету заказов.
Поскольку границы бизнеса иногда трудно четко очертить, это будет сопровождаться созданием таких циклических зависимостей. Но одноклассники могут сказать, но когда я @Autowired, я настолько свободен, раскован и никогда не сообщаю об ошибках, потому что весна решила эту проблему за нас.
2. Подготовка кода
Структура каталога кода выглядит следующим образом
1.InstanceA
public class InstanceA {
private InstanceB b;
public InstanceB getB() {
return b;
}
public void setB(InstanceB b) {
this.b = b;
}
}
2.InstanceB
public class InstanceB {
private InstanceA a;
public InstanceA getA() {
return a;
}
public void setA(InstanceA a) {
this.a = a;
}
}
3.aa.xml файл конфигурации
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd">
<!-- 1. 使用property初始化Bean属性 -->
<bean id="a" class="com.lyf.spring.config.InstanceA">
<property name="b" ref="b"></property>
</bean>
<bean id="b" class="com.lyf.spring.config.InstanceB">
<property name="a" ref="a"></property>
</bean>
</beans>
4. основная функция
public static void main(String[] args) {
ApplicationContext xc = new ClassPathXmlApplicationContext("aa.xml");
InstanceA instanceA = xc.getBean(InstanceA.class);
}
Три, схема + исходный код
Я долго думал, как это лучше описать словами.Поразмыслив, я должен рассмотреть ключевые моменты шаг за шагом со всеми, и, наконец, выложить блок-схему для всех.Если описание непонятно, вы можете используйте блок-схему для отладки.
首先需要大家清楚的是三级缓存其实指的就是3个map,这三个名字请大家务必记牢
Приступим к отладке
1. Найдите метод входа refresh()
оказатьсяrefresh()метод, это точка входа для нашего экземпляра bean-компонента, а также точка входа для нашей круговой зависимости отладки на этот раз
2. Введите refresh(), чтобы найти finishBeanFactoryInitialization(beanFactory), которая выполняет действие создания экземпляра.
3. Введите finishBeanFactoryInitialization(beanFactory), чтобы найти beanFactory.preInstantiateSingletons().
4. getBean(beanName) находится внутри метода preInstantiateSingletons()
1. Выполните создание и инициализацию InstanceA и InstanceB соответственно с помощью цикла
2. После серии суждений введите логику else, чтобы найти getBean(beanName)
//判断是否是抽象类/单例/懒加载
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
//判断是否继承FactoryBean
if (isFactoryBean(beanName))
5. Найдите doGetBean через getBean(beanName) и введите его класс реализации.
Входя в GetSingleton (BeanName), этот метод требует, чтобы все помнили, что мы создаем экземпляры и инициализируем объекты, которые будут считываться со всех уровней кэша.
/** Names of beans that are currently in creation */
private final Set<String> singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<>(16));
Эту коллекцию необходимо запомнить, чтобы записать имя компонента, находящегося в настоящее время на этапе создания.Поскольку мы не вошли в этап создания, когда создаем экземпляр a, мы просто идем в кеш, чтобы попытаться получить a, и если он существует, мы получит его напрямую. Сейчас не существует, поэтому возвращает null
6. Поскольку a не существует в кэше первого уровня и не находится на этапе создания, необходимо инициировать создание экземпляра a в это время.
7. Войдите в getObject() в анонимный внутренний класс, createBean(beanName, mbd, args)
8. Найдите метод doCreateBean(beanName, mbdToUse, args)
9. Введите doCreateBean(beanName, mbdToUse, args)
Найдите следующий метод, функция этого метода состоит в том, чтобы поместить a и его анонимный внутренний класс в кеш третьего уровня.
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
1. Определите, пуста ли входящая ObjectFactory
2. Заблокировать кеш первого уровня
3. Определяем есть ли в кеше первого уровня, очевидно его нет
4. Поместите a и его ObjectFactory в singletonFactories, который является кешем третьего уровня.
На данный момент в нашем кеше третьего уровня наконец-то что-то есть.Текущее состояние выглядит следующим образом
Бин а в настоящее время находится в этом состоянии, поэтому следующим шагом будет заполнение элементами в
10 Здесь мы поместили a в кеш L3 и начали заполнять bean-компонент
найти ключевую строку
Здесь мы немного прыгаем, потому что в середине задействовано больше операций проверки.
ВходитьpopulateBean
найти последнюю строку
applyPropertyValues(beanName, mbd, bw, pvs);
Входите без раздумий, не волнуйтесь ни о чем, просто бегите до конца и бегите к этому циклу for
11. Перейти к кешу, чтобы узнать, существует ли b
Найдите эту строку кода ключа
Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
Продолжайте бежать, и мы обнаружим, что оказываемся в исходном состоянии.doGetBeanв
Чувствуется дежа вю и очень знакомо, и нашелся товарищ.
Ну, здесь мы обнаруживаем, что когда создается экземпляр a, мы обнаруживаем, что bean-компонент b должен быть заполнен, но, поскольку b не существует, мы снова создаем bean-компонент b.
Итак, студенты, очень важно, в чем разница, когда мы создаем экземпляр b по одному и тому же пути? студенты думают о
Когда мы создали раньше, мы поместили на шаге 9 кэш третьего уровня, а затем пошли вниз.
12. Точно также положим b в кеш L3
С этим пунктом согласны все, и теперь такая картинка появится в кэше третьего уровня
13. Переломный момент — когда экземпляр b заполнен
Отслеживая весь путь, я не знаю, помните ли вы еще эту картинку, и заполняйте объекты один за другим через цикл for
В этом методе мы возвращаемся к кешу, чтобы выяснить, существует ли a.Очевидно, что a уже существует в кеше третьего уровня.
Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
введите сноваgetSingleton, знакомый вкус
Но на данный момент есть две разные точки
1. В это время, когда b обращается к кэшу, чтобы найти a, a не существует в кэше первого уровня, но в это время a уже находится в кэше третьего уровня, и состояние находится в процессе создания
2.a уже существует в кеше третьего уровня singletonFactories
Как мы упоминали ранее, то, что помещается в кеш L3,
(a, () -> getEarlyBeanReference(beanName, mbd, bean))
Так что выполняйте здесьgetEarlyBeanReferenceЭтот метод выполняет некоторые операции
При этом удалить a из кеша L3 и поместить его в кеш L2.Распределение в кеше L3 меняется следующим образом
14. Получить a из кеша, а b в это время очень доволен
На этом этапе мы должны вернуться к коду, который мы ввели ранее, который является строкой кода на шаге 11.
Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
Вы можете видеть, что экземпляр a возвращается
Мы также получили конкретизированный объект a, а затем мы можем инициализировать b
15 Не паникуйте, идите вниз, впереди свет
мы снова здесь
Мы не выполняли заполнение бобов раньше, вы можете видеть, что у нас уже есть a в b в это время
Далее следует выполнить инициализацию b
16. Что сделала инициализация?Мы пока не будем подробно ее отслеживать, а продолжим спускаться до этого шага
Мы смело предполагаем, что b в это время завершил действие инициализации, и этот шаг напрямую выбрасывает b из кеша третьего уровня в кеш первого уровня.
Запомните этот метод, и он будет использоваться позже.На данный момент распределение нашего кеша нужно снова настроить, как показано ниже.
17. Возьми всех на память
Одноклассники, мы начали с шага 1 до конца шага 16. Вы еще помните, что мы делали изначально?
Вы ученый? Или народная полиция?
Эй, без шуток, изначально мы хотели получить объект a, а до сих пор мы создавали экземпляр и инициализировали b,Итак, мы снова возвращаемся к основной истории и заполняем объект.
Вот где начинается мечта
Вы можете видеть, что объект b такой длинный
Дальнейшие действия должны быть вам понятны.
1. Заполните
2. Инициализировать
3. Переместите a из кеша L2 в кеш L1.
Следовательно, на этом распределение памяти кэша третьего уровня завершено.
Итак, наш объект a помещен в кеш первого уровня и может использоваться всеми, давайте посмотрим, как выглядит a
Как и положено включен режим Русская матрешка.Заинтересованные ученики могут нажать и посмотреть.Будет ли конец?
18. Итак, экземпляр a был создан и инициализирован, вы все еще помните цикл for, с которого мы начали?
Это наш самый внешний цикл.В настоящее время нам также нужно создать экземпляр и инициализировать объект b, но можно предвидеть, что, поскольку b уже существует в кеше первого уровня, мы можем напрямую сохранить его из кеша первого уровня в этот момент. b Достаньте его и используйте напрямую~~
Итак, все понимают весь процесс, верно? Наконец, вот окончательное непобедимое поле давления блок-схемы, записанное в течение всего процесса отладки.
Я надеюсь, что мои друзья будут отлаживать его шаг за шагом, просто смотрите, не практикуя поддельный дескриптор, и, наконец, не забудьте поставить лайк 👍 + подписаться~~