DTD(Document Type Definition)文档类型定义—是XML文档的一部分,XSD(XML Scheme Definition)XML 规范定义 --也是一份XML文件
Spring XML Bean 的解析
默认标签的解析:
Import、 alias、 bean 和 beans
bean标签解析
bean 的 name 首先取 定义的 id,如果id未定义且存在别名列表,就取第一个别名作为 bean 的 name,如果根据id 和别名列表都没有找到 bean 的名称则根据 Spring bean 名称的生成规则生成该 bean 的名称。
bean的 id 和 别名必须在一个容器中唯一,检查唯一性不通过则打印错误日志,不报错;注意检查通过后 bean 的 name和别名都会被加入到 已经使用的 bean 名称集合中,下次检查下一个 bean 的时候使用。
BeanDefinitionHolder 包括 beanDefinition beanName和别名数组
new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray)
创建BeanDefinition 后,首先根据根据 className 设置 className或 Class 如果有 类加载器不为空,直接获取类设置Class,如果加载器为空则只设置 className
解析 XML中定义的 bean元素的 scope属性 设置 BeanDefinition ,如果当前 元素中未定义 scope且存在父级元素,则使用父级别元素定义的 scope 属性值作为当前 元素解析后的 BeanDefinition 的scope
Lazy-init属性没有设置或设置为 除 true外的其他字符串都被视为 该属性为 false
constructor-arg 该元素的子元素 只能有一个,超过一个则直接报错,注意:description和 meta属性元素除外
constructor-arg 的子元素最多一个,且不能和 constructor-arg 的属性 ref 或 value 共存。三者之能有一个存在
ref :RuntimeBeanReference
value : TypedStringValue
subElement: 子元素的解析有专门的方法解析--而不是直接返回 RuntimeBeanReference 或 TypedStringValue 子元素可以是:非默认空间的元素—调用嵌套自定义元素解析器解析,bean(parseBeanDefinitionElement 返回 BeanDefinitionHolder),ref(返回 RuntimeBeanReference ),idref(返回 RuntimeBeanNameReference),value( 该子元素中可以使用 type属性指定值的类型;返回 TypedStringValue),null(返回 TypedStringValue),array(该元素可以使用 value-type指定集合中元素类型,返回 ManagedArray),list(该元素可以使用 value-type指定集合中元素类型,返回 ManagedList),set(该元素可以使用 value-type指定集合中元素类型,返回 ManagedSet),set(map一定有字元素 entry 该子元素又可以使用 key key-ref value value-ref,,该元素可以使用 key-type指定集合key元素类型,使用 value-type指定集合value元素类型,返回 ManagedMap),props(一定有子元素 prop 该子元素又一定有 属性 key,返回ManagedProperties)
ConstructorArgumentValues
ValueHolder implements BeanMetadataElement
public interface BeanMetadataElement {
/**
* Return the configuration source {@code Object} for this metadata element
* (may be {@code null}).
*/
Object getSource();
}
属性指定 index 或未指定 index (type,name属性可选)
private final Map<Integer, ValueHolder> indexedArgumentValues = new LinkedHashMap<Integer, ValueHolder>(0);
private final List<ValueHolder> genericArgumentValues = new LinkedList<ValueHolder>();
元素的子元素的 property 必须要有 name 属性,且同一个元素中的不同子元素 property name不能重复,
property这个子元素的 嵌套子元素 或 属性 ref 或value 不能共存,接下来返回的就和 constructor-arg 这个子元素一样了。
只是最后这些信息这里是封装在了 PropertyValue 中
qualifier 子元素 用于指定唯一注入的 bean 属性有 type 指定 bean 的 class 且 type属性必须有,value属性可以选择性有,可能的嵌套子元素:attribute 且如果有嵌套子元素 attribute 则嵌套子元素必须包含 key ,value属性。信息封装到这个对象中 AutowireCandidateQualifier
RootBeanDefinition 原始 配置解析的 BeanDefinition ,存在嵌套的则使用 RootBeanDefinition 和ChildBeanDefinition
GenericBeanDefinition 是一站式的 BeanDefinition 是更新版本中加的,AbstractBeanDefinition 则是三者的抽象 实现了所有共同的功能。
默认 bean 标签中嵌套的自定义标签或属性解析
获取默认标签中的所有属性,所有子元素遍历找到不是默认命名空间的属性或元素,针对每一个自定义属性或元素获取对应的自定义命名空间,根据命名空间找到解析器解析自定义属性和标签后进行装饰。
alias标签的解析
<alias name="dataSource" alias="dataa"/>
<alias name="dataSource" alias="datab"/>
获取 beanName,获取alias;获取 beanName和 alias 这两个字段都不能为空,为空则跳过不注册,如果需要注册就将 alias 作为 key. beanName作为 value注册。
条件判断递归调用:
public boolean hasAlias(String name, String alias) {
for (Map.Entry<String, String> entry : this.aliasMap.entrySet()) {
String registeredName = entry.getValue();
if (registeredName.equals(name)) {
String registeredAlias = entry.getKey();
return (registeredAlias.equals(alias) || hasAlias(registeredAlias, alias));
}
}
return false;
}
import 标签解析
<import resource="classpath*:XXXXX.xml">
首先获取 resource 属性,该属性必须有,没有则直接返回不做进一步加载解析处理。
解析resource 属性 中的 系统属性变量,如: "${user.dir}"替换为配置的常量
判断 resource 属性 中配置的文件地址是URL还是相对路径
下列情况就是URL:
classpath*: , classpath: 开头的
能构建 URL的
new URL(resourceLocation)
如果是URL 则递归调用 bean 的解析过程
首先判断当前 ResourceLoader 是不是 ResourcePatternResolver,如果是则表示按照 通配符匹配合适的 Resource;
首先根据 resource 属性配置的 location 查找匹配上的 资源:
如果是classpath* 的则找到确定的根目录,查找根目录下的所有 资源,然后按照 location中 的 通配符匹配需要的资源返回
classpath*:spring/**/spring-load.xml -> classpath*:spring/ **/spring-load.xml*
classpath*:spring/spring-load.xml-> classpath*:spring/spring-load.xml
classpath*:spring/*/spring-load.xml -> classpath*:spring/ */spring-load.xml
protocol: vfs
JarURL:jar,zip,vfszip,wsjar
其他都是文件系统
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);
}
}
否则是当前文件系统的相对路径
先构建出相对资源,然后再匹配资源进行加载解析注册
自定义标签解析
先介绍如何实现自己的自定义标签
一般我们要实现自己的较为复杂的配置的时候,最原始的做法就是用原生态的方式去解析定义好的XML文件,然后转换为配置对象,但是这个方式的缺点是自己必须用原生态的方式去解析XML配置文件,实现起来比较麻烦;还有一种方式就是扩展Spring 定义的 Schema。第二种方式的实现需要以下几个步骤:
- 创建一个需要扩展的组件
- 定义一个XSD文件描述组件内容
- 创建一个文件,实现BeanDefinitionParser 接口,用来解析 XSD文件中的定义和组件定义
- 创建一个Handler文件,扩展自 NamespaceHandlerSupport,目的是将组件注册到 Spring容器
- 编写 Spring.handlers 和 Spring.shcema文件
示例如下:
-
编写 一个POJO 对象,用于接收解析配置文件得到的 Bean对象
import lombok.Data; @Data public class User { private String userName; private String email; }
-
定义一个XSD文件描述组件内容——该文件放在对应 自定义Handler 所在项目模块的 根目录下的 META-INF/文件夹下
<?xml version="1.0" encoding="UTF-8"?> <schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://jbpm.org/4.2/user" xmlns:tns="http://www.lexueba.com/schema/user" elementFormDefault="qualified"> <element name="user"> <complexType> <attribute name="id" type="string"/> <attribute name="userName" type="string"/> <attribute name="email" type="string"/> </complexType> </element> </schema>
在上面的 XSD 文件中描述了一个新的 targetNamespace, 并在这个空间中定义了一个 name 为user的element, user有3个属性id、 userName和email,其中email的类型为string。 这3个类主要用于验证Spring配置文件中向定义格式。 XSD文件是XMLDTD的替代者,使用XML在Schema 语言进行编写
-
创建一个文件 ,实现 BeanDefinitionParser接口,用来解析 XSD 文件中的定义和组件定义
import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; import org.springframework.util.StringUtils; import org.w3c.dom.Element; public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { @Override protected Class<?> getBeanClass(Element element) { return User.class; } @Override protected void doParse(Element element, BeanDefinitionBuilder builder) { String userName = element.getAttribute("userName"); String email = element.getAttribute("email"); if (StringUtils.hasText(userName)) { builder.addPropertyValue("userName", userName); if (StringUtils.hasText(email)) { builder.addPropertyValue("email", email); } } } }
-
创建一个 Handler 文件,扩展 自 NamespaceHandlerSupport,目的是将组件注册到Spring 容器 。
public class CustomeNamespaceHandler extends NamespaceHandlerSupport { @Override public void init() { registerBeanDefinitionParser("user", new UserBeanDefinitionParser()); } }
以上代码很简单, 无非是当遇到向定义标签<user:aaa这样类似于以 user开头的元素,就会把这个元素扔给对应的 UserBeanDefinitionParser去解析。
编写 Spring.handlers 和 Spring.schemas 文件, 默认位置是在对应工程Module 的META-INF/文件夹下(Spring项目加载首次加载所有Handler的时候会去META-INF/spring.handlers 这个配置文件中加载),当然,你可以通过 Spring 的扩展或者修改源码的方式改变路径 。
到这里,自定义 的配置就结束了,而 Spring加载自定义的大致流程是遇到自定义标签然后就去 Spring.handlers 和 Spring.schemas 中去找对应的 handler 和 XSD,默认位置是/META-INF/下,进而有找到对应的 handler以及解析元素的 Parser,从而完成了整个自定义元素的解析,也就是说向定义与 Spring 中默认的标准配置不同在于 Spring 将向定义标签解析的工作委托给了用户去实现 。
自定义标签解析源码解读
首先获取自定义标签元素的命名空间---org.w3c.dom.Node中已经提供了方法供 我们直接调用
根据命名空间找到对应自定义元素的 NameSpaceHandler,所有 NameSpaceHandler 都存储在一个全局的 Mapping中 ,如果首次获取 NameSpaceHandler 的时候这个Map为空,则到指定路径下的配置文件中加载所有Handler,默认配置在如下路径的配置文件中,配置文件中配置的 key 是 nameSpcaceUri,value是 Handler 的全路径 类名称,首次初始化完成后Mapping中存储的 key 就是配置的 nameSpaceUri,value就是累的全路径;在根据 nameSpaceUri 获取对应Handler的时候 如果发现 value类型不是 NamespaceHandler 的 ,则使用类加载器加载对应的类并进行实例化后 ,并调用 对应的 init方法初始化该 Handler 注册的 Parser,再 put回map,
public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";
自定义的NameSpaceHandler中实现继承类 NamespaceHandlerSupport 中的 init 方法,在这个方法中会将该 NameSpace 空间下的所有自定义标签的解析器 注册 完成,注册是调用父类 NamespaceHandlerSupport 的 方法 registerBeanDefinitionParser 完成的,这个方法 会将解析器 put 进维护所有解析器的 map中。
private final Map<String, BeanDefinitionParser> parsers = new HashMap();
然后调用 NameSpaceHandler 的parse方法,该方法是 这些 Handler都必须继承的父类 NamespaceHandlerSupport 中的方法,这个方法会 根据元素和 NameSpace找到注册的 解析器,调用用户自己实现的解析器的 parse方法解析元素(如果自定义元素解析器是直接实现接口 BeanDefinitionParser 的话 )但是我们一般 都是继承 AbstractSingleBeanDefinitionParser 或 AbstractBeanDefinitionParser (如果是继承前者的话我们需要重写 doParse 方法 如果继承后者我们需要重写 parseInternal 方法)。AbstractSingleBeanDefinitionParser 继承自 AbstractBeanDefinitionParser ,AbstractBeanDefinitionParser 的 parse 方法使用了 模版方法 模式,预留了 parseInternal 抽象方法 给 子类自己实现;AbstractSingleBeanDefinitionParser 就是继承了 AbstractBeanDefinitionParser 重写了 预留的 parseInternal 方法,AbstractSingleBeanDefinitionParser 重写的 parseInternal 方法又实用了模版方法模式,预留了 doParse 抽象方法给 自类实现,所有我们 如果是单例的 Bean 的解析,直接继承 AbstractSingleBeanDefinitionParser 重写 doParse 方法;如果不是的话可以继承 AbstractBeanDefinitionParser 实现 parseInternal 方法,而最复杂的是直接实现接口 BeanDefinitionParser,如Dubbo就是直接实现了该接口,没有继承任何抽象类。
分析了源码后我们再来看 调用链路,如果 我们直接实现接口的话,parse方法就直接调用我们自己 的方法,如果是继承的是 AbstractSingleBeanDefinitionParser 或者 AbstractBeanDefinitionParser 的话,则调用的是 AbstractBeanDefinitionParser 的模版方法 parse 。
一个 NameSpace下面可以有多个 自定义元素解析器,都在 Handler 的 init 方法中进行注册
配置文件中如下的配置
<dubbo:application name="spring-dubbo-nacos-provider" />
dubbo 就是 NameSpace application 就是对应的自定义元素。还有 dubbo:service,dubbo:registry 等。