共计 14375 个字符,预计需要花费 36 分钟才能阅读完成。
注释
在上一篇咱们曾经实现了从 xml 配置文件到 BeanDefinition 的转换,转换后的实例是 GenericBeanDefinition 的实例。本文次要来看看标签解析残余局部及 BeanDefinition 的注册。
默认标签中的自定义标签解析
在上篇博文中咱们曾经剖析了对于默认标签的解析,咱们持续看戏之前的代码,如下图片中有一个办法:delegate.decorateBeanDefinitionIfRequired(ele, bdHolder)
这个办法的作用是什么呢?首先咱们看下这种场景,如下配置文件:
<bean id="demo" class="com.dabin.spring.MyTestBean">
<property name="beanName" value="bean demo1"/>
<meta key="demo" value="demo"/>
<mybean:username="mybean"/>
</bean>
这个配置文件中有个自定义的标签,decorateBeanDefinitionIfRequired 办法就是用来解决这种状况的,其中的 null 是用来传递父级 BeanDefinition 的,咱们进入到其办法体:
public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder definitionHolder) {return decorateBeanDefinitionIfRequired(ele, definitionHolder, null);
}
public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder definitionHolder, @Nullable BeanDefinition containingBd) {
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;
}
咱们看到下面的代码有两个遍历操作,一个是用于对所有的属性进行遍历解决,另一个是对所有的子节点进行解决,两个遍历操作都用到了 decorateIfRequired(node, finalDefinition, containingBd); 办法,咱们持续跟踪代码,进入办法体:
public BeanDefinitionHolder decorateIfRequired(Node node, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) {
// 获取自定义标签的命名空间
String namespaceUri = getNamespaceURI(node);
// 过滤掉默认命名标签
if (namespaceUri != null && !isDefaultNamespace(namespaceUri)) {
// 获取相应的处理器
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler != null) {
// 进行装璜解决
BeanDefinitionHolder decorated =
handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd));
if (decorated != null) {return decorated;}
}
else if (namespaceUri.startsWith("http://www.springframework.org/")) {error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", node);
}
else {if (logger.isDebugEnabled()) {logger.debug("No Spring NamespaceHandler found for XML schema namespace [" + namespaceUri + "]");
}
}
}
return originalDef;
}
public String getNamespaceURI(Node node) {return node.getNamespaceURI();
}
public boolean isDefaultNamespace(@Nullable String namespaceUri) {
//BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans";
return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri));
}
首先获取自定义标签的命名空间,如果不是默认的命名空间则依据该命名空间获取相应的处理器,最初调用处理器的 decorate()
进行装璜解决。具体的装璜过程这里不进行讲述,在前面剖析自定义标签时会做具体阐明。
注册解析的 BeanDefinition
对于配置文件,解析和装璜实现之后,对于失去的 beanDefinition 曾经能够满足后续的应用要求了,还剩下注册,也就是 processBeanDefinition 函数中的 BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder,getReaderContext().getRegistry()) 代码的解析了。进入办法体:
public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// Register bean definition under primary name.
// 应用 beanName 做惟一标识注册
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);
}
}
}
从下面的代码咱们看到是用了 beanName 作为惟一标示进行注册的,而后注册了所有的别名 aliase。而 beanDefinition 最终都是注册到 BeanDefinitionRegistry 中,接下来咱们具体看下注册流程。
通过 beanName 注册 BeanDefinition
在 spring 中除了应用 beanName 作为 key 将 BeanDefinition 放入 Map 中还做了其余一些事件,咱们看下办法 registerBeanDefinition 代码,BeanDefinitionRegistry 是一个接口,他有三个实现类,DefaultListableBeanFactory、SimpleBeanDefinitionRegistry、GenericApplicationContext,其中 SimpleBeanDefinitionRegistry 非常简单,而 GenericApplicationContext 最终也是应用的 DefaultListableBeanFactory 中的实现办法,咱们看下代码:
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
// 校验 beanName 与 beanDefinition
Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
// 校验 BeanDefinition
// 这是注册前的最初一次校验了,次要是对属性 methodOverrides 进行校验
((AbstractBeanDefinition) beanDefinition).validate();}
catch (BeanDefinitionValidationException ex) {throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
}
BeanDefinition oldBeanDefinition;
// 从缓存中获取指定 beanName 的 BeanDefinition
oldBeanDefinition = this.beanDefinitionMap.get(beanName);
/**
* 如果存在
*/
if (oldBeanDefinition != null) {
// 如果存在然而不容许笼罩,抛出异样
if (!isAllowBeanDefinitionOverriding()) {throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Cannot register bean definition [" + beanDefinition + "] for bean'" + beanName +
"': There is already [" + oldBeanDefinition + "] bound.");
}
//
else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
if (this.logger.isWarnEnabled()) {
this.logger.warn("Overriding user-defined bean definition for bean'" + beanName +
"'with a framework-generated bean definition: replacing [" +
oldBeanDefinition + "] with [" + beanDefinition + "]");
}
}
// 笼罩 beanDefinition 与 被笼罩的 beanDefinition 不是同类
else if (!beanDefinition.equals(oldBeanDefinition)) {if (this.logger.isInfoEnabled()) {
this.logger.info("Overriding bean definition for bean'" + beanName +
"'with a different definition: replacing [" + oldBeanDefinition +
"] with [" + beanDefinition + "]");
}
}
else {if (this.logger.isDebugEnabled()) {
this.logger.debug("Overriding bean definition for bean'" + beanName +
"'with an equivalent definition: replacing [" + oldBeanDefinition +
"] with [" + beanDefinition + "]");
}
}
// 容许笼罩,间接笼罩原有的 BeanDefinition
this.beanDefinitionMap.put(beanName, beanDefinition);
}
/**
* 不存在
*/
else {
// 检测创立 Bean 阶段是否曾经开启,如果开启了则须要对 beanDefinitionMap 进行并发管制
if (hasBeanCreationStarted()) {
// beanDefinitionMap 为全局变量,防止并发状况
synchronized (this.beanDefinitionMap) {
//
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
if (this.manualSingletonNames.contains(beanName)) {Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
updatedSingletons.remove(beanName);
this.manualSingletonNames = updatedSingletons;
}
}
}
else {
// 不会存在并发状况,间接设置
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
this.manualSingletonNames.remove(beanName);
}
this.frozenBeanDefinitionNames = null;
}
if (oldBeanDefinition != null || containsSingleton(beanName)) {
// 从新设置 beanName 对应的缓存
resetBeanDefinition(beanName);
}
}
处理过程如下:
- 首先 BeanDefinition 进行校验,该校验也是注册过程中的最初一次校验了,次要是对 AbstractBeanDefinition 的 methodOverrides 属性进行校验
- 依据 beanName 从缓存中获取 BeanDefinition,如果缓存中存在,则依据 allowBeanDefinitionOverriding 标记来判断是否容许笼罩,如果容许则间接笼罩,否则抛出 BeanDefinitionStoreException 异样
- 若缓存中没有指定 beanName 的 BeanDefinition,则判断以后阶段是否曾经开始了 Bean 的创立阶段(),如果是,则须要对 beanDefinitionMap 进行加锁管制并发问题,否则间接设置即可。对于
hasBeanCreationStarted()
办法后续做具体介绍,这里不过多论述。 - 若缓存中存在该 beanName 或者 单利 bean 汇合中存在该 beanName,则调用
resetBeanDefinition()
重置 BeanDefinition 缓存。
其实整段代码的外围就在于 this.beanDefinitionMap.put(beanName, beanDefinition);
。BeanDefinition 的缓存也不是神奇的货色,就是定义 一个 ConcurrentHashMap,key 为 beanName,value 为 BeanDefinition。
通过别名注册 BeanDefinition
通过别名注册 BeanDefinition 最终是在 SimpleBeanDefinitionRegistry 中实现的,咱们看下代码:
public void registerAlias(String name, String alias) {Assert.hasText(name, "'name' must not be empty");
Assert.hasText(alias, "'alias' must not be empty");
synchronized (this.aliasMap) {if (alias.equals(name)) {this.aliasMap.remove(alias);
}
else {String registeredName = this.aliasMap.get(alias);
if (registeredName != null) {if (registeredName.equals(name)) {
// An existing alias - no need to re-register
return;
}
if (!allowAliasOverriding()) {
throw new IllegalStateException("Cannot register alias'" + alias + "'for name'" +
name + "': It is already registered for name'" + registeredName + "'.");
}
}
// 当 A ->B 存在时,若再次出现 A ->C->B 时候则会抛出异样。checkForAliasCircle(name, alias);
this.aliasMap.put(alias, name);
}
}
}
上述代码的流程总结如下:
(1)alias 与 beanName 雷同状况解决,若 alias 与 beanName 并名称雷同则不须要解决并删除原有的 alias
(2)alias 笼罩解决。若 aliasName 曾经应用并曾经指向了另一 beanName 则须要用户的设置进行解决
(3)alias 循环查看,当 A ->B 存在时,若再次出现 A ->C->B 时候则会抛出异样。
alias 标签的解析
对应 bean 标签的解析是最外围的性能,对于 alias、import、beans 标签的解析都是基于 bean 标签解析的,接下来我就剖析下 alias 标签的解析。咱们回到 parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) 办法,持续看下办法体,如下图所示:
对 bean 进行定义时,除了用 id 来 指定名称外,为了提供多个名称,能够应用 alias 标签来指定。而所有这些名称都指向同一个 bean。在 XML 配置文件中,可用独自的元素来实现 bean 别名的定义。咱们能够间接应用 bean 标签中的 name 属性,如下:
<bean id="demo" class="com.dabin.spring.MyTestBean" name="demo1,demo2">
<property name="beanName" value="bean demo1"/>
</bean>
在 Spring 还有另外一种申明别名的形式:
<bean id="myTestBean" class="com.dabin.spring.MyTestBean"/>
<alias name="myTestBean" alias="testBean1,testBean2"/>
咱们具体看下 alias 标签的解析过程,解析应用的办法 processAliasRegistration(ele),办法体如下:
protected void processAliasRegistration(Element ele) {
// 获取 beanName
String name = ele.getAttribute(NAME_ATTRIBUTE);
// 获取 alias
String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
boolean valid = true;
if (!StringUtils.hasText(name)) {getReaderContext().error("Name must not be empty", ele);
valid = false;
}
if (!StringUtils.hasText(alias)) {getReaderContext().error("Alias must not be empty", ele);
valid = false;
}
if (valid) {
try {
// 注册 alias
getReaderContext().getRegistry().registerAlias(name, alias);
}
catch (Exception ex) {getReaderContext().error("Failed to register alias'" + alias +
"'for bean with name'" + name + "'", ele, ex);
}
getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));
}
}
通过代码能够发现解析流程与 bean 中的 alias 解析大同小异,都是讲 beanName 与别名 alias 组成一对注册到 registry 中。跟踪代码最终应用了 SimpleAliasRegistry 中的 registerAlias(String name, String alias) 办法
import 标签的解析
对于 Spring 配置文件的编写,经验过大型项目的人都晓得,外面有太多的配置文件了。根本采纳的形式都是分模块,分模块的形式很多,应用 import 就是其中一种,例如咱们能够结构这样的 Spring 配置文件:
<?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="demo" class="com.dabin.spring.MyTestBean" name="demo1,demo2">
<property name="beanName" value="bean demo1"/>
</bean>
<import resource="lookup-method.xml"/>
<import resource="replaced-method.xml"/>
</beans>
applicationContext.xml 文件中应用 import 形式导入有模块配置文件,当前若有新模块入加,那就能够简略批改这个文件了。这样大大简化了配置前期保护的复杂度,并使配置模块化,易于治理。咱们来看看 Spring 是如何解析 import 配置文件的呢。解析 import 标签应用的是 importBeanDefinitionResource(ele),进入办法体:
protected void importBeanDefinitionResource(Element ele) {
// 获取 resource 的属性值
String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
// 为空,间接退出
if (!StringUtils.hasText(location)) {getReaderContext().error("Resource location must not be empty", ele);
return;
}
// 解析零碎属性,格局如:"${user.dir}"
location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);
Set<Resource> actualResources = new LinkedHashSet<>(4);
// 判断 location 是相对路径还是绝对路径
boolean absoluteLocation = false;
try {absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();}
catch (URISyntaxException ex) {
// cannot convert to an URI, considering the location relative
// unless it is the well-known Spring prefix "classpath*:"
}
// 绝对路径
if (absoluteLocation) {
try {
// 间接依据地址加载相应的配置文件
int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
if (logger.isDebugEnabled()) {logger.debug("Imported" + importCount + "bean definitions from URL location [" + location + "]");
}
}
catch (BeanDefinitionStoreException ex) {getReaderContext().error("Failed to import bean definitions from URL location [" + location + "]", ele, ex);
}
}
else {
// 相对路径则依据相应的地址计算出绝对路径地址
try {
int importCount;
Resource relativeResource = getReaderContext().getResource().createRelative(location);
if (relativeResource.exists()) {importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
actualResources.add(relativeResource);
}
else {String baseLocation = getReaderContext().getResource().getURL().toString();
importCount = getReaderContext().getReader().loadBeanDefinitions(StringUtils.applyRelativePath(baseLocation, location), actualResources);
}
if (logger.isDebugEnabled()) {logger.debug("Imported" + importCount + "bean definitions from relative location [" + location + "]");
}
}
catch (IOException ex) {getReaderContext().error("Failed to resolve current resource location", ele, ex);
}
catch (BeanDefinitionStoreException ex) {getReaderContext().error("Failed to import bean definitions from relative location [" + location + "]",
ele, ex);
}
}
// 解析胜利后,进行监听器激活解决
Resource[] actResArray = actualResources.toArray(new Resource[0]);
getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
}
解析 import 过程较为清晰,整个过程如下:
- 获取 source 属性的值,该值示意资源的门路
- 解析门路中的零碎属性,如”${user.dir}”
- 判断资源门路 location 是绝对路径还是相对路径
- 如果是绝对路径,则调递归调用 Bean 的解析过程,进行另一次的解析
- 如果是相对路径,则先计算出绝对路径失去 Resource,而后进行解析
- 告诉监听器,实现解析
判断门路
办法通过以下办法来判断 location 是为相对路径还是绝对路径:
absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
判断绝对路径的规定如下:
- 以 classpath*: 或者 classpath: 结尾为绝对路径
- 可能通过该 location 构建出
java.net.URL
为绝对路径 - 依据 location 结构
java.net.URI
判断调用isAbsolute()
判断是否为绝对路径
如果 location 为绝对路径则调用 loadBeanDefinitions()
,该办法在 AbstractBeanDefinitionReader 中定义。
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException("Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null) {for (Resource resource : resources) {actualResources.add(resource);
}
}
if (logger.isDebugEnabled()) {logger.debug("Loaded" + loadCount + "bean definitions from location pattern [" + location + "]");
}
return loadCount;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
else {
// Can only load single resources by absolute URL.
Resource resource = resourceLoader.getResource(location);
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {actualResources.add(resource);
}
if (logger.isDebugEnabled()) {logger.debug("Loaded" + loadCount + "bean definitions from location [" + location + "]");
}
return loadCount;
}
}
整个逻辑比较简单,首先获取 ResourceLoader,而后依据不同的 ResourceLoader 执行不同的逻辑,次要是可能存在多个 Resource,然而最终都会回归到 XmlBeanDefinitionReader.loadBeanDefinitions()
,所以这是一个递归的过程。
至此,import 标签解析结束,整个过程比拟清晰明了:获取 source 属性值,失去正确的资源门路,而后调用 loadBeanDefinitions()
办法进行递归的 BeanDefinition 加载。