Автор: Брат Сяофу
Блог:bugstack.cn
Осаждайте, делитесь, растите и позвольте себе и другим получить что-то! 😄
Введение
延迟满足能给你带来什么?
В колледже четыре года, но почти всем им трудно найти хорошую работу до окончания учебы, особенно в сфере разработки программного обеспечения, с которой я хорошо знаком. в учебное заведение, чтобы изучать программирование.Технология может пойти и найти работу.Кажется, я вообще ничему не научился за последние несколько лет в школе!
Лично это может быть из-за того, что мне нравилось программировать в школе, и я слышал от брата и сестры, что найти работу после выпуска непросто, а также узнал об уровне социальных требований к навыкам развития программиста. То есть, получив эти новости и добавив, что я готов бросить, я поставил себе небольшую цель, которую я могу выполнять каждый день:
红尘世界几个王,我自不服迎头上。
日敲代码两百行,冲进世界五百强。
Хахаха, точно так же, как 200 строк кода в день, 6000 строк в месяц, 60000 строк в год, 180000 строк кода после трех лет практики, новый стажер набирает почти 200000 строк кода, почти я уже могу выполнять все виды простых задачи очень умело.Добавив реальную отключение всего процесса проекта во время стажировки, я найду正经
Работа по развитию по-прежнему очень проста.
Легкость найти работу в это время исходит от вашей длительной учебы и осадков, но если вы не приложите этих усилий, вы можете сильно занервничать после выпуска, и в конце концов у вас не будет иного выбора, кроме как отправиться в какое-нибудь учреждения для повторного обучения.
2. Вопросы для интервью
谢飞机,小记!
, Раньше мне казалось, что Spring — ничто, я читал getBean, боже мой!
спасибо, самолет: Интервьюер, я недавно смотрел getBean от Spring и обнаружил, что здесь есть много вещей, и одна из них заключается в решении циклических зависимостей.
интервьюер: Вау, как Spring разрешает циклические зависимости?
спасибо, самолет: Ну это решается заблаговременным выставлением объектов через кеш третьего уровня.
интервьюер: Да, какая информация об объектах хранится в этих трех кешах?
спасибо, самолет: Кэш первого уровня хранит завершенные объекты, также называемые готовыми объектами. Кэш второго уровня хранит объекты-полуфабрикаты, то есть объекты, свойства которых не были заданы. Кэш третьего уровня хранитObjectFactory<?>
тип лямбда-выражения, который используется для обработки циклических зависимостей АОП.
интервьюер: Да, спасибо за подготовку! Если нет кеша L3, только L2 или L1, можно ли разрешить циклические зависимости?
спасибо, самолет: На самом деле я прочитал информацию, и это можно решить, но Spring должен гарантировать несколько вещей.Только процесс обработки кеша первого уровня не может быть разделен, и сложность возрастет. -готовый объект может иметь исключение нулевого указателя. Отделение полуфабриката от готового объекта также является более элегантным, простым и легко расширяемым. Кроме того, две основные функции Spring включают не только IOC, но и AOP, то есть метод, основанный на расширении байт-кода, где он должен храниться, и кэш третьего уровня является наиболее важным, круговая зависимость, которую необходимо решить, является обработка АОП, но если создание прокси-объекта АОП расширено, кэш второго уровня также может быть решен. Однако это нарушает принцип Spring по созданию объектов, Spring предпочитает инициализировать все обычные bean-компоненты и обрабатывать инициализацию прокси-объектов.
интервьюер: Самолет, неплохо, многому научился в этот раз. Тогда спросите просто, пробовали ли вы решение циклической зависимости?
спасибо, самолет: О, нет, никакой практики! ! ! Вы действительно должны попробовать это.
3. Что такое круговая зависимость?
1. Описание проблемы
Понимание природы проблемы и последующий анализ проблемы часто более способствует более глубокому пониманию и исследованию проблемы. Поэтому, прежде чем мы проанализируем исходный код Spring на наличие циклических зависимостей, мы должны сначала понять, что такое циклические зависимости.
- Существует три типа циклических зависимостей: самостоятельные, взаимные циклические зависимости и множественные группы циклических зависимостей.
- Но вне зависимости от количества циклических зависимостей суть циклических зависимостей одна и та же. То есть ваше полное творение зависит от меня, и мое полное творение тоже зависит от вас, но мы не можем развязать друг друга, что в итоге приводит к провалу создания зависимости.
- Таким образом, Spring предоставляет решения для внедрения циклических зависимостей сеттера в дополнение к внедрению конструктора и внедрению прототипа. Тогда мы также можем сначала попробовать такую зависимость, и как ее решить, если мы разберемся с ней сами.
2. Представление проблемы
public class ABTest {
public static void main(String[] args) {
new ClazzA();
}
}
class ClazzA {
private ClazzB b = new ClazzB();
}
class ClazzB {
private ClazzA a = new ClazzA();
}
- Этот код представляет собой первоначальный вид циклической зависимости. У вас есть я, у меня есть вы, и при выполнении он сообщит об ошибке.
java.lang.StackOverflowError
- Такой циклический код зависимости не может быть решен.Когда вы видите, что Spring предоставляет get/set или аннотации, причина, по которой это может быть решена, заключается в том, что сначала было выполнено определенное разделение. Разделите создание классов и заполнение атрибутов, сначала создайте полуфабрикаты бинов, а затем обработайте заполнение атрибутов, чтобы завершить предоставление готовых бинов.
3. Решение проблем
В этой части кода есть основная цель. Давайте решим циклическую зависимость самостоятельно. Решение выглядит следующим образом:
public class CircleTest {
private final static Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
public static void main(String[] args) throws Exception {
System.out.println(getBean(B.class).getA());
System.out.println(getBean(A.class).getB());
}
private static <T> T getBean(Class<T> beanClass) throws Exception {
String beanName = beanClass.getSimpleName().toLowerCase();
if (singletonObjects.containsKey(beanName)) {
return (T) singletonObjects.get(beanName);
}
// 实例化对象入缓存
Object obj = beanClass.newInstance();
singletonObjects.put(beanName, obj);
// 属性填充补全对象
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
Class<?> fieldClass = field.getType();
String fieldBeanName = fieldClass.getSimpleName().toLowerCase();
field.set(obj, singletonObjects.containsKey(fieldBeanName) ? singletonObjects.get(fieldBeanName) : getBean(fieldClass));
field.setAccessible(false);
}
return (T) obj;
}
}
class A {
private B b;
// ...get/set
}
class B {
private A a;
// ...get/set
}
-
Этот код предоставляет два класса, A и B, которые зависят друг от друга. Но зависимости в двух классах заполняются сеттерами. То есть единственный способ избежать двух классов в начале создания — это не сильно зависеть от другого объекта.
-
getBean
, является основным содержанием всего решения циклических зависимостей. После создания A он зависит от B при заполнении свойств. Затем создайте B. Когда B начинает заполняться, выясняется, что он зависит от A, но в это время полуфабрикат A был сохранен в кэше дляsingletonObjects
находится внутри, поэтому B может быть создан обычным образом, а A также создается полностью с помощью рекурсии.
Четыре, анализ исходного кода
1. Расскажите о деталях
Из вышеприведенного примера мы, наверное, знаем, что когда А и В зависят друг от друга, после создания А атрибут Б заполняется, а Б продолжает создаваться, а затем, когда атрибут А заполняется, его можно получить из кеша. , следующее:
Так как же выглядит эта проблема решения циклических зависимостей в Spring? Расширьте детали!
Несмотря на то что, основной принцип решения циклических зависимостей тот же, но он станет более сложным при поддержке функций IOC и AOP во всем Spring.Весь процесс работы с циклическими зависимостями Spring выглядит следующим образом;
- Вышеизложенное касается процесса приобретения объекта с циклическими зависимостями в Spring, чего вы и хотите.
说说细节
- На первый взгляд процессов очень много, но это в основном куски кода, которые надо пройти при отладке кода, очень удобно получить этот процесс выполнения и потом его отлаживать.
2. Обработка
Анализ исходного кода случаев, рассматриваемых в этой главе, был обновлен на github:GitHub.com/заместитель комиссара/… - interview-31
Ниже приведена операция получения Bean-компонентов для зависимостей AB в модульном тестировании, основное внимание уделяется исходному коду, который следует за getBean;
@Test
public void test_alias() {
BeanFactory beanFactory = new ClassPathXmlApplicationContext("spring-config.xml");
Bean_A bean_a = beanFactory.getBean("bean_a", Bean_A.class);
logger.info("获取 Bean 通过别名:{}", bean_a.getBean_b());
}
org.springframework.beans.factory.support.AbstractBeanFactory.java
@Override
public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
return doGetBean(name, requiredType, null, false);
}
- После входа из getBean операция получения бина войдет в doGetBean.
- Причина такого уровня упаковки в том, что doGetBean имеет много перегруженных методов с разными входными параметрами, что удобно для внешних операций.
метод doGetBean
protected <T> T doGetBean(
final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
throws BeansException {
// 从缓存中获取 bean 实例
Object sharedInstance = getSingleton(beanName);
// mbd.isSingleton() 用于判断 bean 是否是单例模式
if (mbd.isSingleton()) {
// 获取 bean 实例
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
try {
// 创建 bean 实例,createBean 返回的 bean 实例化好的
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
}
}
});
// 后续的处理操作
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
// ...
// 返回 bean 实例
return (T) bean;
}
- Как видно на блок-схеме анализа исходного кода, эта часть заключается в том, чтобы сначала определить, есть ли экземпляр объекта из getSingleton.Для первой записи не должно быть объекта, поэтому продолжайте идти вниз.
- После оценки синглтона mbd.isSingleton() начните создавать createBean на основе упаковки ObjectFactory.После ввода основной логикой является начало выполнения операции doCreateBean.
метод doCreateBean
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
throws BeanCreationException {
// 创建 bean 实例,并将 bean 实例包装到 BeanWrapper 对象中返回
instanceWrapper = createBeanInstance(beanName, mbd, args);
// 添加 bean 工厂对象到 singletonFactories 缓存中
addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
// 获取原始对象的早期引用,在 getEarlyBeanReference 方法中,会执行 AOP 相关逻辑。若 bean 未被 AOP 拦截,getEarlyBeanReference 原样返回 bean。
return getEarlyBeanReference(beanName, mbd, bean);
}
});
try {
// 填充属性,解析依赖关系
populateBean(beanName, mbd, instanceWrapper);
if (exposedObject != null) {
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
}
// 返回 bean 实例
return exposedObject;
}
- Метод doCreateBean включает в себя большое количество содержимого, но основная его задача заключается в создании экземпляра, добавлении кэша и, наконец, выполнении заполнения атрибутов.Заполнение атрибутов заключается в заполнении классов, задействованных в каждом поле атрибутов компонента.
-
createBeanInstance
, создайте экземпляр bean-компонента и оберните экземпляр bean-компонента в объект BeanWrapper для возврата -
addSingletonFactory
, добавьте объект bean factory в кеш singletonFactories -
getEarlyBeanReference
, получите раннюю ссылку на исходный объект, в методе getEarlyBeanReference будет выполнена логика, связанная с АОП. Если компонент не перехвачен AOP, getEarlyBeanReference возвращает компонент как есть. -
populateBean
, заполнить свойства, разрешить зависимости. То есть, начиная с этого, чтобы найти атрибут B в экземпляре A, затем создать экземпляр B и, наконец, вернуть его.
Кэш L3 getSingleton
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 从 singletonObjects 获取实例,singletonObjects 是成品 bean
Object singletonObject = this.singletonObjects.get(beanName);
// 判断 beanName ,isSingletonCurrentlyInCreation 对应的 bean 是否正在创建中
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 从 earlySingletonObjects 中获取提前曝光未成品的 bean
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 获取相应的 bean 工厂
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 提前曝光 bean 实例,主要用于解决AOP循环依赖
singletonObject = singletonFactory.getObject();
// 将 singletonObject 放入缓存中,并将 singletonFactory 从缓存中移除
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
-
singletonObjects.get(beanName)
, получить экземпляры из singletonObjects, которые являются готовыми bean-компонентами -
isSingletonCurrentlyInCreation
, чтобы определить, создается ли bean-компонент, соответствующий beanName и isSingletonCurrentlyInCreation. -
allowEarlyReference
, получить незавершенные bean-компоненты раннего доступа от EarlySingletonObjects -
singletonFactory.getObject()
, Предоставление экземпляров bean-компонентов заранее, в основном используется для решения циклических зависимостей AOP
Подводить итоги, представляет собой кодовый процесс для работы с циклическими зависимостями. Контент, извлекаемый в этой части, в основном является основным контентом, а не вынимается из всех длинных историй. При отладке вы будете задействовать больше, и вы должны действовать в соответствии с блок-схему, насколько это возможно Отладьте несколько раз.
3. Разрешение зависимостей
Подводя итог, мы попытались решить круговую зависимость самостоятельно и изучили основной принцип решения круговой зависимости. Он также анализирует процесс обработки круговой зависимости, решаемой Spring, и анализ основного исходного кода. Далее мы обобщим различные процессы обработки трехуровневого кеша, что является кратким и удобным для понимания каждым.
1. Можно ли решить кеш первого уровня?
- На самом деле, только один уровень кеша не в состоянии решить циклические зависимости, как в примере, который мы сделали сами.
- А вот в Spring, если обрабатывать как в нашем примере, станет очень хлопотно, а еще могут быть проблемы с NPE.
- Поэтому по процессу обработки кода в Spring анализируем процесс хранения готовых бинов в кеше первого уровня, что не может решить проблему циклических зависимостей. Поскольку создание готового продукта A зависит от B, а создание готового продукта B зависит от A, когда атрибуты B должны быть завершены, A еще не создан, поэтому будет бесконечный цикл.
2. Можно ли решить кэш L2?
- С кешем второго уровня вообще проще справиться с этим делом, один кеш используется для хранения готовых объектов, а другой кеш – для хранения полуфабрикатов.
- А сохраняет объект-полуфабрикат в кэше после создания объекта-полуфабриката, а затем дополняет атрибуты объекта А, которые зависят от Б.
- Б продолжает творить, а созданный полуфабрикат тоже помещается в тайник.При добавлении атрибута А объекта его можно получить из кэша полуфабрикатов.Теперь Б-полный объект, а потом А также является полным объектом, подобным рекурсивной операции.
3. Что решает кеш L3?
- С кешем второго уровня может быть решена зависимость Spring.Как может быть кеш третьего уровня? Фактически, мы также упоминали в предыдущем анализе исходного кода, кеш третьего уровня в основном предназначен для решения характеристик Spring AOP. АОП сам по себе является усовершенствованием метода и
ObjectFactory<?>
Тип лямбда-выражения, и принцип Spring не хочет предварительно создавать этот тип bean-компонента, поэтому его необходимо хранить в кеше третьего уровня для обработки. - Фактически общий процесс обработки аналогичен, за исключением того, что когда B заполняет атрибут A, он сначала запрашивает кеш готового продукта, затем кеш полуфабриката и, наконец, проверяет, есть ли класс одноэлементного проекта в кеше третьего уровня. . После окончательного получения вызывается метод getObject для возврата прокси-ссылки или исходной ссылки.
- На данный момент проблема трехуровневого кэша, вызванная Spring AOP, решена.Зависимости АОП, рассматриваемые в этой главе, содержат примеры исходного кода, которые можно отлаживать.
V. Резюме
- Обзор этой статьи в основном начинается с практических примеров, чтобы помочь всем получить общее представление о циклических зависимостях, и примерах его решений, которые можно использовать, чтобы продолжение решения Spring для циклических зависимостей не было таким незнакомым.
- Вы также можете видеть, что кеш третьего уровня не нужен, но он необходим по принципу собственного создания Spring. Если вы можете загрузить исходный код Spring, чтобы внести изменения в эту часть кода, заранее создать АОП-объекты и сохранить их в кэше, то кэш второго уровня также может решить проблему циклической зависимости.
- Что касается циклических зависимостей, это может быть не очень хороший метод кодирования.Если вы все же хотите использовать более разумные шаблоны проектирования, чтобы избежать циклических зависимостей в своих собственных программах, эти методы могут увеличить объем кода, но они будут более удобными для обслуживания.Конечно, это не обязательно, это может прийти в соответствии с вашими потребностями.
6. Рекомендации серии
- Вы говорите, как запихнуть Beans в контейнер Spring?
- В чем особенности Spring IOC, не могу прочитать исходный код!
- Полный анализ исходного кода getBean в Spring
- После долгого ожидания книга брата Сяо Фу «Повторное изучение шаблонов проектирования Java» наконец-то опубликована в цвете и на бумаге!
- Ошибка, позвольте мне обнаружить .AJ (конус) в мире Java!