предисловие
В сочетании с процессом загрузки Spring Bean в этой статье анализируется циклическая зависимость одноэлементного конструктора Spring и циклическая зависимость Field. Для циклических зависимостей конструктора Spring в настоящее время не может решить их; для циклических зависимостей Field Spring устраняет зависимости, заранее открывая экземпляры bean-компонентов и кэшируя bean-компоненты на разных этапах (кэш уровня 3). Об этом тоже много статей в интернете, но автор хочет найти другой путь от жизненного цикла кеша и множественных циклических зависимостей бина, и глубоко понять суть Spring Ioc. Это второй пост в блоге, я надеюсь выработать хорошую привычку сортировать заметки.
Что такое циклическая зависимость?
Циклическая зависимость, проще говоря, представляет собой циклическую ссылку, два или более компонента удерживают друг друга, образуя замкнутый цикл. Например, А зависит от В, В зависит от А, и между ними образуется круговая зависимость, или А зависит от В, В зависит от С, а С зависит от А. Эту зависимость можно описать диаграммой.
Как разрешить циклические зависимости?
Теоретическая основа циклической зависимости Spring на самом деле заключается в том, что Java основана на передаче по ссылке Когда мы получаем ссылку на объект, поле или свойство объекта можно установить позже. Далее это будет объяснено циклическими зависимостями конструктора и круговыми зависимостями поля.
Процесс загрузки Spring Bean
Прежде чем анализировать циклические зависимости, давайте рассмотрим процесс загрузки бина в Spring mvc.
1) Создайте экземпляр ServletContext при запуске проекта и сохраните значение пары ключ-значение в параметре контекста в ServletContext;
2) Когда вы создаете Context LoaderListener, поскольку прослушиватель реализует интерфейс ServletContextListener и предоставляет ServletContextListener при запуске мониторинга веб-контейнера, прослушиватель событий и уничтожается после инициализации события ServletContext перед прослушивателем ServletContext; поэтому реализация contextLoaderListener по умолчанию contextInitialized и два метода contextDestroyed ;contextInitialized из контейнера инициализации запускается;
3) Сначала создайте экземпляр WebApplicationContext.Если значение атрибута contextClass настроено, это означает, что настроен соответствующий класс реализации контейнера WebApplicationContext.Если он не настроен, по умолчанию создается объект экземпляра XmlWebApplicationContext;
4) Получите файл конфигурации, загруженный контейнером, через contextConfigLocation, выполните цикл по configLocation и вызовите метод loadDefinitionBeans класса AbstractBeanDefinitionReader для синтаксического анализа и регистрации Процесс синтаксического анализа в основном включает следующие шаги:
-
Преобразуйте xml в объект Document и, наконец, вызовите метод parseBeanDefinitions в DefaultBeanDefinitionDocumentReader;
-
Разберите узел Node в документе, если это тег bean-компонента по умолчанию, зарегистрированный напрямую (вызывается метод org.springframework.beans.factory.support.BeanDefinitionReaderUtils#registerBeanDefinition, если это настраиваемый тег пространства имен, после получения пространства имен получите Соответствующий NamespaceHandler (полученный из файла свойств meta-inf/spring.handlers в пакете jar весной) вызывает свой метод parse для синтаксического анализа;
-
Вызовите метод инициализации NamespaceHandler, чтобы зарегистрировать синтаксический анализатор, соответствующий каждому тегу;
-
Получите соответствующий синтаксический анализатор в соответствии с именем тега и проанализируйте конкретный тег; Шаги синтаксического анализа и регистрации, наконец, помещают проанализированное BeanDefinition в карту, и в это время не выполняется никаких инъекций.
5) Создать экземпляр Запись представляет собой метод AbstractApplicationContext#finishBeanFactoryInitialization, который принимает метод getBean в качестве записи и сначала получает его из кеша. Если он недоступен, создайте экземпляр Bean с помощью фабричного метода или конструктора. Для конструктора мы можем указать параметры конструкции.
6) Внедрение зависимостей (populateBean) При сборке зависимостей bean-компонентов большинство проектов используют аннотации @Autowired, org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues, этот процесс может выполнять рекурсивное внедрение зависимостей; наконец, поля задаются в bean-компоненты посредством отражения.
7) Инициализация: выполните некоторые настройки для bean-компонента через постпроцессор, например, определите, следует ли реализовывать initializingBean, вызывает ли реализация метод afterPropertiesSet, создаете прокси-объект и т. д.;
8) Наконец, установите корневой контекст в свойство servletContext;
Основной процесс загрузки компонентов Spring сконцентрирован на трех шагах 5, 6 и 7, соответствующих трем методам createBean, populateBean и initializeBean.Круговая зависимость создается двумя методами createBean и populateBean.
Циклическая зависимость конструктора
Для циклической зависимости конструктора зависимость создается на экземпляре Bean, то есть в методе createBean. Невозможно разрешить эту циклическую зависимость с помощью Spring.
<bean id = "aService" class="com.yfty.eagle.service.AService">
<constructor-arg index="0" ref="bService"/>
</bean>
<bean id = "bService" class="com.yfty.eagle.service.BService">
<constructor-arg index="0" ref="cService"/>
</bean>
<bean id = "cService" class="com.yfty.eagle.service.CService">
<constructor-arg index="0" ref="aService"/>
</bean>
Процесс реализации
анализировать:При синтаксическом анализе элемента bean-компонента в xml для генерации объекта BeanDefination узел конструктора-аргумента в конечном итоге будет назначен карте конструктораArgumentValues в качестве параметра конструктора. При разборе объекта конструктора при создании AService обнаруживается ссылка на BService, в этот раз при создании BService обнаруживается ссылка на CService, а CService ссылается на AService. При создании экземпляра bean-компонента beanName будет храниться в коллекции singletonsCurrentlyInCreation.Когда найден дубликат, это означает, что существует циклическая зависимость и выдается исключение.Таким образом, Spring не может разрешить циклическую зависимость конструктора./**
* Callback before singleton creation.
* <p>The default implementation register the singleton as currently in creation.
* @param beanName the name of the singleton about to be created
* @see #isSingletonCurrentlyInCreation
*/
protected void beforeSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}
Свойство поля или круговая зависимость свойств
Настройте атрибут свойства в xml-файле или используйте аннотацию @Autowired, по сути, эти два относятся к одной категории. Далее анализируется, как Spring устраняет зависимости между bean-компонентами с помощью механизма раннего доступа + кэш L3.
Кэш L3
/** Cache of singleton objects: bean name --> bean instance */
一级缓存:维护着所有创建完成的Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
/** Cache of early singleton objects: bean name --> bean instance */
二级缓存:维护早期暴露的Bean(只进行了实例化,并未进行属性注入)
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
/** Cache of singleton factories: bean name --> ObjectFactory */
三级缓存:维护创建中Bean的ObjectFactory(解决循环依赖的关键)
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
Исключение зависимости
Быть 由Spring Bean创建的过程,首先Spring会尝试从缓存中获取,这个缓存就是指singletonObjects,主要调用的方法是getSingleton;如果缓存中没有,则调下Spring bean创建过程中,最重要的一个方法doCreateBean。
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 从一级缓存中获取
Object singletonObject = this.singletonObjects.get(beanName);
// 如果一级缓存没有,并且bean在创建中,会从二级缓存中获取
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
// 二级缓存不存在,并且允许从singletonFactories中通过getObject拿到对象
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
// 三级缓存不为空,将三级缓存提升至二级缓存,并清除三级缓存
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
анализировать:Spring сначала пытается получить его из singletonObjects (кэш первого уровня), если не может получить и объект создается, пытается получить его из EarlySingletonObjects (кэш второго уровня), получается кэш третьего уровня, т.е. через singletonFactory.getObject(). Если вы его получили, сохраните его в кеше L2 и очистите кеш L3.
Если в кэше нет объекта bean-компонента, Spring создаст объект bean-компонента, заранее выставит экземпляр bean-компонента и добавит его в кэш.
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
throws BeanCreationException {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
......
if (instanceWrapper == null) {
//这个是实例化Bean的方法,会调用构造方法,生成一个原始类型的Bean
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
// 提前曝光这个实例化的Bean,方便其他Bean使用
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
// 满足单例 + allowCircularReferences默认为true + bean在singletonsCurrentlyInCreation集合中时,earlySingletonExposure为true
if (earlySingletonExposure) {
if (logger.isDebugEnabled()) {
logger.debug("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
// 将bean加入三级缓存中
addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}
});
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
// 属性注入,这里可能发生循环依赖
populateBean(beanName, mbd, instanceWrapper);
if (exposedObject != null) {
// 初始化bean
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
}
// 由于AService提前暴露,会走这段代码
if (earlySingletonExposure) {
// 从二级缓存中拿出AService(这个对象其实ObjectFactory.getObject()得来的,可能是个包装类,
而exposedObject可能依然是实例化的那个bean,这时为保证最终BService中的AService属性与AService本身
持有的引用一直,故再次进行exposedObject的赋值操作,保证beanName对应实例唯一性。)
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
}
// ...........
return exposedObject;
}
анализировать:Когда экземпляр bean-компонента получен с помощью конструкции без параметров, Spring выставит его заранее, то есть добавит его в кеш третьего уровня перед внедрением свойств после создания экземпляра. Ниже в качестве примера рассматривается взаимная зависимость AService и BService для иллюстрации процесса исключения зависимостей.
- После того, как экземпляр AService создан, он открывается заранее перед внедрением свойств и добавляется в singletonFactories в кэше третьего уровня для использования другими bean-компонентами;
- AService внедряет BService через populateBean, получает BService из кеша, обнаруживает, что кеша нет, и начинает создавать экземпляр BService;
- Экземпляр BService также будет представлен заранее перед внедрением атрибута и добавлен в кеш третьего уровня.В настоящее время в кеше третьего уровня находятся AService и BService;
- Когда BService выполняет инъекцию атрибутов, он обнаруживает, что есть ссылка на AService.В это время при создании AService он сначала получит AService из кеша (сначала из кеша первого уровня, после того, как его не получит, он будет берется из кеша второго уровня.В это время он берется из кеша третьего уровня), то AService в кеше третьего уровня очищается, добавляется в кеш второго уровня EarlySingletonObjects и возвращается BService для его использования;
- После того, как BService завершит внедрение атрибутов и будет инициализирован, он добавит кеш первого уровня и очистит BService в кеше третьего уровня.В это время кеш третьего уровня пуст, в кеше второго уровня есть AService , а в кеше первого уровня есть BService. ;
- После того, как BService инициализирован, он внедряется в AService, AService инициализируется, затем получает кеш второго уровня с помощью метода getSingleton, присваивает его экспонируемому объекту и, наконец, добавляет его в кеш первого уровня, чтобы очистить кеш второго уровня AService. ;
Из приведенного выше анализа видно, что singletonFactories, кеш третьего уровня, является ключом к решению циклических зависимостей и мостом. Когда AService инициализируется, он получит объект, предоставленный заранее из кэша второго уровня, и назначит его в экспонированный объект. В основном это связано с тем, что EarlySingletonReference объекта кэша второго уровня может быть классом-оболочкой, а ссылка, хранящаяся в BService, является этим EarlySingletonReference.После присвоения гарантируется уникальность экземпляра, соответствующего beanName.
Роль кэша третьего уровня ObjectFactory
Мы уже знаем, как решить циклические зависимости Spring, но для Spring Почему такой дизайн, общее ощущение тумана, в основном онлайн Боуэн этого не сказал. Следующий автор рассуждает со своей точки зрения.
Кэш третьего уровня принимает режим фабричного проектирования.Бин получается с помощью метода getObject, а бин, полученный с помощью фабрики, в конечном счете является законченным бином, потому что сам метод getObject включает в себя некоторые функции обратного вызова жизненного цикла и решает, следует ли создавать прокси-класс. При необходимости он создаст прокси-класс. Что касается циклических зависимостей, когда BService внедряет AService через populateBean, окончательный ссылочный адрес может быть получен через кеш третьего уровня, который является bean-компонентом, сформированным после окончательной инициализации AService, что гарантирует, что реальные зависимости могут быть получены в круговые зависимости.
@Override
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (bean != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
if (exposedObject == null) {
return null;
}
}
}
}
return exposedObject;
}
анализировать:С помощью метода getObject() BeanFactory вызовите метод getEarlyBeanReference, оберните его и, наконец, сгенерируйте прокси-объект. После создания экземпляра AService объект, полученный из кэша второго уровня, фактически является ссылкой, соответствующей BeanFactory.
Роль кэша второго уровня EarlySingletonObjects
Если есть кеш третьего уровня, зачем проектировать кеш второго уровня? Многие могут подумать, что кеш второго уровня — это куриное ребрышко, и он необязателен, но на самом деле это отражение широкого использования кеша в Spring для повышения производительности. Каждый раз, когда мы проходим через фабрику, чтобы получить его, нам нужно пройти через все постпроцессоры, чтобы определить, создавать ли прокси-объект, а решение о том, создавать ли сам прокси-объект, является сложным и трудоемким процессом. Разработайте кеш второго уровня, чтобы избежать повторного вызова метода getEarlyBeanReference и улучшить процесс загрузки компонента. Можно только сказать, что Весна — это океан.
жизненный цикл кэша
В чем разница между добавлением экземпляров bean-компонентов в кеш и их удалением? Далее эта статья будет отвечать один за другим.
- Кэш L3
Когда свойство EarlySingletonExposure имеет значение true, beanFactory добавляется в кеш; когда созданный исходный bean-компонент извлекается из кэша третьего уровня с помощью getSingleton или после завершения инициализации, кэш bean-компонента в singletonFactory очищается.
- Кэш L2
Когда свойство EarlySingletonExposure имеет значение true, beanFactory добавляется в кеш.Когда созданный исходный бин извлекается из кеша третьего уровня через getSingleton, в это время полученный бин добавляется в кеш второго уровня. Когда инициализация bean-компонента завершена и bean-компонент добавлен в кэш первого уровня, кэш второго уровня очищается;
- Кэш L1
Когда инициализация бина завершена, бин добавляется в кеш первого уровня singletonObjects через addSingleton, и этот кеш остается в памяти.
Из приведенного выше анализа видно, что кеш третьего уровня и кеш второго уровня не сосуществуют, и они будут очищены при инициализации в Spring.
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
Суммировать
- Spring не может решить циклическую зависимость конструкторов.Основная причина в том, что при циклическом получении параметров построения бин сохраняется в singletonsCurrentlyInCreation.Во время предварительной проверки создания бина обнаруживается, что в процесс создания Verify no pass, bean не может быть создан;
- Spring решает круговую зависимость свойства или поля с помощью механизма раннего раскрытия + кеш.Каждый раз, когда он получен, он сначала берется из кеша.Когда его невозможно получить, он создается.После создания экземпляра он добавляется к третьему- кэш уровня для использования другими bean-компонентами. ;
- При решении циклической зависимости кеш третьего уровня автоматически обновляется до кеша второго уровня, бин инициализируется и автоматически очищается, после инициализации бина кэш второго уровня очищается;
- Наличие кеша второго уровня позволяет избежать сложного процесса повторного получения bean-компонентов через фабрику в циклических зависимостях и повышает эффективность загрузки;
- Наконец, автор хотел бы дополнить время инициализации bean-компонента Spring, о котором говорит большая часть Интернета. В настоящее время в большинстве постов в блогах говорится, что инициализация бина находится в методе initializeBean, что верно, но автор считает, что при наличии зависимости метод внедрения атрибута populateBean также имеет инициализацию самого бина, т.е. bean-компонент, полученный в случае круговой зависимости, сам по себе является инициализированным bean-компонентом.