前言
本篇将会带你认识以下几个问题
- 什么是Resource?
- BeanDefinition又是什么?
- xml的解析配置过程是怎样的?
- 什么是默认标签和自定义标签?
- 默认标签的解析过程又是怎样的?
- 什么是BeanDefinition的注册?
带着以上这几个问题,我们开启本篇的叙述。
总体流程
在分析前,我们先看下Spring在注册BeanDefinition的过程中,核心对象的变化过程。
过程简单描述如下:
- 找到location指定的xml文件
- 将xml文件转换为Resource
- 将Resource包装成EncodedResource
- 获取到Document对象
- 根据上面得到的Document和Resource对象,抽象成BeanDefinition,并完成注册
- 实例化bean
相关概念的说明
什么是Resource
Resource即为资源的定义规范。在Spring中,将资源的定义和加载分开处理了。资源的加载是通过ResourceLoader这个接口定义的规范进行处理的。这里不对Resource和ResourceLoader进行扩展说明,只做简单的说明即可,有机会单独写一篇文章阐述。 我们来看下Resource的源码
public interface Resource extends InputStreamSource {
boolean exists();
default boolean isReadable() {
return exists();
}
default boolean isOpen() {
return false;
}
default boolean isFile() {
return false;
}
URL getURL() throws IOException;
URI getURI() throws IOException;
File getFile() throws IOException;
default ReadableByteChannel readableChannel() throws IOException {
return Channels.newChannel(getInputStream());
}
long contentLength() throws IOException;
long lastModified() throws IOException;
Resource createRelative(String relativePath) throws IOException;
String getFilename();
String getDescription();
}
从源码里,能够知道,Resource是对我们定义的xml资源文件属性的统一描述,比如文件是否存在、是否可读、是否为文件、文件长度、URI、文件名等。等到下面的BeanDefinition时,可以知道BeanDefinition其实是对xml中元素标签等具体内容的描述。
什么是BeanDefinition?
在讲述它的含义前,我们来看一下这个类(接口)的定义,由于篇幅太长,这里截取一部分。
package org.springframework.beans.factory.config;
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
void setParentName(@Nullable String parentName);
String getParentName();
void setBeanClassName(@Nullable String beanClassName);
String getBeanClassName();
void setScope(@Nullable String scope);
String getScope();
void setLazyInit(boolean lazyInit);
boolean isLazyInit();
void setDependsOn(@Nullable String... dependsOn);
String[] getDependsOn();
void setAutowireCandidate(boolean autowireCandidate);
boolean isAutowireCandidate();
void setPrimary(boolean primary);
boolean isPrimary();
void setInitMethodName(@Nullable String initMethodName);
String getInitMethodName();
boolean isSingleton();
boolean isPrototype();
boolean isAbstract();
// ....
}
看到上面的scope、lazyInit、InitMethodName、isSingleton、ParentName、isAbstract... 这些方法,是不是很熟悉?没错,其实是和spring的xml文件对应的。根据这点,可以得出一个结论:Spring经过一系列复杂的处理之后,将xml元素转换成了BeanDefinition对象。那这个过程又是如何的?为什么要抽象出这个类?
<bean id="xxxxx" name="xxxxx" abstract="true" depends-on="userDaoImpl" init-method="xx" lazy-init="true" scope="singleton" primary="true"></bean>
BeanDefinition的注册与Bean的实例化
- 一般而言,所谓的Bean定义信息的注册,或者说是BeanDefinition的注册,其实就是将由xml经过一系列的处理得到的BeanDefinition,存入Spring的BeanFactory中,这个过程就是所谓的注册。具体保存在了DefaultListableBeanFactory类的beanDefinitionMap和beanDefinitionNames这两个属性之中。
- Bean的实例化,是在BeanDefinition注册完成之后进行的,通过遍历所有注册的BeanDefinition,完成Bean的实例化和属性填充等,成为一个可用的Bean实例。
小结
通过上面的分析,知道了Spring如何将资源的定义和加载进行的区分。而对实例信息的注册和实例对象的创建,同样也进行了解耦。
obtainFreshBeanFactory方法中默认标签的解析和Bean定义的注册
资源的加载和BeanDefinition的注册是在AbstractApplicationContext类的refresh方法中通过调用obtainFreshBeanFactory()方法完成整个过程的。我们来看obtainFreshBeanFactory()方法的源码:
# AbstractApplicationContext
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
return getBeanFactory();
}
其中资源的加载和定义信息的注册主要是在refreshBeanFactory()中完成。该方法的源码如下:
# AbstractRefreshableApplicationContext
protected final void refreshBeanFactory() throws BeansException {
// 如果beanFactory存在了, 就先销毁
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
// 创建一个DefaultListableBeanFactory对象实例, 并在以下代码中设置序列号等属性
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
// 加载所有的BeanDefinitions,实际解析xml的位置
loadBeanDefinitions(beanFactory);
this.beanFactory = beanFactory;
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
该方法比较简单,主要完成两件事:
- 确保创建一个新的beanFactory、并设置相关的属性信息
- 加载BeanDefinition信息并完成注册 我们再来简单看一下里面的
customizeBeanFactory(beanFactory)这个方法:
protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
// 是否允许 BeanDefinition的覆盖, 默认true
if (this.allowBeanDefinitionOverriding != null) {
beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
// 是否允许 循环依赖, 默认true
if (this.allowCircularReferences != null) {
beanFactory.setAllowCircularReferences(this.allowCircularReferences);
}
}
该方法中设置了两个属性allowBeanDefinitionOverriding和allowCircularReferences,分别代表是否允许BeanDefinition的覆盖和是否允许循环依赖注入。
- BeanDefinition覆盖:简单说明一下,该参数的作用就是告诉spring,在注册BeanDefinition时,若存在相同的BeanDefinition,是否允许覆盖。部分逻辑代码如下。若不允许覆盖,则抛出异常;若允许覆盖,则记录一些日志,并用新的BeanDefinition进行覆盖。
- 循环依赖:这个处理比较复杂,这里不做过多的说明, 那么这里就引申出一个问题,我们在使用框架时,如何修改这两个属性?只需要在继承ClassPathXmlApplicationContext的类中,重写customizeBeanFactory()方法,在该方法中直接设置这两个值为false或true即可。
# DefaultListableBeanFactory
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException {
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
if (existingDefinition != null) {
if (!isAllowBeanDefinitionOverriding()) {
throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
}
else if (existingDefinition.getRole() < beanDefinition.getRole()) {
// 日志记录
}
else if (!beanDefinition.equals(existingDefinition)) {
// 日志记录
}
else {
// 日志记录
}
this.beanDefinitionMap.put(beanName, beanDefinition);
}
}
下面再来看loadBeanDefinitions(beanFactory)方法
# AbstractXmlApplicationContext
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// 通过委托XmlBeanDefinitionReader去解析BeanDefinition
// 基于注解的方式则是使用AnnotationBeanDefinitionReader去解析BeanDefinition
// 还要注意一点的是, 该读取器的构造函数的参数是BeanDefinitionRegistry类型, 此处beanFactory对应的实际类型为DefaultListableBeanFactory
// 而DefaultListableBeanFactory实现了BeanDefinitionRegistry接口
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
// 留意ResourceEntityResolver类, 处理和xml命名空间相关的schemal相关的文件
// ResourceEntityResolver及其父类中,映射了xml中网络xsd和本地存储的xsd文件的映射位置, 感兴趣的可以跟踪构造函数查看
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// 设置两项属性值:validationMode = VALIDATION_AUTO, 即等于1; namespaceAware = false
initBeanDefinitionReader(beanDefinitionReader);
// 使用委托方式进行BeanDefinition的解析
loadBeanDefinitions(beanDefinitionReader);
}
继续跟踪最后一行的 loadBeanDefinitions(beanDefinitionReader) 源码:
# AbstractXmlApplicationContext
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
Resource[] configResources = getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
String[] configLocations = getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}
这里看一下这个getConfigLocations()这个方法,该方法从AbstractRefreshableConfigApplicationContext类的configLocations这个属性变量中获取所有的位置信息,这个变量是一个String类型的数组。那这个属性值是在什么时候赋值的呢?其实是在ClassPathXmlApplicationContext的构造函数中,调用了setConfigLocations(configLocations)设置进去的。
继续往下:
# AbstractBeanDefinitionReader
@Override
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
Assert.notNull(locations, "Location array must not be null");
int count = 0;
for (String location : locations) {
count += loadBeanDefinitions(location);
}
return count;
}
这里遍历了传过来的位置,继续看源码, 其中省略了日志和异常等不重要的源码:
# AbstractBeanDefinitionReader
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
// resourceLoader的类型为ClassPathXmlApplicationContext
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
}
if (resourceLoader instanceof ResourcePatternResolver) {
try {
// 将location对应路径下的资源文件抽象成Resource对象
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
// 核心方法
int count = loadBeanDefinitions(resources);
if (actualResources != null) {
Collections.addAll(actualResources, resources);
}
return count;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
else {
Resource resource = resourceLoader.getResource(location);
int count = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
return count;
}
}
方法看起来较多,但其实主要完成了一件事:由传入的location位置,找到对应的xml,并抽象成Resource对象,再通过loadBeanDefinitions方法完成BeanDefinition的注册。 下面继续跟踪loadBeanDefinitions方法:
# AbstractBeanDefinitionReader
@Override
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
Assert.notNull(resources, "Resource array must not be null");
int count = 0;
for (Resource resource : resources) {
count += loadBeanDefinitions(resource);
}
return count;
}
此时的参数类型为Resource,省略中间的调用部分,继续跟踪:
# XmlBeanDefinitionReader
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
// 使用ThreadLocal
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
// 若返回false, 说明集合中存在了, 抛出异常
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
// 最终实现将Resource转换成BeanDefinition
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
// 处理结束之后, 从集合中移除
currentResources.remove(encodedResource);
// 清空ThreadLocal
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
方法中resourcesCurrentlyBeingLoaded属性的定义为:ThreadLocal<Set>,使用线程本地变量,存储编码过后的Resource资源。通过ThreadLocal和Set来保证,一个线程尚未处理完成Resource,又有另一个线程进入该方法,不会再去处理该Resource资源。
再来看doLoadBeanDefinitions(..)方法,源码如下,省略异常处理代码:
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
try {
// inputSource包装了从Resource中读取的输入流,
// 根据xml配置文件, 获得Document对象
// XML的解析是基于更高效的SAX的方式
Document doc = doLoadDocument(inputSource, resource);
int count = registerBeanDefinitions(doc, resource);
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
}
看下doLoadDocument方法:
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
// 留意getValidationModeForResource(resource)
// 实际上该方法的作用为:探测xml的模式,
// 返回值有四种情况之一:XmlValidationModeDetector.VALIDATION_NONE=0 | VALIDATION_AUTO=1 | VALIDATION_DTD=2 | VALIDATION_XSD=3
// 根据返回的模式不同, 使用不同的方式验证. 例如, 返回的是2, 则使用基于DTD的方式进行xml格式的验证
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
该方法得到一个Document对象并返回,其中的getValidationModeForResource方法是用来分析xml的模式,以便作格式校验。 下面重点分析registerBeanDefinitions方法,源码如下:
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 获取Document读取器, 通过该对象来读取文档内容
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
// 由documentReader完成Document的解析, 并完成BeanDefinition的注册工作
/**
* createReaderContext:
* 这里createReaderContext()创建的XmlReadContext, 在解析自定义标签/命名空间的时候, 会使用到该对象
* 同时初始化了自定义标签的解析处理器(根据 spring-beans模块里的"META-INF/spring.handlers"文件)
*/
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
主要完成两件事:
- 通过反射创建一个Document读取器,负责DOM的解析工作
- 注册BeanDefinition,并统计注册前后的BeanDefinition的数量
继续跟踪registerBeanDefinitions()方法:
# DefaultBeanDefinitionDocumentReader
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
doRegisterBeanDefinitions(doc.getDocumentElement());
}
# DefaultBeanDefinitionDocumentReader
protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
// 和profile相关的处理逻辑
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
return;
}
}
}
preProcessXml(root);
// 解析的主要方法
parseBeanDefinitions方法(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}
主要做了两件事
- 处理profile相关的逻辑,profile在springboot中使用,可以方便的切换各种环境
- 解析注册BeanDefinition、并在前后提供两个可以扩展的模板方法
下面重点分析 parseBeanDefinitions 方法
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
// 解析自定义标签元素
delegate.parseCustomElement(ele);
}
}
}
}
else {
// 解析自定义标签元素
delegate.parseCustomElement(root);
}
}
方法分别解析了默认的标签元素和自定义的标签元素。这里解释一下这两个概念
- 自定义标签:以
<xxx:xx>指定前缀形式的声明,如<context:component-scan/>、<aop:config>等,这些前缀需要在xml头部的命名空间中引入。- 默认标签:最常用的就是
<bean>标签了,即不需要指定前缀,类似的还有<import>等
本文着重分析默认标签的解析。关于自定义标签,后续文章会详细说明。继续看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);
}
}
通过上面的源码,也可以知道,默认的标签一共有四个,在这里通过四个if分别进行了处理:import、alias、bean、beans。
- importBeanDefinitionResource:spring的xml文件可以分成多各文件,通过import进行汇总。解析的时候就会用到此方法,实际是个递归方法,这里不做说明。
- processAliasRegistration:处理alias别名标签,例如下面的xml声明中,person是user的别名,user和person指的都是同一个实例,不重要。
- processBeanDefinition:解析bean标签,核心处理逻辑,下面会重点来说。
- doRegisterBeanDefinitions:处理beans,又是一个递归处理,不重要。
别名标签的使用:
<alias name="user" alias="person"></alias>
<bean id="user" class="xxx.xxx.User">
来看processBeanDefinition()方法:
# DefaultBeanDefinitionDocumentReader
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
// 通过代理解析Element节点
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
// 看名字应该是修饰Bean的定义信息的,没仔细看过。
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// 上面解析完成后, 这里开始进行注册BeanDefinition
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));
}
}
该方法主要完成三件事:
- 解析得到一个BeanDefinition的包装类
- 完成BeanDefinition的注册
- 触发一个通知事件
先看第一件事,也即第一行的委派解析delegate.parseBeanDefinitionElement(ele), 返回的是一个BeanDefinition实例的包装类,来看源码:
# BeanDefinitionParserDelegate
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
// 调用了下面的解析方法
return parseBeanDefinitionElement(ele, null);
}
// 解析
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
String id = ele.getAttribute(ID_ATTRIBUTE);
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));
}
// 默认id为beanName
String beanName = id;
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
// 如果没有配置id的话,将会取第一个别名当做beanName
beanName = aliases.remove(0);
}
// 会进入if里,因为传过来的就是值就是null
if (containingBean == null) {
// 校验beanName、alias是否重复
// 通过HashSet<String>用来存所有处理过的name, 防止重复处理, 该集合变量定义在BeanDefinitionParserDelegate#usedNames中
checkNameUniqueness(beanName, aliases, ele);
}
// 这里就开始解析xml的剩余元素了,得到一个beanDefinition
// 上面只是解析了核心的id, bean中其他详细的属性和子标签, 则在此处进行了解析处理
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
if (beanDefinition != null) {
if (!StringUtils.hasText(beanName)) {
try {
if (containingBean != null) {
// 生成bean的变量名称
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);
}
}
} catch (Exception ex) {
error(ex.getMessage(), ele);
return null;
}
}
String[] aliasesArray = StringUtils.toStringArray(aliases);
// 把beanDefinition封装成Holder
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}
return null;
}
上面的源码完成了BeanDefinition包装类BeanDefinitionHolder实例的创建,分两步执行:
- 优先处理id、name和别名
- 解析bean标签的其他属性、以及子标签:parseBeanDefinitionElement()方法
在分析步骤二之前,我们先来深入分析BeanDefinition,在文章的开头,简单描述了什么是BeanDefinition:xml文件内容的类形式描述。BeanDefinition实际是一个接口,继承关系如下图:
三个类简单说明:
- AbstractBeanDefinition:BeanDefinition接口的中规范的抽象实现类、包括BeanDefinition的默认属性的设置,如scop、懒加载等
- GenericBeanDefinition:继承自AbstractBeanDefinition,新增了一个parent属性。一般使用这个类即可。
- RootBeanDefinition:可合并的BeanDefinition。在Spring实例化bean的时候,getMergedLocalBeanDefinition(..)方法返回的就是RootBeanDefinition。这个和父子bean结合使用的时候,子的BeanDefinition,是可以覆盖父的BeanDefinition中相同的属性的。 Spring中关于BeanDefinition的类很多,有兴趣可以查阅相关的文章。在本篇的文末会给出父子Bean的使用参考。
下面可以继续说明步骤二了:parseBeanDefinitionElement(..),省略了异常和finally,代码如下:
# BeanDefinitionParserDelegate
public AbstractBeanDefinition parseBeanDefinitionElement(
Element ele, String beanName, @Nullable BeanDefinition containingBean) {
this.parseState.push(new BeanEntry(beanName));
// 获取class属性
String className = null;
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}
// 获取parent属性,文末会给出用法参考
String parent = null;
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
}
try {
// 直接通过new的方式, 创建了一个GenericBeanDefinition的实例,
// 并设置parentName属性值, parentName属性是
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
// 解析xml中bean标签中的属性:
// 包括:scope、autowrite、abstract lazy-init depends-on init-method destroy-method factory-method factory-bean
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
//
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
// 下面开始解析bean标签中的字标签
// 解析meta子标签:<meta key="key1" value="固定值"/>
parseMetaElements(ele, bd);
// 解析lockup-method子标签:<lookup-method name="方法名" bean="bean名称"/>
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
// 解析replaced-method子标签:
// <replaced-method name="方法名" replacer="bean名称">
// <arg-type>参数类型,用于确认唯一的方法</arg-type>
// </replaced-method>
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
// 解析constructor-arg子标签:<constructor-arg ref="bean" value="固定值" type="参数类型" name="参数名称" index="索引"/>
parseConstructorArgElements(ele, bd);
// 解析property子标签:<property name="key1" value="固定值" ref="beanRef"/>
parsePropertyElements(ele, bd);
// 解析qualifier子标签:<qualifier type="bean类型" value="限定的bean的名称"/>
parseQualifierElements(ele, bd);
bd.setResource(this.readerContext.getResource());
bd.setSource(extractSource(ele));
this.parseState.pop();
return bd;
}
// 省略异常处理
return null;
}
该方法主要分为两大块:
- 解析bean标签的各种属性值,设置到创建好的BeanDefinition实例中
- 解析bean标签的子标签
先看解析bean的属性值源码:
public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,
BeanDefinition containingBean, AbstractBeanDefinition bd) {
if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {
error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele);
} else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) {
bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
} else if (containingBean != null) {
bd.setScope(containingBean.getScope());
}
if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {
bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));
}
String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);
if (isDefaultValue(lazyInit)) {
lazyInit = this.defaults.getLazyInit();
}
bd.setLazyInit(TRUE_VALUE.equals(lazyInit));
String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);
bd.setAutowireMode(getAutowireMode(autowire));
if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {
String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);
bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS));
}
String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE);
if (isDefaultValue(autowireCandidate)) {
String candidatePattern = this.defaults.getAutowireCandidates();
if (candidatePattern != null) {
String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern);
bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName));
}
} else {
bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate));
}
if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {
bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));
}
if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) {
String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE);
bd.setInitMethodName(initMethodName);
} else if (this.defaults.getInitMethod() != null) {
bd.setInitMethodName(this.defaults.getInitMethod());
bd.setEnforceInitMethod(false);
}
if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) {
String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE);
bd.setDestroyMethodName(destroyMethodName);
} else if (this.defaults.getDestroyMethod() != null) {
bd.setDestroyMethodName(this.defaults.getDestroyMethod());
bd.setEnforceDestroyMethod(false);
}
if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) {
bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE));
}
if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) {
bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE));
}
return bd;
}
上面这段代码比较简单,通过Element获取到每个属性值,再设置到BeanDefinition实例中,最后返回该BeanDefinition。 再来看解析子标签的几个方法:
// 解析meta子标签:<meta key="key1" value="固定值"/>
public void parseMetaElements(Element ele, BeanMetadataAttributeAccessor attributeAccessor) {
NodeList nl = ele.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (isCandidateElement(node) && nodeNameEquals(node, META_ELEMENT)) {
Element metaElement = (Element) node;
// 分别获取key与value的元素值
String key = metaElement.getAttribute(KEY_ATTRIBUTE);
String value = metaElement.getAtBeanMetadataAttribute实例中tribute(VALUE_ATTRIBUTE);
// 将key与value封装到attribute中
// 留意BeanMetadataAttribute其实是AbstractBeanDefinition的父类,
BeanMetadataAttribute attribute = new BeanMetadataAttribute(key, value);
attribute.setSource(extractSource(metaElement));
// 这里实际上是添加到了BeanMetadataAttribute的父类:AttributeAccessorSupport的attributes属性中了
// 该属性定义如下:private final Map<String, Object> attributes = new LinkedHashMap<>();
// 是一个map类型, 可以方便的对元数据进行扩展
attributeAccessor.addMetadataAttribute(attribute);
}
}
}
// 解析lockup-method子标签:<lookup-method name="方法名" bean="bean名称"/>
public void parseLookupOverrideSubElements(Element beanEle, MethodOverrides overrides) {
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
// 循环所有的子标签, 找出其中的lockup-method标签
if (isCandidateElement(node) && nodeNameEquals(node, LOOKUP_METHOD_ELEMENT)) {
Element ele = (Element) node;
String methodName = ele.getAttribute(NAME_ATTRIBUTE);
String beanRef = ele.getAttribute(BEAN_ELEMENT);
// 封装成LookupOverride对象
LookupOverride override = new LookupOverride(methodName, beanRef);
override.setSource(extractSource(ele));
// 加入到Set集合中
overrides.addOverride(override);
}
}
}
// 解析replaced-method子标签:
// <replaced-method name="方法名" replacer="bean名称">
// <arg-type>参数类型,用于确认唯一的方法</arg-type>
// </replaced-method>
public void parseReplacedMethodSubElements(Element beanEle, MethodOverrides overrides) {
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (isCandidateElement(node) && nodeNameEquals(node, REPLACED_METHOD_ELEMENT)) {
Element replacedMethodEle = (Element) node;
String name = replacedMethodEle.getAttribute(NAME_ATTRIBUTE);
String callback = replacedMethodEle.getAttribute(REPLACER_ATTRIBUTE);
ReplaceOverride replaceOverride = new ReplaceOverride(name, callback);
// Look for arg-type match elements.
List<Element> argTypeEles = DomUtils.getChildElementsByTagName(replacedMethodEle, ARG_TYPE_ELEMENT);
for (Element argTypeEle : argTypeEles) {
String match = argTypeEle.getAttribute(ARG_TYPE_MATCH_ATTRIBUTE);
match = (StringUtils.hasText(match) ? match : DomUtils.getTextValue(argTypeEle));
if (StringUtils.hasText(match)) {
replaceOverride.addTypeIdentifier(match);
}
}
replaceOverride.setSource(extractSource(replacedMethodEle));
overrides.addOverride(replaceOverride);
}
}
}
// 解析property子标签:<property name="key1" value="固定值" ref="beanRef"/>
public void parsePropertyElements(Element beanEle, BeanDefinition bd) {
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (isCandidateElement(node) && nodeNameEquals(node, PROPERTY_ELEMENT)) {
parsePropertyElement((Element) node, bd);
}
}
}
至此,BeanDefinition的解析与封装就介绍完了,最后我们再看一下Spring是如何完成BeanDefinition注册的。先回顾一下代码:
来看processBeanDefinition()方法:
# DefaultBeanDefinitionDocumentReader
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
// 通过代理解析Element节点
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
// 对bean标签解析出来的BeanDefinition进行装饰
// 用的很少,但此处的是个spi很重要
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// 上面解析完成后, 这里开始进行注册BeanDefinition
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));
}
}
来看一下registerBeanDefinition这个静态方法:
# BeanDefinitionReaderUtils
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// Register bean definition under primary name.
String beanName = definitionHolder.getBeanName();
// 当基于纯注解的方式启动, 且传入的参数是class时, 会在这里调用BeanDefinition的注册
// 基于自定义标签元素处理时, 也会进入该方法, 进行BeanDefinition的注册
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);
}
}
}
继续跟踪registerBeanDefinition方法,Spring中bean的描述是通过BeanDefinition来完成的,而BeanDefinition的基本操作是通过BeanDefinitionRegistry这个策略接口来完成的,基本操作主要为:注册BeanDefinition、移除BeanDefinition、获取BeanDefinition等。代码如下:
public interface BeanDefinitionRegistry extends AliasRegistry {
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException;
void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
boolean containsBeanDefinition(String beanName);
String[] getBeanDefinitionNames();
int getBeanDefinitionCount();
boolean isBeanNameInUse(String beanName);
}
有关策略的几个接口,这里给一张图,简单说明:
继续来看BeanDefinition的注册。
# DefaultListableBeanFactory
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
((AbstractBeanDefinition) beanDefinition).validate();
} catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
}
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
if (existingDefinition != null) {
// 处理BeanDefinition重复的问题, 前面已经说过了
if (!isAllowBeanDefinitionOverriding()) {
throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
} else if (existingDefinition.getRole() < beanDefinition.getRole()) {
// 仅仅记录日志
} else if (!beanDefinition.equals(existingDefinition)) {
// 仅仅记录日志
} else {
// 仅仅记录日志
}
this.beanDefinitionMap.put(beanName, beanDefinition);
}
else {
if (hasBeanCreationStarted()) {
// Cannot modify startup-time collection elements anymore (for stable iteration)
// 为什么if这里加了同步锁,而下面的else没有加锁呢?
synchronized (this.beanDefinitionMap) {
// 默认是不会进入这个分支的,那什么时候会进入到这个里面呢?
// 通过@Configuration的方式注入一个BeanDefinition的时候, 其实是可以进入这里的
// 在parse()和validate()结束之后, 调用loadBeanDefinitions()时, 会进入if里
this.beanDefinitionMap.put(beanName, beanDefinition);
// 现有容量加1, 即刚好是完成BeanDefinition注册,所需要的容量
List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
//加入所有已注册的beanDefinitionName
updatedDefinitions.addAll(this.beanDefinitionNames);
//加入本次注册的beanName
updatedDefinitions.add(beanName);
//更新已注册的beanDefinitionName列表为, 这里操作为什么要这么的麻烦?其实是为了节省内存空间,有兴趣可以阅读ArrayList的源码
this.beanDefinitionNames = updatedDefinitions;
removeManualSingletonName(beanName);
}
} else {
// 在loadBeanDefinition的过程中, 将beanDefinition添加到map和list集合中, 即赋值
// 默认在此处完成了真正的bean定义信息的注册工作
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
removeManualSingletonName(beanName);
}
this.frozenBeanDefinitionNames = null;
}
if (existingDefinition != null || containsSingleton(beanName)) {
resetBeanDefinition(beanName);
} else if (isConfigurationFrozen()) {
clearByTypeCache();
}
}
这里完成了BeanDefinition的注册,即将BeanDefinition分别放入beanDefinitionMap、beanDefinitionNames这两个属性中,来看下这两个变量的定义:
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
private volatile List<String> beanDefinitionNames = new ArrayList<>(256);
当正真开始进行Bean实例化的时候,就需要使用到这两个变量了。这里等到写生命周期和实例化的时候,再探讨吧。
至此,Spring中BeanDefinition的注册就完成了。关于最后的事件通知,本篇亦不作说明。
文章的最后,我们再来思考一个问题,在上面最后一个方法中,if里加了锁,而else却没有加锁?代码如下:
// ................................
if (hasBeanCreationStarted()) {
// Cannot modify startup-time collection elements anymore (for stable iteration)
// 为什么if这里加了同步锁,而下面的else没有加锁呢?
synchronized (this.beanDefinitionMap) {
// 注册BeanDefinition
}
} else {
//
// 注册BeanDefinition
}
// ................................
我们分析一下:
if中的hasBeanCreationStarted()是用来判断Spring是否正在进行实例化操作。当Spring早实例化Bean的时候,会将该beanName存入alreadyCreated这个Set集合中,整个Bean实例化完成之后,又会清空这个Set集合。
因此进入该if,即表示alreadyCreated不为空,表示Spring正在进行Bean的实例化操作。而Spring的实例化过程是需要遍历beanDefinitionNames集合,这个集合如前文所说,是一个非线程安全的ArrayList类型。
这么说有点啰嗦了,其实就是在遍历的过程中,还想往里面添加,这就会导致线程不安全了。
那什么时候会出现这种情况?给出一个连接,有兴趣的同学可以自己研究:
总结
最后,本篇主要介绍了Resource、BeanDefinition的基本概念、以及默认标签的解析处理、BeanDefinition的注册操作。我们用一张流程图来总结本篇文档
文末
本篇的文末,给出前面的parent和abstract的用法参考: 定义两个类
@Data
public class MyChild {
private String name;
private int age;
private String address;
}
@Data
public class MyParent {
private String name;
private String address;
}
xml文件如下:parent其实是一个模板,可以有包括child1、child2等等来引用该模板中的相同属性值。简单起见这里,只给出了一个
spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="parent" abstract="true" class="MyParent">
<property name="name" value="老子"></property>
<property name="address" value="江苏二"></property>
</bean>
<bean id="child" parent="parent" class="MyChild">
<property name="name" value="儿子"></property>
<property name="age" value="20"></property>
</bean>
</beans>
测试类如下:
public class App {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring.xml");
MyChild child = (MyChild) context.getBean("child");
System.out.println(child);
}
}
程序输出:
MyChild{name='儿子', age=20, address='江苏二'},可以看出,child的address属性的值,其实是"继承"自 xml中的"父类",当公共属性值较多时,该方式就会比较有效。使用比较简单,但其设计思路,很值得我们借鉴,通常我们会将多个类继承(extends)该类(java中的继承),而Spring则是通过父子类的属性替换实现的。