Изучение исходного кода Spring (1) инфраструктуры контейнера

Spring

`Spring` загружает конфигурацию `xml` следующим образом

[TOC]


В этой заметке в основном записано следующее:

использоватьClassPathXmlApplicationContext, сквозьxmlЗарегистрироватьbean, код отслеживания, чтобы понять его из файла конфигурации<bean>этикетка, загруженная вBeanFactoryреестрbeanDefinitionMapподробный процесс.

Отображаемый код извлекает некоторые основные методы, удаляет некоторые настройки по умолчанию и вывод журнала, а также удаляет большинство исключений ошибок.Если вы хотите увидеть подробный код, комментарии и демонстрацию, вы можете загрузить загруженный мной проект заметок📒

Адрес Code Cloud Gitee

адрес гитхаба

В процессе чтения исходного кода поймите дизайнерские идеи дизайнера и извлеките из них уроки.springиметь базовое понимание.


в основном построить

В начале я расскажу, как зарегистрироваться и использовать его в коде.bean:

config.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans>
	<bean id="book" class="domain.SimpleBook"/>
</beans>

Определите простой класс:

SimpleBook.java

public class SimpleBook {

	private int id;

	private String name = "Default Name";

}

использоватьClassPathXmlApplicationContextотxmlполучен из конфигурационного файлаbean:

public static void main(String[] args) {
	ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("config.xml");
	SimpleBook book = context.getBean(SimpleBook.class);
	System.out.println(book.getName());
}

После нормального запуска кода консоль выводит:

Default Name

Обычно мы хотим использовать объект, нам нужно передатьnewИнициализировать, выделить место в памяти и другие операции для инстанцирования, но сSpringПосле контейнера мы можем поставитьSimpleBookпередалSpringУправляемый, нет необходимости делать это в кодеnew SimpleBookи т. д., путем автоинъекции (например,@Autowireаннотацию) или, как в примере, получить объект контекста и использоватьgetBean()метод, вы можете легко получить экземпляр объекта~.


ClassPathXmlApplicationContext

ClassPathXmlApplicationContextДиаграмма архитектуры наследования:

ClassPathXmlApplicationContext

Эта структурная схема черезIDEAредактораDiagramsНа дисплее функции щелкните правой кнопкой мыши текущий класс и выберите, вы можете увидеть систему наследования, какие классы наследуются и на какие интерфейсы ссылаются, чтобы мы могли понять ~

ClassPathXmlApplicationContextунаследовано отAbstractApplicationContext,а такжеAbstractRefreshableApplicationContextдаAbstractApplicationContextАбстрактный подкласс , использующий фабрику регистрации классов, котораяDefaultListableBeanFactory, эта зарегистрированная фабрика также очень важна, и она будет представлена ​​позже.

Проще говоря,DefaultListableBeanFactoryдаSpringЗарегистрируйтесь и загрузитеbeanРеализация по умолчанию , которая будет регистрироватьbeanположить вbeanDefinitionMapпровестиkey-valueхранение формы.

Вы можете видеть в правом верхнем углу изображения,ResourceLoaderявляется его интерфейсом верхнего уровня, указывающим, что этот класс реализует функцию загрузки ресурсов.

Код конструктора:

public ClassPathXmlApplicationContext(
		String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
		throws BeansException {
	super(parent);
	// 注释 1.1 获取资源文件
	setConfigLocations(configLocations);
	if (refresh) {
		refresh();
	}
}

Конструктор

Из этой строки кода видно, что конструктор подкласса вызывает конструктор суперкласса:

super(parent)

Продолжайте отслеживать код и обнаружите, что он начинается с подкласса и вызывает родительский класс до тех пор, покаAbstractApplicationContext :

public AbstractApplicationContext() {
	this.resourcePatternResolver = getResourcePatternResolver();
}

public AbstractApplicationContext(@Nullable ApplicationContext parent) {
	this();
	setParent(parent);
}
protected ResourcePatternResolver getResourcePatternResolver() {
	return new PathMatchingResourcePatternResolver(this);
}

Функция инициализации в основном используется для установки процессора для сопоставления ресурсов,ResourcePatternResolverИнтерфейс определяет стратегию анализа шаблонов местоположения (например, шаблонов пути в стиле муравья) в объекты ресурсов, а конкретный класс реализации —PathMatchingResourcePatternResolver(Путь соответствует синтаксическому анализатору шаблонов ресурсов, который используется для анализа пути, который мы передаем вconfig.xml)


указать путь к файлу конфигурации

org.springframework.context.support.AbstractRefreshableConfigApplicationContext

public void setConfigLocations(@Nullable String... locations) {
	if (locations != null) {
		Assert.noNullElements(locations, "Config locations must not be null");
		// 注释 1.2 将配置资源路径放入 configLocations 数组中
		this.configLocations = new String[locations.length];
		for (int i = 0; i < locations.length; i++) {
			this.configLocations[i] = resolvePath(locations[i]).trim();
		}
	}
	else {
		this.configLocations = null;
	}
}

resolvePath, цель:Разобрать заданный путь, заменив заполнитель соответствующим заполнителем

Напримерnew ClassPathXmlApplicationContext("classpath:config.xml");, надо анализироватьclasspath, становится правильным путем.

protected String resolvePath(String path) {
	return getEnvironment().resolveRequiredPlaceholders(path);
}

У нас разные операционные среды,dev,testилиprod, файлы конфигурации и свойства, загруженные в это время, должны быть другими, на этот раз вам нужно использоватьEnvironmentразличать.

SpringСреды и свойства состоят из четырех частей:

  • Environment: среда, поProfileа такжеPropertyResolverкомбинация.
  • Profile: файл конфигурации, который можно понимать как атрибуты нескольких групп конфигурации в контейнере иbean, только активноprofile, соответствующий атрибут группы иbeanбудет загружен
  • PropertySource: источник свойства, использованиеCopyOnWriteArrayListМассив для пары свойствkey-valueформа хранения
  • PropertyResolver: Анализатор атрибутов, эта цель состоит в том, чтобы анализировать атрибуты

Environment

Первый взглядStandardServletEnvironmentсистема наследования:

StandardServletEnvironment

Как видите, интерфейс верхнего уровняPropertyResolver, он используется для синтаксического анализа свойства, а окончательный метод синтаксического анализа —

PropertyPlaceholderHelper.replacePlaceholders

public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
	Assert.notNull(value, "'value' must not be null");
    // 用返回的值替换格式为{@code ${name}}的所有占位符
	return parseStringValue(value, placeholderResolver, null);
}

Profile

Благодаря этому свойству в файле конфигурации можно одновременно развернуть два набора конфигураций, которые подходят для производственной среды и среды разработки, так что среды разработки и развертывания можно легко переключать, и это часто используется для заменить различные базы данных или файлы конфигурации.

demo: (цитата из ссылки 4)

<!-- 测试环境配置文件 -->
<beans profile="test">
    <context:property-placeholder location="classpath:test/*.properties, classpath:common/*.properties" />
</beans>

<!-- 生产环境配置文件 -->
<beans profile="production">
    <context:property-placeholder location="classpath:production/*.properties, classpath:common/*.properties" />
</beans>

<!-- 开发环境配置文件 -->
<beans profile="development">
    <context:property-placeholder location="classpath:dev/*.properties, classpath:common/*.properties" />
</beans>

Есть два способа указать, какую конфигурацию использовать:

① вweb.xmlустановить в

<context-param>
    <param-name>spring.profiles.active</param-name>
    <param-value>test</param-value>
</context-param>

② Установить при запуске кода

context.getEnvironment().setActiveProfiles("test");

Property

PropertyОписание официальной заметки:

/**
 * A description of a JavaBeans Property that allows us to avoid a dependency on
 * {@code java.beans.PropertyDescriptor}. The {@code java.beans} package
 * is not available in a number of environments (e.g. Android, Java ME), so this is
 * desirable for portability of Spring's core conversion facility.
 *
 **/

它允许我们避免对 {@code java.bean . propertydescriptor}的依赖。

因为 {@code java。bean} package 在许多环境中都不可用(例如 Android、Java ME),因此这对于 Spring 的核心转换工具的可移植性来说是非常理想的。

существуетAbstractEnvironment.javaможно найти в настройках средыenvчас,newвзял одинMutablePropertySources, используйте этот объект для хранения свойств:

private final MutablePropertySources propertySources = new MutablePropertySources()

private final ConfigurablePropertyResolver propertyResolver =
			new PropertySourcesPropertyResolver(this.propertySources);
            
public AbstractEnvironment() {
	customizePropertySources(this.propertySources);
}

Интерфейс источника свойств

Система наследования выглядит следующим образом:

PropertySource

отPropertySourceЧто касается системы наследования,customizePropertySourcesЦепочка вызовов метода вызывается из подкласса полностью вверх:

AbstractEnvironment -> StandardServletEnvironment -> StandardEnvironment

в конце концовStandardEnvironmentиспользоватьCopyOnWriteArrayListМассив для хранения свойств

protected void customizePropertySources(MutablePropertySources propertySources) {
	propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
	propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}

Например, из вышеизложенного видно, чтоpropertySourceListПараметры системы будут сохранены:

В то время эти параметры можно использовать в запущенном приложении, через контекстcontextприобрести

((MutablePropertySources)((StandardEnvironment)context.environment).propertySources).propertySourceList

резюме

Просто серия предварительных работ, просто используемых для определения ресурсов пути и загрузки системных параметров.

  • установить конструктор
  • Определение переменных пути
  • Установить параметры среды:в основномEnvironmentсистема иpropertySourcesПараметры времени выполнения сохраняются в

Парсинг и регистрация бина

Spring beanПарсинг и регистрация имеет важный методrefresh()

AbstractApplicationContext.refresh()

public void refresh() throws BeansException, IllegalStateException {
	synchronized (this.startupShutdownMonitor) {
		// Prepare this context for refreshing. (为更新准备上下文,设定一些标志)
		prepareRefresh();
		// Tell the subclass to refresh the internal bean factory. (告诉子类去更新它们的 bean factory)
		// 类的注册到 bean factory 也是在这一步
		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
		// Prepare the bean factory for use in this context.
		prepareBeanFactory(beanFactory);
		try {
			// Allows post-processing of the bean factory in context subclasses.
			postProcessBeanFactory(beanFactory);
			// Invoke factory processors registered as beans in the context.
			invokeBeanFactoryPostProcessors(beanFactory);
			// Register bean processors that intercept bean creation.
			registerBeanPostProcessors(beanFactory);
			// Initialize message source for this context.
			initMessageSource();
			// Initialize event multicaster for this context.
			initApplicationEventMulticaster();
			// Initialize other special beans in specific context subclasses.
			onRefresh();
			// Check for listener beans and register them.
			registerListeners();
			// Instantiate all remaining (non-lazy-init) singletons.
			finishBeanFactoryInitialization(beanFactory);
			// Last step: publish corresponding event.
			finishRefresh();
		}
		catch (BeansException ex) {
			if (logger.isWarnEnabled()) {
				logger.warn("Exception encountered during context initialization - " +
						"cancelling refresh attempt: " + ex);
			}
			// Destroy already created singletons to avoid dangling resources.
			destroyBeans();
			// Reset 'active' flag.
			cancelRefresh(ex);
			// Propagate exception to caller.
			throw ex;
		}
		finally {
			// Reset common introspection caches in Spring's core, since we
			// might not ever need metadata for singleton beans anymore...
			resetCommonCaches();
		}
	}
}

Далее будет отслеживаться и анализироваться этот метод.


prepareRefresh подготовиться к обновлению

Этот метод работает:подготовить этот контекст для обновления, установить его дату начала иactiveфлаги, а также выполнять инициализацию любых источников свойств.

protected void prepareRefresh() {
	// Switch to active.
	// Initialize any placeholder property sources in the context environment.(空方法,等子类实现)
	initPropertySources();
	// Validate that all properties marked as required are resolvable:(校验参数)
	// see ConfigurablePropertyResolver#setRequiredProperties
	getEnvironment().validateRequiredProperties();
	// Allow for the collection of early ApplicationEvents,
	// to be published once the multicaster is available...
	this.earlyApplicationEvents = new LinkedHashSet<>();
}

конкретный метод проверки

org.springframework.core.env.AbstractPropertyResolver#validateRequiredProperties

public void validateRequiredProperties() {
	MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException();
	for (String key : this.requiredProperties) {
		if (this.getProperty(key) == null) {
			ex.addMissingRequiredProperty(key);
		}
	}
	if (!ex.getMissingRequiredProperties().isEmpty()) {
		throw ex;
	}
}

Как видите, логика проверки заключается в обходеrequiredProperties, который является персонажемSet, по умолчанию он пуст, значит, ни один элемент не нужно проверять, если в списке есть значение, то согласноkeyЕсли соответствующая переменная среды пуста, будет сгенерировано исключение, в результате чегоSpringОшибка инициализации контейнера.


Проверка пользовательской переменной среды

Поскольку даноrequirePropertiesСписок, указывающий, что мы можем добавить в него пользовательские переменные среды, которые необходимо проверить:

  • Создайте класс, который наследуется отAnnotationConfigServletWebServerApplicationContext, перегруженinitPropertySources
  • Когда приложение запускается, установите вновь созданный класс в качестве контекста приложения (application.setApplicationContextClass(CustomContext.class);)

Например: (цитата из Ссылки, статья 5)

public class CustomApplicationContext extends AnnotationConfigServletWebServerApplicationContext {
    @Override
    protected void initPropertySources() {
        super.initPropertySources();
        //把"MYSQL_HOST"作为启动的时候必须验证的环境变量
        getEnvironment().setRequiredProperties("MYSQL_HOST");
    }
}


public static void main(String[] args) {
    SpringApplication springApplication = new SpringApplication(CustomizepropertyverifyApplication.class);
    springApplication.setApplicationContextClass(CustomApplicationContext.class);
    springApplication.run(args);
}

Добавив пользовательское значение проверки, вSpringКогда приложение запускается, его можно проверить заранее


Получатьbeanконтейнер

в этой строке кодаConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

Конкретный вызов:

org.springframework.context.support.AbstractRefreshableApplicationContext#refreshBeanFactory

protected final void refreshBeanFactory() throws BeansException {
	// 在更新时,如果发现已经存在,将会把之前的 bean 清理掉,并且关闭老 bean 容器
	if (hasBeanFactory()) {
		destroyBeans();
		closeBeanFactory();
	}
	try {
		DefaultListableBeanFactory beanFactory = createBeanFactory();
		beanFactory.setSerializationId(getId());
		customizeBeanFactory(beanFactory);
		// 注释 1.3 开始加载 (bean 注册)
		loadBeanDefinitions(beanFactory);
		synchronized (this.beanFactoryMonitor) {
			this.beanFactory = beanFactory;
		}
	}
	catch (IOException ex) {
		throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
	}
}

Этот метод входа очень важен, и на этом шаге он создается заново.beanКонтейнеры и парсингbean, и воляbeanЗарегистрируйтесь в контейнере.


Система наследования BeanFactory

В этом примере и в большинстве случаевbeanконтейнерыDefaultListableBeanFactory, так что давайте введем его систему наследования:

DefaultListableBeanFactory

Видно, что система наследования очень большая, наследующая несколько регистраторов и реализующая несколько интерфейсов, а обычно используется одноэлементный.SingletonРегистратор и псевдонимыAliasRegistrar, эти два понятия тоже очень большие, с ними можно сначала ознакомиться, знать, что объект контейнера по умолчанию — это режим singleton, а найти его можно через псевдонимbean, я дам более подробную информацию позже, когда у меня будет возможность.


Настройка BanFactory

Конкретный метод заключается в следующем.С помощью этого метода фабрика может быть настроена, а подкласс может быть свободно настроен:

org.springframework.context.support.AbstractRefreshableApplicationContext#customizeBeanFactory

protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
	if (this.allowBeanDefinitionOverriding != null) {
		// 默认是 false,不允许覆盖
		beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
	}
	if (this.allowCircularReferences != null) {
		// 默认是 false,不允许循环引用
		beanFactory.setAllowCircularReferences(this.allowCircularReferences);
	}
}

Загрузка и разбор бина

Основной метод таков:

org.springframework.context.support.AbstractXmlApplicationContext#loadBeanDefinitions(org.springframework.beans.factory.support.DefaultListableBeanFactory)

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
	// Create a new XmlBeanDefinitionReader for the given BeanFactory.
	// 为给定的BeanFactory创建一个新的XmlBeanDefinitionReader
	XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
	// Configure the bean definition reader with this context's
	// resource loading environment.
	beanDefinitionReader.setEnvironment(this.getEnvironment());
	beanDefinitionReader.setResourceLoader(this);
	beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
	// Allow a subclass to provide custom initialization of the reader,
	// then proceed with actually loading the bean definitions.(空方法,让子类进行扩展实现)
	initBeanDefinitionReader(beanDefinitionReader);
	loadBeanDefinitions(beanDefinitionReader);
}

разборXML, используются следующие две системы наследования:EntityResolverа такжеBeanDefinitionReader


EntityResolver

EntityResolver

Полный путь интерфейса:org.xml.sax.EntityResolver, конкретный метод анализа:

org.springframework.beans.factory.xml.ResourceEntityResolver#resolveEntity

Этот метод используется для разбораschemaа такжеdtd, детали тоже очень сложные, но анализxmlЭто не то, что я хочу знать, так что сначала пропусти это~


BeanDefinitionReader

BeanDefinitionReader

Интерфейс верхнего уровняBeanDefinitionReader, дляXML BeanОпределенныйBeanОпределите читателя. на самом деле будет читатьXMLДокументация делегирована реализации.

Цель этих двух классов очень ясна, т.XMLПреобразуйте его во входной поток, и заинтересованные студенты смогут продолжить углубленное изучение~


загрузка конфигурационного файла

Метод ввода: (Поскольку существует несколько методов с одинаковым именем, при копировании пути также копируется тип параметра)

org.springframework.beans.factory.support.AbstractBeanDefinitionReader#loadBeanDefinitions(java.lang.String, java.util.Set<org.springframework.core.io.Resource>)

Основным методом являются эти две строки

public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
    // 获取资源文件(资源加载器从路径识别资源文件)
    Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location)
    // 注释 1.6 根据资源文件加载 bean
    int count = loadBeanDefinitions(resources);		
    ···
}

После получения файла ресурсов начните разбор файла ресурсов (то есть первый переданный параметрconfig.xml), преобразовать его вDocument

Код отслеживания может видеть, что файл ресурсов для парсинга начинается сResourceупаковано вEncodeResouce, который добавляет кодировку символов во входной поток (по умолчаниюnull), который воплощает шаблон проектирования — шаблон декоратора

Просмотрите файлы ресурсов и преобразуйте их.Основной метод состоит из следующих двух строк:

org.springframework.beans.factory.xml.XmlBeanDefinitionReader#loadBeanDefinitions(org.springframework.core.io.support.EncodedResource)

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
	// 注释 1.7 从资源文件中获取输入流
	InputStream inputStream = encodedResource.getResource().getInputStream();
	InputSource inputSource = new InputSource(inputStream);
	return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}

Разбор бина

org.springframework.beans.factory.xml.XmlBeanDefinitionReader#doLoadBeanDefinitions

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
	throws BeanDefinitionStoreException {
	// 注释 1.8 将资源文件解析成 document
	Document doc = doLoadDocument(inputSource, resource);
	// 注释 1.10 从 doc 和资源中解析元素,注册到 bean factory
	int count = registerBeanDefinitions(doc, resource);
	if (logger.isDebugEnabled()) {
		logger.debug("Loaded " + count + " bean definitions from " + resource);
	}
	return count;
}

существуетdoLoadDocument()метод, разберите файл ресурсов наdocuemntДокументация

org.springframework.beans.factory.xml.XmlBeanDefinitionReader#registerBeanDefinitions

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
	// 使用 DefaultBeanDefinitionDocumentReader 实例化 BeanDefinitionDocumentReader
	BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
	// 记录统计前 beanDefinition 的加载个数
	int countBefore = getRegistry().getBeanDefinitionCount();
	// 加载及注册 bean,这里使用注册工厂的是 DefaultListableBeanFactory
	documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
	// 记录本次加载的 BeanDefinition 个数(新值 - 旧值)
	return getRegistry().getBeanDefinitionCount() - countBefore;
}

Не так много здесь, как преобразовать вdocumentа такжеdocumentReaderИнициализация, заинтересованные студенты, пожалуйста, продолжайте следить~

Следующее, чтобы сказатьbeanконтейнерDefaultListableBeanFactoryРазобратьdocument

org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#doRegisterBeanDefinitions

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);
			// We cannot use Profiles.of(...) since profile expressions are not supported
			// in XML config. See SPR-12458 for details.
			if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
				if (logger.isDebugEnabled()) {
					logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
							"] not matching: " + getReaderContext().getResource());
				}
				return;
			}
		}
	}
	// preProcess 和 postProcess 点进去会发现是空方法,这两个方法留给子类重载,体现了设计模式 - 模板方法
	preProcessXml(root);
	// 注释 1.11 核心方法,解析 doc 元素
	parseBeanDefinitions(root, this.delegate);
	postProcessXml(root);
	this.delegate = parent;
}

Как видно из вышеизложенного, перед синтаксическим анализом, если пространство имен начинается сhttp://www.springframework.org/schema/beansв начале проверюprofileАтрибуты

После прохождения проверки начинается формальный анализdocэлемент

org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#parseBeanDefinitions

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
	if (delegate.isDefaultNamespace(root)) {
		// 注释 1.12 遍历 doc 中的节点列表
		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)) {
					// 注释 1.13 识别出默认标签的 bean 注册
					// 根据元素名称,调用不同的加载方法,注册 bean
					parseDefaultElement(ele, delegate);
				}
				else {
					delegate.parseCustomElement(ele);
				}
			}
		}
	}
	else {
		delegate.parseCustomElement(root);
	}
}

На этом шаге мыxmlНастроенные свойства могут соответствоватьdocumentВ объекте он вынимается и используется в последующем процессе


Разбор тегов по умолчанию

Эта часть не будет подробно описана, и я напишу статью, чтобы дополнить ее позже, поэтому я просто пройдусь по тому, как парсить теги по умолчанию в коде ниже.

  • IMPORT: Импорт тегов
  • ALIAS: метка псевдонима
  • BEAN:beanЭтикетка
  • NESTED_BEANS:beansтеги (вложенныеbeans)

org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#parseDefaultElement

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
	if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
		importBeanDefinitionResource(ele);
	}
	else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
		processAliasRegistration(ele);
	}
	else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
		processBeanDefinition(ele, delegate);
	}
	else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
		// recurse
		doRegisterBeanDefinitions(ele);
	}
}

Давайте посмотрим, как разобратьbeanЭтикетка


парсинг тега bean-компонента

org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#processBeanDefinition

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
	// 注释 1.15 解析 bean 名称的元素
	BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
	if (bdHolder != null) {
		bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
		try {
			// Register the final decorated instance. (注释 1.16 注册最后修饰后的实例)
			BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
		}
		catch (BeanDefinitionStoreException ex) {
			getReaderContext().error("Failed to register bean definition with name '" +
					bdHolder.getBeanName() + "'", ele, ex);
		}
		// Send registration event.
		getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
	}
}

Вот несколько основных способов сделать что-то


получить идентификатор и имя

org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseBeanDefinitionElement(org.w3c.dom.Element, org.springframework.beans.factory.config.BeanDefinition)

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
	// 获取 ID 属性
	String id = ele.getAttribute(ID_ATTRIBUTE);
	// 获取 NAME 属性
	String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
	List<String> aliases = new ArrayList<>();
	if (StringUtils.hasLength(nameAttr)) {
		// 名称按照 , ; 进行分割
		String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
		aliases.addAll(Arrays.asList(nameArr));
	}
	String beanName = id;
	if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
		// 如果没有指定 id,将 name 的第一个值作为 id
		beanName = aliases.remove(0);
	}
	// 默认 null
	if (containingBean == null) {
		// 检查名字是否唯一,如果 id 重复了,将抛出错误
		// 内部 usedNames 是一个 HashSet,将会存储加载过的 name 和 aliases
		checkNameUniqueness(beanName, aliases, ele);
	}
	// 将公共属性放入 AbstractBeanDefinition,具体实现在子类 GenericBeanDefinition
	AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
	if (beanDefinition != null) {
		if (!StringUtils.hasText(beanName)) {
			if (containingBean != null) {
				// 如果 id 和 name 都是空,那个 spring 会给它生成一个默认的名称
				beanName = BeanDefinitionReaderUtils.generateBeanName(
						beanDefinition, this.readerContext.getRegistry(), true);
			}
			else {
				beanName = this.readerContext.generateBeanName(beanDefinition);
				String beanClassName = beanDefinition.getBeanClassName();
				if (beanClassName != null &&
						beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
						!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
					aliases.add(beanClassName);
				    }
			    }
		    }
		}
		String[] aliasesArray = StringUtils.toStringArray(aliases);
		return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
	}
	return null;
}

Получатьidа такжеnameПроцесс атрибутов становится понятным, когда вы шаг за шагом следуете комментариям к коду.

Основной рабочий процесс этого метода выглядит следующим образом:

  • извлечь элементid nameАтрибуты
  • Далее проанализируйте все остальные свойства и единообразно инкапсулируйте их вGenericBeanDefinitionв экземпляре типа
  • обнаруженbeanне указанbeanNameСоздано с правилами по умолчаниюbeanName
  • Инкапсулировать полученную информацию вBeanDefinitionHolderв случае

Разбор других атрибутов в тегах

org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseBeanDefinitionElement(org.w3c.dom.Element, java.lang.String, org.springframework.beans.factory.config.BeanDefinition)

public AbstractBeanDefinition parseBeanDefinitionElement(
		Element ele, String beanName, @Nullable BeanDefinition containingBean) {
    AbstractBeanDefinition bd = createBeanDefinition(className, parent);
    parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
    bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
    parseMetaElements(ele, bd);
    parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
    parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
    parseConstructorArgElements(ele, bd);
    parsePropertyElements(ele, bd);
    parseQualifierElements(ele, bd);
    bd.setResource(this.readerContext.getResource());
    bd.setSource(extractSource(ele));
    return bd;
}

инициализацияBeanDefinitonВ этом методе: (конкретная реализация является его подклассомGenericBeanDefinitionо~)

BeanDefinitionReaderUtils.createBeanDefinition(parentName, className, this.readerContext.getBeanClassLoader())

public static AbstractBeanDefinition createBeanDefinition(
			@Nullable String parentName, @Nullable String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException {
	GenericBeanDefinition bd = new GenericBeanDefinition();
	bd.setParentName(parentName);
	if (className != null) {
		if (classLoader != null) {
			bd.setBeanClass(ClassUtils.forName(className, classLoader));
		}
		else {
			bd.setBeanClassName(className);
		}
	}
	return bd;
}

Далее следует проанализировать содержимое других тегов, а затем составить яму~


Система наследования BeanDefinition

GenericBeanDefinition

Как видно из рисунка,BeanDefinitionэто интерфейс,GenericBeanDefinition,RootBeanDefinition,ChildBeanDefinition, все три наследуютAbstractBeanDefinition.

вBeanDefinitionэто файл конфигурации<bean>Внутреннее представление тега элемента в контейнере.

<bean>тег элемента имеетclass,scope,lazy-initи другие свойства конфигурации,BeanDefinitionпредоставляет соответствующиеbeanClass,scope,lazyInitсвойства, они соответствуют друг другу.


BeanDefinitionHolder украшение

org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#decorateBeanDefinitionIfRequired(org.w3c.dom.Element, org.springframework.beans.factory.config.BeanDefinitionHolder, org.springframework.beans.factory.config.BeanDefinition)

public BeanDefinitionHolder decorateBeanDefinitionIfRequired(
			Element ele, BeanDefinitionHolder definitionHolder, @Nullable BeanDefinition containingBd) {
	// 方法中的第三个参数是父类 bean
	// 当对某个嵌套配置进行分析时,这里需要传递,是为了使用父类的 scope 属性,以备子类没设定 scope,可以使用父类的 scope 属性
	BeanDefinitionHolder finalDefinition = definitionHolder;

	// Decorate based on custom attributes first.
	NamedNodeMap attributes = ele.getAttributes();
	// 遍历所有的属性,进行属性的修饰
	for (int i = 0; i < attributes.getLength(); i++) {
		Node node = attributes.item(i);
		finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
	}

	// Decorate based on custom nested elements.
	NodeList children = ele.getChildNodes();
	// 遍历所有的子节点,修饰子元素
	for (int i = 0; i < children.getLength(); i++) {
		Node node = children.item(i);
		if (node.getNodeType() == Node.ELEMENT_NODE) {
			finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
		}
	}
	return finalDefinition;
}

После предыдущего обычного синтаксического анализа атрибутов на этом этапе он в основном используется для завершения синтаксического анализа пользовательских элементов метки, и здесь есть яма ~


Регистрация бина

После многих невзгод, проведя вышеописанную серию аналитических операций, наконец-то добрались до регистрации.beanметод информации

org.springframework.beans.factory.support.BeanDefinitionReaderUtils#registerBeanDefinition

public static void registerBeanDefinition(
			BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
			throws BeanDefinitionStoreException {
		// Register bean definition under primary name.
		// 注释 1.17 在 DefaultListableBeanFactory 的 beanDefinitionMap 中添加 bean 定义
		String beanName = definitionHolder.getBeanName();
		registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
		// Register aliases for bean name, if any.
		String[] aliases = definitionHolder.getAliases();
		if (aliases != null) {
			for (String alias : aliases) {
				registry.registerAlias(beanName, alias);
			}
		}
	}

Как было сказано выше, здесь используетсяbeanконтейнерDefaultListableBeanFactory, следующие две строки кода для ключевой операции метода регистрации:

org.springframework.beans.factory.support.DefaultListableBeanFactory#registerBeanDefinition

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
	this.beanDefinitionMap.put(beanName, beanDefinition);
	this.beanDefinitionNames.add(beanName);
}

В этот моментbeanинформацию вbeanDefinitionMap, операция регистрации класса завершена~

Для описания полноты логики кода кратко представлены следующие методы.


prepareBeanFactory

Подготовьте среду загрузчика классов, для полученного ранееbeanFactory(ConfigurationListableBeanFactory)Выполните соответствующие настройки, в том числеClassLoader, post-processorsЖдать


postProcessBeanFactory

загрузит всеbeanопределено, но еще не реализованоbean, изменить внутренности контекста приложения после его стандартной инициализацииbeanконтейнер.

Это позволяет конкретноApplicationContextзарегистрируйте специальныйbeanpostprocessorЖдать.

Это также пустой метод, ожидающий реализации подклассами.


invokeBeanFactoryPostProcessors

создать экземпляр и вызвать все зарегистрированныеBeanFactoryPostProcessorBean, это постпроцессоры и тип обработкиBeanFactory, SpringКонтейнеры позволяют создавать экземплярыbeanраньше, читайbeanинформацию и изменять ее свойства.

Это эквивалентно предоставлению пользователю последней возможности изменить перед созданием экземпляра.beanИнформация.

Другой момент заключается в том, что выполнение также может иметь последовательный порядок, в зависимости от того, реализуют ли эти процессорыPriorityOrdered,Orderинтерфейс, судя поorderзначения сортируются.


registerBeanPostProcessors

Создать и зарегистрировать все постпроцессоры.В отличие от предыдущего, этот метод обрабатывает типыBean, как и в предыдущем методе, существует также концепция приоритета~


initMessageSource

источник сообщения для инициализации этого контекста


initApplicationEventMulticaster

многоадресная передача событий, которая инициализирует этот контекст


onRefresh

Метод шаблона, который можно переопределить, чтобы добавить контекстно-зависимую работу по обновлению.

специальный вызов перед созданием синглтонаbeanинициализация. (Туман, я не знаю, что особенногоbean, оставьте дырку =-=)

Эта реализация пуста.


registerListeners

проверить слушателяbeanи зарегистрируйте их

Тип прослушивателя событийjava.util.EventListener


finishBeanFactoryInitialization

ЗаканчиватьbeanИнициализация контейнера, создание экземпляров всех оставшихся (не лениво-инициализированных) синглетонов


finishRefresh

Последний шаг — опубликовать соответствующее событие

Типы событий:java.util.EventObject


resetCommonCaches

Последний шаг реальной регистрации, используемый для очистки кеша

сброс настроекSpringобщедоступный кеш самоанализа в ядре, так как нам больше никогда не понадобятся синглтоныbeanметаданные


Суммировать

Примечания в этой главе отражают толькоbeanКак добраться изxmlзагружен вbeanВ реестре контейнера после многих строчек кода я наконец-то разобрался со ссылкой на вызов.

Вот краткое изложение ядраloadBeanDefinitions(beanFactory)процесс работы:

① Прочтите файл конфигурации

  • файл ресурсов пакета: получить файл пути и инкапсулировать его какEncodeResource
  • получить входной поток:отResourceполучить соответствующийInputStreamи построитьInputSource
  • передать параметры: построенInputSourceэкземпляр иResourceэкземпляр, переданныйdoLoadBeanDefinitionsметод

② Загрузкаbean

  • получить паруXMLРежим проверки файлов ресурсов
  • нагрузкаXMLФайлы ресурсов, разобранные на соответствующиеDocumentДокументация: есть несколькоNodeИнформация об узле, сохраняет информацию о конфигурации, которую мы написали
  • согласно сDocumentфайл дляBeanАнализ информации

beanПарсинг и регистрация тегов

  • доверитьBeanDefinitionDelegateКатегорияparseBeanDefinitionElementметод: разобрать элемент и вернутьBeanDefinitionHolderэкземпляр типа (который содержитclass,name,id,aliasи другие свойства)
  • Разобрать теги: Определите тип тега, чтобы увидеть, анализируется ли тег по умолчанию или пользовательский тег.
  • правильноbdHodlerзарегистрироваться: После завершения парсинга зарегистрируйтесьbeanинформации, операция регистрации делегируетсяBeanDefinitionReaderUtilsизregisterBeanDefinitionметод
  • отправить ответное событие: Уведомить связанных слушателей, уведомитьbeanКонтейнер был загружен

Увидимся в следующей заметке~


Побить пит-рекорд

Javadoc generation failed. Generated Javadoc options file (useful for troubleshooting)

При компиляции обнаруживается, что она не может быть успешной, что побуждаетJavadocошибка, решение вgradleДобавьте в файл следующую конфигурацию:

tasks.withType(Javadoc) {
    options.addStringOption('Xdoclint:none', '-quiet')
    options.addStringOption('encoding', 'UTF-8')
}

использованная литература

1,spring-analysis/note/Spring.md

2,Spring Framework 5.0.0.M3 Китайская документация

3. Углубленный анализ исходного кода Spring / под редакцией Хао Цзя — Пекин: Издательство «Народная почта и телекоммуникации».

4.Используйте конфигурацию профиля после Spring 3.1 для загрузки разных файлов конфигурации в разных средах.

5.Один из расширенных сражений spring4.1.8: проверка переменных пользовательской среды

Портал:

Категории