Как изучить исходный код Spring
предисловие
Эта статья является одной из серий статей о анализе кода источника пружины. Статья в основном вводит, как выучить исходный код весны, надеясь помочь тем, кто нуждается в наибольшей степени. Общая сложность статьи не велика, но она относительно тяжелая. При изучении вы должны быть терпеливы и настойчивы.
Получить исходный код
Есть много способов получить исходный код
GitHub
Вы можете получить исходный код с GitHub и скомпилировать его самостоятельно.
maven
Любой, кто использовал maven, знает, что вы можете загрузить соответствующий исходный код и связанные документы через maven, что просто и удобно.
Веб-проект рекомендуется собирать через maven. Учитесь лучше, выполняя отладку на лету в реальном проекте.
Как начать учиться
Предварительные условия
Если вы хотите начать изучать исходный код Spring, вы должны сначала иметь базовое представление об использовании Spring Framework. Поймите некоторые функции в spring, такие как ioc и так далее. Поймите роль каждого модуля в spring.
Определить цель
Прежде всего, нам нужно знать, что сам фреймворк Spring теперь представляет собой огромное семейство после многих лет разработки. Возможно, реализация одной из функций зависит от взаимодействия нескольких модулей и нескольких классов, что чрезвычайно затруднит нам чтение кода. Прыжки между классами могут легко вызвать у нас головокружение.
Поэтому при чтении исходного кода spring вы не можете понять код построчно, как вы это делаете в коде JDK, и вам нужно выделить более ограниченную энергию на важные места. И мы не должны читать это таким образом.
Читая код функции spring, вы должны получить обзор всей ситуации с точки зрения бога. Вам нужно только знать процесс реализации определенной функции, и, к счастью, спецификации кода Spring лучше, и большинство методов в основном хорошо известны, что избавляет нас от многих проблем.
Эффективно используйте инструменты
Читать код лучше всего в idea или eclipse, и многие функции, предоставляемые такими IDE, очень полезны.
При чтении лучше сотрудничать с документацией spring (лучше смотреть комментарии напрямую, если вы сами компилируете исходный код).
Примечания и обзор
Этот процесс очень важен Я видел исходный код Spring раньше, но несколько раз чувствовал, что это сложно, и сдавался после прочтения некоторых. А из-за отсутствия заметок и рецензий о нем быстро забыли. Это пустая трата времени, чтобы смотреть его снова в следующий раз, когда вы хотите посмотреть его.
Ниже в качестве примера используется IOC, чтобы проиллюстрировать, как я это вижу, для справки.
IOC
Запись: ApplicationContext
При исследовании исходного кода вы должны сначала найти запись, как выбрать эту запись, вы можете определить сами, и она должна быть связана с модулем, который вам нужно увидеть.
Например, в IOC мы сначала думаем о процессе создания контейнера?
Создается при запуске программы, и большинство экземпляров bean-компонентов внедряются во время запуска.
Вопрос в том, с какого класса он начинается при запуске? Те, кто знаком с Spring, должны знать, что если мы обычно хотим получить экземпляры bean-компонентов при модульном тестировании, то это делается с помощью аннотаций, и мы также можем получить их, создав ApplicationContext:
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath*:application.xml");
XxxService xxxService = applicationContext.getBean("xxxService");
После создания экземпляра ApplicationContext вы можете получить bean-компонент, поэтому процесс создания экземпляра эквивалентен процессу запуска, поэтому мы можем использовать ApplicationContext в качестве нашей записи.
Что такое контекст приложения
Первое, что нам нужно понять, это то, что контейнер IOC, о котором мы всегда говорим в Spring, на самом деле относится кApplicationContext
.
Если вы читали мою предыдущую серию статей о Spring, написанных от руки, вы должны знать, что BeanFactory в то время выступал в качестве контейнера ioc в статье. также полученный от BeanFactory.
Так почему же вы говорите, что ApplicationContext теперь является контейнером IOC?
Потому что BeanFactory на самом деле скрыта весной. ApplicationContext является инкапсуляцией BeanFactory, а также предоставляет такие функции, как получение экземпляров компонентов. Поскольку способность самой BeanFactory слишком сильна, если мы сможем использовать ее небрежно, это может привести к повреждению работы пружинной функции. Таким образом, он инкапсулирует ApplicationContext, который предоставляет содержимое контейнера запросов ioc для использования.
Если вам нужно использовать ApplicationContext в проекте, вы можете напрямую использовать аннотации, предоставленные Spring, чтобы получить его:
@Autowired
private ApplicationContext applicationContext;
Как использовать контекст приложения
Если мы хотим использовать ApplicationContext, мы можем передать экземпляр нового класса и определить соответствующий XML-файл. Затем передайте следующий код:
@Test
public void testClassPathXmlApplicationContext() {
//1.准备配置文件,从当前类加载路径中获取配置文件
//2.初始化容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath*:application.xml");
//2、从容器中获取Bean
HelloApi helloApi = applicationContext.getBean("hello", HelloApi.class);
//3、执行业务逻辑
helloApi.sayHello();
}
Система ApplicationContext
Чтобы понять класс, вы можете сначала взглянуть на его отношения наследования, чтобы понять, какие функции он предоставляет изначально. Затем посмотрите, какие функции он реализует.
Отношение наследования на рисунке выше кратко описывает свои функции слева направо.
- ApplicationEventPublisher: Предоставляет функцию публикации событий прослушивания, получая сущность события прослушивания в качестве параметра. Что вам нужно знать, вы можете найти в этой статье:прослушиватель событий
- ResourcePatternResolver: используется для синтаксического анализа некоторых входящих путей к файлам (например, путей в стиле ant), а затем загрузки файла в качестве ресурса.
- HierarchicalBeanFactory: Обеспечивает отношение родительского-дочернего контейнера, чтобы гарантировать, что дочерний контейнер может получить доступ к родительскому контейнеру, а родительский контейнер не может получить доступ к дочернему контейнеру.
- ListableBeanFactory: унаследован от BeanFactory, предоставляя методы для доступа к контейнеру IOC.
- EnvironmentCapable: получить содержимое, связанное с переменными среды.
- MessageSource: обеспечивает синтаксический анализ интернационализированных сообщений.
Загрузка файлов конфигурации
Каждая функция в Spring — это большой проект, поэтому для понимания при чтении ее следует разделить на несколько модулей. Чтобы понять контейнер IOC, нам сначала нужно понять, как Spring загружает файлы конфигурации.
Обзор
Idea или eclipse предоставляют хорошую функцию, которая может видеть цепочку вызовов всего процесса в режиме отладки. С помощью этой функции мы можем напрямую наблюдать за общим процессом реализации функции, а также удобно переключаться между разными классами при чтении кода.
На примере файла конфигурации загрузки здесь приведена вся цепочка вызовов.
Красное поле под изображением выше — это написанный нами код, с которого мы должны начать. Красное поле внизу — это место, где заканчивается загрузочный файл конфигурации. Середина — это процесс реализации общего процесса. При чтении исходного кода, загруженного файлом конфигурации, нам нужно заботиться только о содержании этой части.
Важно знать, что показанные здесь методы лишь тесно связаны с этим процессом. На самом деле, в этом процессе все еще есть обязательные методы для выполнения, но стек методов всплывает после выполнения, поэтому он здесь не отображается. Тем не менее, большинство методов готовятся к этому процессу, поэтому в основном нам не нужно уделять этой части слишком много внимания.
refresh()
предыдущий оClassPathXmlApplicationContext
Конструкторская часть ничего не говорит, метод вызывается в конструктореAbstractApplicationContext#refresh
. Этот метод очень важен и в основном участвует во всем процессе создания IOC-контейнера. Основная функция — загрузить конфигурацию, или она используется для обновления загруженной конфигурации контейнера. Этот метод может динамически добавлять файлы конфигурации и т. д. во время работы:
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext();
ctx.setConfigLocation("application-temp.xml");
ctx.refresh();
AbstractApplicationContext#refresh
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
prepareRefresh();
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// more statement ...
}
}
Здесь будут удалены неактуальные части текущей функции.Вы видите, что после ввода метода будет введен синхронизированный блок кода. Это делается для предотвращения дублирования экземпляров, вызванных тем, что несколько потоков начинают создавать контейнер IOC одновременно.
prepareRefresh();
Этот метод в основном используется для установки некоторой информации, связанной с журналом, такой как время запуска контейнера, чтобы рассчитать общее время для запуска контейнера, а также для установки некоторых переменных, чтобы определить, что текущий контейнер был активирован и не будет создан позже.
obtainFreshBeanFactory();
Метод используется для получения фасоли, во время которого загружен файл конфигурации и анализа для генерации фаната.
refreshBeanFactory
Метод refreshBeanFactory имеет вызов методаgetFreshBeanFactory.
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
Метод сначала оценивает, был ли создан экземпляр BeanFactory, и если создание экземпляра завершено, созданный экземпляр BeanFactory будет уничтожен.
Затем создайте экземпляр класса реализации BeanFactory с помощью нового ключевого слова и установите соответствующую информацию.customizeBeanFactory(beanFactory)
Этот метод используется для установки того, следует ли запускать повторение beanName для изменения имени компонента (allowBeanDefinitionOverriding) и запускать ли циклические ссылки (allowCircularReferences).
loadBeanDefinitions(beanFactory)
метод является одновременно методом для начала загрузки определения компонента. Когда BeanFactory создается после загрузки всей информации о конфигурации, затем назначьте созданную BeanFactory BeanFactory в текущем контексте.
loadBeanDefinitions
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}
loadBeanDefinitions
Как следует из названия, это метод, используемый для загрузки определений bean-компонентов вAbstractXmlApplicationContext
Ряд перегруженных методов этого метода определен в . Вышеупомянутый метод в основном заключается в том, чтобы ввестиXmlBeanDefinitionReader
.XmlBeanDefinitionReader
Является классом для чтения определений bean-компонентов в XML-файлах и предоставляет для использования некоторые свойства, такие как BeanFactory и BeanDefinitionRegistery. Но на самом деле настоящая операция чтения этим классом не совершается, и он тоже существует как прокси.
В spring прослеживаются имена классов, выполняющих некоторые похожие операции, например чтение xml файлов здесь заканчивается на reader, а определения бинов в подобных аннотациях чтения тоже такиеAnnotatedBeanDefinitionReader
. Если вам нужно внедрить в класс несколько компонентов Spring, это обычно заканчивается Aware, напримерBeanFactoryAware
Ждать. Таким образом, при чтении исходного кода Spring, если мы встретим такой класс, мы можем напрямую понять его приблизительную реализацию по его имени.
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
if (resourceLoader instanceof ResourcePatternResolver) {
try {
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null) {
for (Resource resource : resources) {
actualResources.add(resource);
}
}
//logging
return loadCount;
}catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}else {
Resource resource = resourceLoader.getResource(location);
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
//logging
return loadCount;
}
}
Приведенный выше кодloadBeanDefinitions
Класс реализации , основное внимание этого метода сосредоточено в трех местах.
Во-первых, два исключения, генерируемые в методе.Вообще говоря, предыдущее исключение не требует нашего внимания из-за проблемы, определенной ResourceLoader. Последнее заключается в том, что файл конфигурации неверен, что может быть связано с неправильным форматом xml самого файла или из-за циклической ссылки и других причин, и конкретная причина также будет напечатана в журнале. Нам нужно иметь представление об этой аномальной информации, и нам не нужно запоминать ее преднамеренно.Мы можем быстро определить проблему, когда с ней столкнемся.
Другой тот, что в кодеif(){}else{}
Блок операторов и оператор суждения используются для синтаксического анализа файла конфигурации.Разница в том, что if поддерживает синтаксический анализ местоположений совпадающих стилей, таких какclasspath*:spring.xml
Эта функция реализуетсяResourcePatternResolver
поставка,ResourcePatternResolver
правильноResourceLoader
Функция была улучшена для поддержки синтаксического анализа муравьиного стиля и других шаблонов местоположения. В противном случае может быть проанализирован только указанный файл, напримерspring.xml
Такого рода. на самом деле вApplicationContext
реализовано вResourcePatternResolver
, если также согласноspring.xml
конфигурации, также согласноResourceLoader
Проанализируйте предоставленный метод синтаксического анализа.
Последнее местоResource
Добрый,Resource
Это интерфейс, специально разработанный Spring для облегчения загрузки файлов. Он предоставляет большое количество методов работы для входящего расположения и поддерживает различные стили расположения (например, файловая система или ClassPath). Он сам имеет много разных классов реализации, по существуFile,URL,ClassPath
Это очень мощный способ получить интеграцию местоположения различными способами. Даже если наш проект не зависит от Spring, мы можем использовать Resource в Spring, если он включает операции с ресурсами.
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
//log and assert
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<EncodedResource>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
Этот метод по-прежнему является перегруженным методом loadBeanDefinitions.
Метод передает EncodedResource, который может кодировать ресурс с помощью указанного набора символов, что выгодно для унифицированного формата кодировки символов.
Тогда код над блоком операторов try также более важен, его основная функция — определить, есть ли проблема с циклическими ссылками в файле конфигурации.
Проблема с зацикливанием приложения возникает, например, когда я загружаю файл конфигурацииapplication.xml
, но внутри файла снова черезimport
Маркировка ссылок сама. В решенииimport
загрузится, когдаimport
указанный файл. Это создает бесконечный цикл, и программа никогда не запустится, если она не разрешена.
Решение также очень простое, черезThreadLocal
Запишите имя файла конфигурации, загружаемого в данный момент (включая путь), каждый раз, когда новый файл конфигурации загружается изThreadLocal
Выньте его и поместите в коллекцию набора, и оцените, загружается ли он циклически с помощью функции автоматической дедупликации набора. Когда файл загружается, он начинается сThreadLocal
удален (окончательно). Здесь нужно судить о том, загружается ли файл xml повторно, и судить о том, циклически ссылаются ли на bean-компонент в spring, немного отличается в реализации, но основная идея та же.
doLoadBeanDefinitions(InputSource, Resource)
В этот момент начинается настоящий анализ. Хотя этот метод имеет большое количество строк кода, большинство из них относится к обработке исключений, а код исключения опущен. Все, на чем нам нужно сосредоточиться, — это две строки кода в файле try.
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
Document doc = doLoadDocument(inputSource, resource);
return registerBeanDefinitions(doc, resource);
}catch (Exception ex) {
//多个catch语句块
}
}
Document doc = doLoadDocument(inputSource, resource)
Это процесс чтения файла конфигурации и преобразования его содержимого в документ. Вообще говоря, синтаксический анализ xml не требует от нас его освоения в частности.Достаточно иметь небольшое понимание.Способ парсинга, используемый здесь spring, - парсинг Sax.Если вам интересно, вы можете напрямую искать связанные статьи, которые будут не быть представленным здесь. последующийregisterBeanDefinitions
Вот на что нам нужно обратить внимание.
registerBeanDefinitions(Document, Resource)
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
documentReader.setEnvironment(getEnvironment());
int countBefore = getRegistry().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
После входа в метод сначала создаетсяBeanDefinitionDocumentReader
, который аналогичен предыдущему классу чтения для чтения xml, за исключением того, что этот класс используется для чтения из файлов xml.BeanDefinition
.
Environment
В приведенном выше коде для Reader установлена среда Environment.Давайте поговорим об этой среде здесь.
Окружение — это набор описаний, связанных с окружением в программе spring, в основном разделенных на профиль и свойства.
Профиль представляет собой набор определений bean-компонентов.Через профиль можно указать разные файлы конфигурации, чтобы разделить конфигурацию тестовой среды и рабочей среды в разных средах. При развертывании вам нужно только настроить текущее значение среды для загрузки разных конфигураций в соответствии с разными категориями.
Профиль поддерживает конфигурацию XML и методы аннотации.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
//more >
<!-- 定义开发环境的profile -->
<beans profile="development">
<!-- 只扫描开发环境下使用的类 -->
<context:component-scan base-package="com.demo.service" />
<!-- 加载开发使用的配置文件 -->
<util:properties id="config" location="classpath:dev/config.properties"/>
</beans>
<!-- 定义生产环境的profile -->
<beans profile="produce">
<!-- 只扫描生产环境下使用的类 -->
<context:component-scan
base-package="com.demo.service" />
<!-- 加载生产使用的配置文件 -->
<util:properties id="config" location="classpath:produce/config.properties"/>
</beans>
</beans>
Его также можно настроить с помощью аннотаций:
@Service
@Profile("dev")
public class ProductRpcImpl implements ProductRpc {
public String productBaseInfo(int id) {
return "success";
}
}
Затем соответствующая конфигурация загружается при запуске на основе переданных значений среды.
Свойства — это очень широкое определение, и его источников много, например, файлы свойств, системные переменные JVM, переменные системной среды, JNDI, параметры контекста сервлета, карта и т. д. Spring считывает эти конфигурации и предоставляет удобные методы для управления ими в интерфейсе среды.
Короче говоря, он предназначен для поиска среды из среды, чтобы найти среду.
handler
Затем код опускается,documentReader.registerBeanDefinitions(doc, createReaderContext(resource))
Этот шаг, очевидно, представляет собой процесс чтения BeanDefinition из проанализированного объекта документа, но перед этим нам нужно обратить вниманиеcreateReaderContext(resource)
метод.
Давайте сначала посмотрим на файл XML.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc" >
</beans>
Выше приведена часть определения корневого элемента в xml, которую многие люди могут не заметить в обычное время. xmlns в его атрибуте является аббревиатурой от XML NameSpace. Роль пространства имен в основном заключается в предотвращении конфликтов между узлами, определенными в xml. Например, пространство имен mvc объявлено выше:xmlns:mvc="http://www.springframework.org/schema/mvc"
. В файле xml мы можем использовать mvc:
<mvc:annotation-driven />
<mvc:default-servlet-handler/>
Фактически, весной соответствующие классы обработки подготавливаются в соответствии с пространством имен, определенным выше. Здесь, поскольку процесс синтаксического анализа заключается в извлечении каждого узла, определенного в xml, для инициализации или регистрации bean-компонентов в соответствии с настроенными атрибутами и значениями, чтобы обеспечить читаемость кода и четкое разделение труда, каждое пространство имен обрабатывается выделенным обработчиком.
отслеживатьcreateReaderContext(resource)
метод, наконецDefaultNamespaceHandlerResolver
в конструкторе класса.
public DefaultNamespaceHandlerResolver(ClassLoader classLoader) {
//DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers"
this(classLoader, DEFAULT_HANDLER_MAPPINGS_LOCATION);
}
Вы можете видеть, что обработчик по умолчанию отображается через локальный файл. Этот файл находится в файле spring.handlers в папке META-INF зависимого пакета jar.
http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler
Это показывает только файлы сопоставления в пакете bean-компонентов.Другие, такие как пакет aop и пакет контекста, имеют соответствующие файлы сопоставления. Сопоставьте соответствующие классы обработки, прочитав эти файлы конфигурации. При анализе xml он будет использовать соответствующий класс обработчика для анализа в соответствии с используемым префиксом пространства имен. Этот механизм реализации на самом деле является так называемым SPI (Service Provider Interface).В настоящее время многие приложения используют SPI в процессе реализации, например, dubbo, реализация mysql jdbc и т. д. Если вам интересно, вы можете узнать об этом.
doRegisterBeanDefinitions(Element)
На данный момент метод опущен посередине, и его очень просто и не нужно анализировать.
protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
return;
}
}
}
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}
delegate
Делегат здесь является прокси для функции BeanDefinitionParse, предоставляя некоторые методы для поддержки процесса синтаксического анализа. Мы видим, что выше есть код, который воссоздает делегата при сохранении предыдущего делегата. В комментарии говорится, что это сделано для того, чтобы рекурсивная операция тегов вложенных компонентов не вызывала ошибок, но позже в комментарии говорится, что это не нужно обрабатывать таким образом.Эта операция действительно непонятна.На самом деле, я думаю, что даже рекурсия должна иметь нет эффекта. Или я неправильно понимаю?
После создания делегата следующий блок операторов if используется для определения того, является ли загруженный в данный момент файл конфигурации файлом конфигурации, заданным текущим используемым профилем. Я уже представил его, когда представил Environment.Если загруженный здесь файл конфигурации не соответствует указанному в профиле, он сразу же завершится.
preProcessXml(root)
Метод является пустой реализацией, и кажется, что этот метод не реализован в текущем фреймворке Spring, поэтому мне здесь все равно. Аналогично имеются следующиеpostProcessXml(root)
.
parseBeanDefinitions(Element, BeanDefinitionParserDelegate)
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
Этот метод не сложен для понимания, он сначала читает все дочерние узлы под корневым узлом (beans), а затем анализирует эти узлы. Здесь следует отметить, что даже парсинг узлов имеет суждение.
В основном смотреть наdelegate.isDefaultNamespace(ele)
,
public boolean isDefaultNamespace(String namespaceUri) {
//BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans"
return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri));
}
Другими словами, существует один метод анализа тегов в пространстве имен компонентов и один метод анализа других тегов.
parseDefaultElement(Element, BeanDefinitionParserDelegate)
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {//import
importBeanDefinitionResource(ele);
}
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {//alias
processAliasRegistration(ele);
}
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {//bean
processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
Видно, что для каждого тега предусмотрен метод парсинга, а последний метод используется для парсинга вложенных тегов, здесь в качестве примера взят парсинг bean-тегов.
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
//用于将一些属性值塞进BeanDefinition中如lazy-init
//以及子节点中的值 如bean节点下的property
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
Объект держателя также является серией объектов весной, в основном для упаковки некоторых экземпляров, таких какBeanDefinitionHolder
вот такBeanDefinition
Упаковка в основном предназначена для хранения BeanDefinition, его имени и псевдонима (BeanDefinition является интерфейсом и не может предоставлять такие атрибуты, как имя).
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}
СледующийBeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry())
На этот раз его можно рассматривать как самый важный шаг к нашей цели, и все предыдущие процессы прокладывают путь к этому шагу. С помощью этого метода анализируемое BeanDefinition регистрируется в контейнере, что удобно для создания экземпляра.
Метод получает два параметра: держатель и реестр.Если вы читали мою рукописную серию статей (статьи IOC), вы должны знать, что для того, чтобы связать определение компонента с контейнером и связать определение компонента с контейнеромbeanfactory
упрощается, поэтому мы определяемBeanDefinitionRegistry
интерфейс используется дляBeanDefinition
Регистрация в контейнерах и выборка из нихBeanDefinition
, функция реестра здесь такая же. (BeanDefinitionRegistry
одеялоDefaultListableBeanFactory
понял, иDefaultListableBeanFactory
собственно контейнер)
И вы можете увидеть, что фанат фаната фактически используется для различения того, повторяется ли BeanDefinition (на самом деле, это должно быть моим имитацией весны (смеется)), но псевдонимы предоставляются для регистрации BeanDefinittions с тем же именем, которые не были реализован при реализации IOC раньше. Этот шаг.
существуетprocessBeanDefinition
Последним шагом метода является регистрация слушателя вBeanDefinition
Он запускается после регистрации, но фактический метод триггера в spring является пустым методом.BeanDefinition
Какие работы могут быть переданы по наследству после завершения регистрацииEmptyReaderEventListener
после внедренияcomponentRegistered(componentDefinition)
метод.
На этом загрузка BeanDefinition в основном завершена, а затем описанный выше процесс повторяется для загрузки нескольких файлов конфигурации.
резюме
В этом разделе в основном представлены некоторые из моих методов изучения исходного кода Spring и анализируется общий процесс на примере загрузки Spring BeanDefinition, в надежде помочь всем. Также следует отметить, что исходный код Spring очень сложен, недостаточно просто открыть точку останова и отладить его полностью, нужно делать больше заметок в процессе чтения. В связи с большим содержанием статьи и уровнем моих проблем, в статье могут быть ошибки, если они есть, то вы можете указать на них для удобства доработки.