[TOC]
В этой заметке в основном записано следующее:
использоватьClassPathXmlApplicationContext
, сквозьxml
Зарегистрироватьbean
, код отслеживания, чтобы понять его из файла конфигурации<bean>
этикетка, загруженная вBeanFactory
реестрbeanDefinitionMap
подробный процесс.
Отображаемый код извлекает некоторые основные методы, удаляет некоторые настройки по умолчанию и вывод журнала, а также удаляет большинство исключений ошибок.Если вы хотите увидеть подробный код, комментарии и демонстрацию, вы можете загрузить загруженный мной проект заметок📒
В процессе чтения исходного кода поймите дизайнерские идеи дизайнера и извлеките из них уроки.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
Диаграмма архитектуры наследования:
Эта структурная схема через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
система наследования:
Как видите, интерфейс верхнего уровня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
Что касается системы наследования,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
, так что давайте введем его систему наследования:
Видно, что система наследования очень большая, наследующая несколько регистраторов и реализующая несколько интерфейсов, а обычно используется одноэлементный.Singleton
Регистратор и псевдонимыAlias
Registrar, эти два понятия тоже очень большие, с ними можно сначала ознакомиться, знать, что объект контейнера по умолчанию — это режим 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
Полный путь интерфейса:org.xml.sax.EntityResolver
, конкретный метод анализа:
org.springframework.beans.factory.xml.ResourceEntityResolver#resolveEntity
Этот метод используется для разбораschema
а такжеdtd
, детали тоже очень сложные, но анализxml
Это не то, что я хочу знать, так что сначала пропусти это~
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
Как видно из рисунка,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 / под редакцией Хао Цзя — Пекин: Издательство «Народная почта и телекоммуникации».
5.Один из расширенных сражений spring4.1.8: проверка переменных пользовательской среды
Портал:
-
Изучение исходного кода Spring (1) инфраструктуры контейнера
-
Изучение исходного кода Spring (2) анализ тегов по умолчанию
-
Функция расширения изучения исходного кода Spring (6), часть 1
-
Функция расширения изучения исходного кода Spring (семь), часть 2
-
Изучение исходного кода Spring (8) Принципы использования и реализации АОП
-
Изучение исходного кода Spring (9) Транзакционная транзакция