Эта статья была включена в общедоступный аккаунт: https://mp.weixin.qq.com/s/FIm84EGVV21phajCaLjgaA
предисловие
У вас могут возникнуть следующие вопросы:
1. Я хочу увидеть исходный код Spring, но не знаю, как его запустить, я не имею представления обо всем процессе Bean и понятия не имею, с чего начать, когда сталкиваюсь с сопутствующими проблемами.
2. Несколько раз читал исходники, но не могу до конца понять, не чувствую и через некоторое время забываю
В этой статье будут объединены актуальные проблемы, приведен исходный код из проблемы и попытка объяснить IOC, DI, жизненный цикл, область действия и т. д. Spring Bean шаг за шагом в виде диаграмм.
Первый взгляд на проблему циклической зависимости
Феномен
Циклическая зависимость на самом деле является циклической ссылкой, то есть два или более bean-компонента удерживают друг друга и в конечном итоге образуют замкнутый цикл. Например, А зависит от В, В зависит от С, а С зависит от А. Как показано ниже:
Как понять «зависимость», в Spring есть:
- Циклическая зависимость конструктора
- Свойство field вводит циклические зависимости
Перейдите непосредственно к коду:
Циклическая зависимость конструктора
@Service
public class A {
public A(B b) { }
}
@Service
public class B {
public B(C c) {
}
}
@Service
public class C {
public C(A a) { }
}
Результат: Не удалось запустить проект и был найден цикл.
2. Атрибут поля вводит циклические зависимости
@Service
public class A1 {
@Autowired
private B1 b1;
}
@Service
public class B1 {
@Autowired
public C1 c1;
}
@Service
public class C1 {
@Autowired public A1 a1;
}
Результат: Проект успешно стартовал
3. Свойство field вводит циклические зависимости (прототип)
@Service
@Scope("prototype")
public class A1 {
@Autowired
private B1 b1;
}
@Service
@Scope("prototype")
public class B1 {
@Autowired
public C1 c1;
}
@Service
@Scope("prototype")
public class C1 {
@Autowired public A1 a1;
}
Результат: Не удалось запустить проект и был найден цикл.
Резюме явления: в одном и том же сценарии циклической зависимости внедрение конструктора и внедрение свойства типа прототипа не смогут инициализировать bean-компоненты. Поскольку @Service по умолчанию является одноэлементным, внедрение свойства одиночного элемента может быть успешным.
Проанализируйте причины
Причиной анализа является процесс обнаружения SpringIOC.Если вас не интересует исходный код, вы можете уделить внимание анализу каждого исходного кода.Резюме и анализ проблем циклической зависимостиВот и все.
Процесс загрузки SpringBean (анализ исходного кода)
Простой фрагмент кода в качестве записи
ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
ac.getBean(XXX.class);
ClassPathXmlApplicationContext — это класс, который загружает файлы конфигурации XML, в отличие от AnnotationConfigWebApplicationContext.Эти два класса почти одинаковы, за исключением того, что ресурс ClassPathXmlApplicationContext представляет собой XML-файл, а AnnotationConfigWebApplicationContext получается с помощью аннотации Scan.
Когда вы видите вторую строку, вы можете напрямую получить экземпляр компонента, поэтому, когда первая строка создает метод, загрузка всех компонентов завершена.
Пример ClassPathXmlApplicationContext, в нем хранятся следующие вещи:
имя объекта | тип | роль | Атрибуция |
configResources | Resource[] | Массив объектов ресурсов файла конфигурации | ClassPathXmlApplicationContext |
configLocations | String[] | Массив строк файла конфигурации, хранящий путь к файлу конфигурации | AbstractRefreshableConfigApplicationContext |
beanFactory | DefaultListableBeanFactory | Фабрика бобов, используемая контекстом | AbstractRefreshableApplicationContext |
beanFactoryMonitor | Object | Монитор синхронизации, используемый фабрикой компонентов | AbstractRefreshableApplicationContext |
id | String | Уникальный идентификатор, используемый контекстом для идентификации этого ApplicationContext. | AbstractApplicationContext |
parent | ApplicationContext | родительский контекст приложения | AbstractApplicationContext |
beanFactoryPostProcessors | List<BeanFactoryPostProcessor> | Сохраняет интерфейс BeanFactoryPostProcessor, точку расширения, предоставляемую Spring. | AbstractApplicationContext |
startupShutdownMonitor | Object | Монитор, совместно используемый методом обновления и методом уничтожения, чтобы избежать одновременного выполнения двух методов. | AbstractApplicationContext |
shutdownHook | Thread | Крюк, предоставленный Spring, метод в Thread будет запущен, когда JVM перестанет выполняться. | AbstractApplicationContext |
resourcePatternResolver | ResourcePatternResolver | Парсер формата ресурса, используемый контекстом | AbstractApplicationContext |
lifecycleProcessor | LifecycleProcessor | Интерфейс обработчика жизненного цикла для управления жизненным циклом компонента | AbstractApplicationContext |
messageSource | MessageSource | Интерфейс для интернационализации | AbstractApplicationContext |
applicationEventMulticaster | ApplicationEventMulticaster | Интерфейс мультикастера событий в механизме управления событиями, предоставляемом Spring | AbstractApplicationContext |
applicationListeners | Set<ApplicationListener> | Слушатели приложений в механизме управления событиями, предоставляемом Spring | AbstractApplicationContext |
Метод строительства следующий:
Далее взгляните на метод обновления:
Давайте сначала не будем рассматривать подметод, а сначала посмотрим на структуру метода обновления, На самом деле, есть несколько моментов, которые стоит изучить:
1. Почему метод заблокирован? Это делается для того, чтобы избежать одновременного обновления контекста Spring в многопоточных сценариях.
2. Хотя весь метод заблокирован, используется блокировка объекта startUpShutdownMonitor с ключевым словом Synchronized, что имеет два преимущества:
(1) Когда ресурс закрыт, вызывается метод close().Метод close() также использует ту же блокировку объекта, и два конфликтующих метода закрытия и обновления ресурса закрываются, чтобы избежать конфликтов.
(2) По сравнению со всем методом блокировки блокировки объекта здесь объем синхронизации меньше, степень детализации блокировки меньше, а эффективность выше.
3. Это обновление метода определяет весь процесс Spring IOC.Название каждого метода понятно и легко для понимания, ремонтопригодно и удобочитаемо.
Резюме: При просмотре исходного кода вам нужно найти нужную запись, больше думать при чтении и изучить гениальный дизайн Spring. Самый важный метод в методе построения ApplicationContext — это refresh, который имеет несколько хороших дизайнов.
метод getFreshBeanFactory
Цель этого метода — получить фабрику компонентов, которая обновляет контекст Spring:
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
this.refreshBeanFactory();
return this.getBeanFactory();
}
protected final void refreshBeanFactory() throws BeansException {
if (this.hasBeanFactory()) {
this.destroyBeans();
this.closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = this.createBeanFactory();
beanFactory.setSerializationId(this.getId());
this.customizeBeanFactory(beanFactory);
this.loadBeanDefinitions(beanFactory);
synchronized(this.beanFactoryMonitor) {
this.beanFactory = beanFactory; }
} catch (IOException var5) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + this.getDisplayName(), var5);
}
}
Ядром этого кода являетсяDefaultListableBeanFactory,Давайте реорганизуем основные классы в формате диаграммы:
Ниже приведены три выделенные жирным шрифтом Карты, и эти Карты являются ключом к решению проблемы. . . Мы подробно разберем позже
имя объекта | тип | роль | Атрибуция |
aliasMap | Map<String, String> | Store Bean name -> Отношение сопоставления псевдонимов Bean | SimpleAliasRegistry |
singletonObjects | Map<String, Object> | Сохранить имя одноэлементного компонента -> отношение сопоставления реализации одноэлементного компонента | DefaultSingletonBeanRegistry |
singletonFactories | Map<String, ObjectFactory> | Store Bean name -> ObjectFactory реализует отношение сопоставления | DefaultSingletonBeanRegistry |
earlySingletonObjects | Map<String, Object> | Store Bean Name -> Preload Bean Relation Mapping Relationship | DefaultSingletonBeanRegistry |
registeredSingletons | Set<String> | Сохраните зарегистрированное имя компонента | DefaultSingletonBeanRegistry |
singletonsCurrentlyInCreation | Set<String> | Сохраняет имя bean-компонента, создаваемого в данный момент. | DefaultSingletonBeanRegistry |
disposableBeans | Map<String, Object> | Store Bean name -> Disposable interface для реализации отношения сопоставления реализации Bean |
DefaultSingletonBeanRegistry |
factoryBeanObjectCache | Map<String, Object> | Storage Bean name -> FactoryBean interface Сопоставление реализации Bean-компонента | FactoryBeanRegistrySupport |
propertyEditorRegistrars | Set<PropertyEditorRegistrar> | Хранит коллекцию реализаций интерфейса PropertyEditorRegistrar. | AbstractBeanFactory |
embeddedValueResolvers | List<StringValueResolver> | Сохраняет список реализаций интерфейса StringValueResolver. | AbstractBeanFactory |
beanPostProcessors | List<BeanPostProcessor> | Сохраняет список реализаций интерфейса BeanPostProcessor. | AbstractBeanFactory |
mergedBeanDefinitions | Map<String, RootBeanDefinition> | Store Bean name -> отношение сопоставления определения объединенного корневого компонента | AbstractBeanFactory |
alreadyCreated | Set<String> | Хранит коллекцию имен bean-компонентов, которые были созданы хотя бы один раз. | AbstractBeanFactory |
ignoredDependencyInterfaces | Set<Class> | Сохраняет коллекцию объектов класса интерфейса, которые не связаны автоматически. | AbstractAutowireCapableBeanFactory |
resolvableDependencies | Map<Class, Object> | Сохранить исправленные сопоставления зависимостей | DefaultListableBeanFactory |
beanDefinitionMap | Map<String, BeanDefinition> | Store Bean name --> Отношение сопоставления определения Bean | DefaultListableBeanFactory |
beanDefinitionNames | List<String> | Сохраняет список имен определений bean-компонентов | DefaultListableBeanFactory |
Регистрация BeanDefinition в контейнере IOC
Далее краткий анализloadBeanDefinitions.
Для этого BeanDefinition я понимаю это так: это продукт в середине процесса SpringIOC, который можно рассматривать как абстракцию определения бина.Инкапсулированные в нем данные связаны с определением бина, и он инкапсулирует некоторые основные свойства компонента, метод инициализации, метод уничтожения и т. д.
Основным методом здесь являетсяloadBeanDefinitions,Не вдаваясь в подробности здесь, он в основном делает несколько вещей:
1. Инициализируйте BeanDefinitionReader
2. Получите ресурс через BeanDefinitionReader, который является расположением файла конфигурации xml, и преобразуйте файл в объект с именем Document.
3. Далее необходимо преобразовать объект Document в структуру данных внутри контейнера (то есть BeanDefinition), то есть проанализировать List, Map, Set и другие элементы, определенные Bean, и преобразовать их в управляемые классы (Spring BeanDefinition Инкапсуляция данных) помещается в BeanDefinition; это метод RegisterBeanDefinition(), который представляет собой процесс синтаксического анализа.
4. После завершения синтаксического анализа результат синтаксического анализа будет помещен в объект BeanDefinition и установлен в Map
Описанный выше процесс представляет собой регистрацию BeanDefinition в контейнере IOC.
Вернитесь к методу Refresh и резюмируйте каждый шаг следующим образом:
Резюме: Эта часть шагов в основном связана с тем, как Spring загружает файл Xml или аннотацию и анализирует его в BeanDefinition.
Процесс создания бинов в Spring
Сначала вернемся к предыдущему методу обновления (то есть методу при построении ApplicationContext), и мы пропустим неважную часть:
мы смотрим прямоfinishBeanFactoryInitializationМетод preInstantiateSingletons внутри, как следует из названия, инициализирует все singleton bean-компоненты, а часть перехвата выглядит следующим образом:
Теперь давайте посмотрим на основной метод getBean. Поскольку все полученные объекты Bean являются экземплярами, используется этот метод getBean. Этот метод, наконец, вызывает метод doGetBean, где происходит так называемая DI (внедрение зависимостей).
Программа = данные + алгоритм, предыдущее определение BeanDefinition — это «данные», а внедрение зависимостей выполняется, когда определение BeanDefinition готово.Этот процесс не прост, поскольку Spring предоставляет множество конфигураций параметров, и каждый параметр представляет собой IOC-контейнер. реализация этих характеристик должна быть завершена в жизненном цикле Бина.
Кода много, поэтому выкладывать не буду. Вы можете сами проверить метод doGetBean в AbstractBeanFactory. Вот картинка прямо вверху. На этой картинке весь процесс внедрения зависимостей:
Резюме: после того, как Spring создаст BeanDefinition, он начнет создавать экземпляр Bean и заполнять свойства зависимостей Bean. Нижний уровень использует технологию отражения CGLIB или Java при создании экземпляров. Основной метод InstantiateBean PupulateBean на приведенном выше рисунке очень важен!
Анализ проблемы циклической зависимости
Сначала подведем итоги предыдущих выводов:
1. Внедрение конструктора и внедрение поля типа прототипа не может быть инициализировано, когда возникают циклические зависимости
2. Когда поле вводится в одноэлементный компонент, компонент все еще может быть успешно инициализирован, несмотря на циклические зависимости.
В ответ на эти выводы задайте вопросы
- Как bean-компонент инъекций singleton setter решает проблему циклической зависимости? Если B вводится в A, в каком порядке они инициализируются?
- Почему Spring типа прототипа и типа конструктора не может разрешать циклические зависимости?
Раньше в классе DefaultListableBeanFactory числилась таблица, теперь перечисляю ключевые свойства сущности:
一级缓存:
/** 保存所有的singletonBean的实例 */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);
二级缓存:
/** 保存所有早期创建的Bean对象,这个Bean还没有完成依赖注入 */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
三级缓存:
/** singletonBean的生产工厂*/
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
/** 保存所有已经完成初始化的Bean的名字(name) */
private final Set<String> registeredSingletons = new LinkedHashSet<String>(64);
/** 标识指定name的Bean对象是否处于创建状态 这个状态非常重要 */
private final Set<String> singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(16));
Первые три Карты, которые мы называем трехуровневым кешем, инициализируемым одним экземпляром, понимают эту проблему,Сейчас нам нужно сосредоточиться только на «третьем уровне», которыйsingletonFactories
анализировать:
Для вопроса 1, инъекция одноэлементной настройки, если B вводится в A, а B должен быть атрибутом в A, то предположение должно заключаться в том, что после того, как A был создан экземпляр (создан), в populateBean (заполнение атрибута в A), инициализировать Б.
Для вопроса 2 создание экземпляра (создание экземпляра) на самом деле является процессом понимания нового объекта, и метод построения должен выполняться, когда он новый, поэтому предполагается, что A должен быть инициализирован, когда создается экземпляр B (создается экземпляр).
После анализа и догадок вокруг ключевых атрибутов, согласно всему коду от метода doGetBean на рисунке выше до populateBean, я организовал следующий рисунок:
Вышеприведенное изображение представляет собой ключевой путь кода во всем процессе. Если вам интересно, вы можете отладить его самостоятельно несколько раз. Наиболее важным решением циклической зависимости являются два вышеуказанных метода, отмеченные красным. Первый метод getSingleton возьмет Singleton из singletonFactory, а addSingletonFactory поместит Singleton в singletonFactory.
На вопрос 1: Как bean-компонент инъекции singleton setter решает проблему циклической зависимости? Если B вводится в A, в каком порядке они инициализируются?
Предположим, что циклическая инъекция — это A-B-A: A зависит от B (автоматически связывает B с A), а B зависит от A (автоматически связывает A с B):
Суть в том, что кэш L3 работает и решает петлю.
Для вопроса 2 в то время создание экземпляра (создание экземпляра) на самом деле является процессом понимания нового объекта, и метод построения должен выполняться, когда он новый, поэтому предполагается, что A должен быть инициализирован, когда создается экземпляр B (создается экземпляр).
Ответ также очень прост, потому что конструктор в A вводит B, затем A инициализирует B до ключевого метода addSingletonFactory(), в результате чего A вообще отсутствует в кеше третьего уровня, поэтому возникает бесконечный цикл, и Spring выбросьте его после обнаружения.Что-то пошло не так. Что касается того, как Spring находит исключение, он по существу помечает бин в соответствии с состоянием бина.Если обнаруживается, что бин создается во время рекурсивного вызова, он может долгое время выдавать исключение циклической зависимости. .
Так как же инициализируется bean-компонент прототипа?
ProtoBean имеет одно ключевое свойство:
/** Names of beans that are currently in creation */
private final ThreadLocal<Object> prototypesCurrentlyInCreation =
new NamedThreadLocal<Object>("Prototype beans currently in creation");
Содержит beanName создаваемого прототипа и не предоставляет никакого фабричного кеша в процессе. И вbeforePrototypeCreation(String beanName)
поместите BeanName каждого создаваемого прототипа в набор:
protected void beforePrototypeCreation(String beanName) {
Object curVal = this.prototypesCurrentlyInCreation.get();
if (curVal == null) {
this.prototypesCurrentlyInCreation.set(beanName);
}
else if (curVal instanceof String) {
Set<String> beanNameSet = new HashSet<String>(2);
beanNameSet.add((String) curVal);
beanNameSet.add(beanName);
this.prototypesCurrentlyInCreation.set(beanNameSet);
}
else {
Set<String> beanNameSet = (Set<String>) curVal;
beanNameSet.add(beanName);
}
}
И он проверит, находится ли beanName в созданном состоянии, когда он циклически зависим, и выдаст исключение, если это так:
protected boolean isPrototypeCurrentlyInCreation(String beanName) {
Object curVal = this.prototypesCurrentlyInCreation.get();
return (curVal != null &&
(curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
}
Из процесса видно, что будь то внедрение конструкции или внедрение заданного значения, метод getBean при вводе одного и того же компонента во второй раз обязательно вызовет исключение в части проверки, поэтому внедрение не может быть завершено, и циклическая ссылка не может быть реализована.
Резюме: Spring выполняет метод конструктора, когда InstantiateBean создает экземпляр, и, если это синглтон, он помещает его вsingletonBeanFactoryВ кеше выполните метод populateBean, чтобы установить свойства. Проблема круговой зависимости решается кэшем singletonBeanFactory.
решить другую проблему
Теперь, когда у всех есть ощущение всего процесса Spring, давайте решим простую распространенную проблему:
Рассмотрим следующий одноэлементный код:
@Service
public class SingletonBean{
@Autowired
private PrototypeBean prototypeBean;
public void doSomething(){
System.out.println(prototypeBean.toString()); }
}
@Component
@Scope(value="prototype")
public class PrototypeBean{
}
Компонент-одиночка автоматически подключил прототип компонента, затем возникает проблема, каждый раз, когда вы вызываетеSingletonBean.doSomething()Является ли объект напечатанным в то же время одним и тем же?
С предыдущим запасом знаний кратко разберем: поскольку Singleton — это синглтон, он будет инициализирован при старте проекта.prototypeBeanПо сути, это только одно из его свойств, поэтому существует только один SingletonBean и один тип прототипа PrototypeBean, созданный при инициализации SingletonBean в ApplicationContext.
Затем каждый раз, когда вызывается SingletonBean.doSomething(), Spring будет получать SingletonBean из ApplicationContext, и SingletonBean, полученный каждый раз, будет одним и тем же, поэтому, даже если PrototypeBean является прототипом, PrototypeBean остается тем же. Адрес памяти, печатаемый каждый раз, должен быть одним и тем же.
Как решить эту проблему?
Решение тоже очень простое.В этом случае мы не можем внедрить прототипBean посредством инъекции.Мы можем только вручную вызвать метод getBean("prototypeBean") во время работы программы.Я написал простой инструментальный класс:
@Service
public class SpringBeanUtils implements ApplicationContextAware {
private static ApplicationContext appContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringBeanUtils.appContext=applicationContext;
}
public static ApplicationContext getAppContext() {
return appContext;
}
public static Object getBean(String beanName) {
checkApplicationContext();
return appContext.getBean(beanName);
}
private static void checkApplicationContext() {
if (null == appContext) {
throw new IllegalStateException("applicaitonContext未注入");
}
}
@SuppressWarnings("unchecked")
public static <T> T getBean(Class<T> clazz) {
checkApplicationContext();
Map<?, ?> map = appContext.getBeansOfType(clazz);
return map.isEmpty() ? null : (T) map.values().iterator().next();
}
}
Для этого интерфейса ApplicationContextAware:
В некоторых особых случаях бину необходимо реализовать функцию, но функция может быть достигнута только с помощью контейнера Spring.В этом случае бин должен сначала получить контейнер Spring, а затем реализовать функцию с помощью Весенний контейнер. Чтобы компонент мог получить контейнер Spring в том месте, где он находится, компонент может реализовать интерфейс ApplicationContextAware.
Заинтересованные читатели могут попробовать сами.
Суммировать:
Возвращаясь к вопросу о циклических зависимостях, некоторые люди могут спросить:singletonBeanFactory — это всего лишь кеш третьего уровня, так какой смысл в кеше первого уровня и кеше второго уровня?
На самом деле, пока вы понимаете весь процесс, вы можете врезаться. Spring может грубо разделить на несколько шагов при инициализации Singleton, инициализация-установка-уничтожение.В сценарии циклической зависимости есть только порядок A-B-A, ноВ параллельном сценарии можно вызывать метод getBean при выполнении каждого шага, и одноэлементный компонент должен гарантировать, что существует только один экземпляр, поэтому Spring решает проблемы такого рода с помощью этих кешей и блокировок объектов, и он может также сохранить Устранить ненужное дублирование. Выбор детализации блокировки Spring также очень заметен, и мы пока не будем подробно изучать его здесь.
Ключом к решению таких проблем является знание всего процесса SpringIOC и DI. Просмотр исходного кода обычно не требует глубокого понимания каждой строки кода, но необходим для всего процесса и того, что происходит в каждой из них. Конечно, таким образом, когда проблема действительно возникает, ее можно быстро проанализировать и решить.
Надеюсь, что эта статья поможет вам глубже понять поток ввода-вывода и внедрения зависимостей в Spring!