内容简介:[TOC]本篇笔记主要记录了以下内容:使用
[TOC]
本篇笔记主要记录了以下内容:
使用 ClassPathXmlApplicationContext
,通过在 xml
注册一个 bean
,跟踪代码,了解它从配置文件的 <bean>
标签,加载到 BeanFactory
注册表 beanDefinitionMap
的详细过程。
展示的代码摘取了一些核心方法,去掉一些默认设置和日志输出,还有大多数错误异常也去掉了,小伙伴想看详细代码,注释和 demo,可以下载我上传的笔记项目:ledger:
通过阅读源码的过程,了解设计者的设计思路和从中学习,对 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
接口定义了将位置模式(例如, ant样式的路径模式)解析为资源对象的策略,具体实现类是 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
,用途是: 解析给定的路径,用对应的占位符(placeholder)替换占位符
例如 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
:(引用自参考资料第四条)
<!-- 测试环境配置文件 --> <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
中保存了运行时的参数
Bean 的解析和注册
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);
)
例如:(引用自参考资料第五条)
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
注册器,这两个概念也很庞大,可以先简单熟悉下,知道容器默认的对象是单例模式,还有可以通过别名来找到 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); } } 复制代码
Bean 加载和解析
核心方法是这个:
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()); } 复制代码
Bean 解析
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)); } } 复制代码
下面讲下几个关键方法所做的事情
获取 id 和 name
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 注册
经历千辛万苦,通过上面一些列的解析操作,终于到了注册 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') } 复制代码
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Spring源码探究:容器
- springMVC容器加载源码分析
- Spring容器创建源码解析
- 详解CopyOnWrite容器及其源码
- stl 源码阅读之容器vertor
- Spring源码解析:高级容器的扩展内幕
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The Haskell School of Music
Paul Hudak、Donya Quick / Cambridge University Press / 2018-10-4 / GBP 42.99
This book teaches functional programming through creative applications in music and sound synthesis. Readers will learn the Haskell programming language and explore numerous ways to create music and d......一起来看看 《The Haskell School of Music》 这本书的介绍吧!