前言
在 Dubbo源码分析 - API和属性配置 中介绍了Dubbo的配置承载对象,分析了核心的配置类及方法。了解了API配置后XML配置就容易多了,XML配置相比较API配置的区别在配置对象创建及其属性的设置是由Spring管理的,Dubbo和Spring XML融合是关键。
Dubbo和Spring融合
Dubbo框架直接集成了Spring的能力,利用Spring配置文件扩展出自定义的解析方式,即使用Spring的自定标签。关于Spring自定标签的示例,在Spring自定义标签 中有详细介绍,Dubbo基于schema的设计也是如此,下面我们就来分析下Dubbo是怎么和Spring融合的。
Dubbo的配置对象模型
Dubbo的配置对象模型已经在 [Dubbo源码分析 - API和属性配置] 中详细介绍过了,在Dubbo的命名空间处理器中也可以具体看到哪些配置类和Spring进行交互,这里就不再介绍。
Dubbo的xsd文件
dubbo.xsd文件是用来约束使用XML配置时的标签和对应的属性,如Dubbo中的<dubbo:service>标签等。由于当前分析的dubbo版本是2.6.5,Dubbo已经捐给了Apache组织,为了遵循Apache标准和兼容Dubbo原来的版本,会出现两个xsd文件,这篇文章还是按照Dubbo原来的版本进行相关描述。
- dubbo.xsd总览
Dubbo设计的粒度很多都是针对方法级别的,如方法级别的timeout、retries等特性。具体的每个复杂类型的详细使用可以参考:官方文档
- dubbo.xsd中的类型关系
上图的类型继承关系和Dubbo的配置类之间的关系几乎保持一致,因为这里定义的复杂类型就是要映射到配置类的属性上,即schema中的字段对应Config类中的属性和get/set方法。
Dubbo的spring.schemas文件
http\://dubbo.apache.org/schema/dubbo/dubbo.xsd=META-INF/dubbo.xsd
http\://code.alibabatech.com/schema/dubbo/dubbo.xsd=META-INF/compat/dubbo.xsd
spring.schemas文件用来指明约束文件的具体路径。
Dubbo的spring.handlers
http\://dubbo.apache.org/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler
http\://code.alibabatech.com/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler
spring.handlers文件用来指明Dubbo的XML命名空间处理器,即使用DubboNamespaceHandler来解析Dubbo自定义的标签。
Dubbo的DubboNamespaceHandler
public class DubboNamespaceHandler extends NamespaceHandlerSupport {
static {
Version.checkDuplicate(DubboNamespaceHandler.class);
}
/**
* 方法中定义了每个<xsd:element/>对应的BeanDefinitionParser 【Dubbo Bean定义解析器】
*/
@Override
public void init() {
registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
// 注解已经重写,AnnotationBeanDefinitionParser 已经废弃
registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
}
}
Dubbo解析配置的入口是在 DubboNamespaceHandler类中完成的,该类主要把不同的标签关联到解析实现类中,registerBeanDefinitionParser方法约定在遇到Dubbo自定的标签如application、registry、protocol等都会委托给Dubbo的命名空间处理器DubboNamespaceHandler处理,该处理器又会把解析任务交给DubboBeanDefinitionParser来处理。
Dubbo的DubboBeanDefinitionParser
实现了Spring的BeanDefinitionParser接口,是真正用来解析自定的Dubbo标签,将标签解析成对应的Bean定义并注册到Spring上下文中。
使用Dubbo标签
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!-- provider's application name, used for tracing dependency relationship -->
<dubbo:application name="demo-provider" owner="gentryhuang"/>
<!-- use multicast registry center to export service -->
<!--<dubbo:registry address="multicast://224.5.6.7:1234" protocol="test"/> -->
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
<!-- use dubbo protocol to export service on port 20880 -->
<dubbo:protocol name="dubbo" port="20880"/>
<!-- service implementation, as same as regular local bean -->
<bean id="demoService" class="com.alibaba.dubbo.demo.provider.DemoServiceImpl"/>
<!-- declare the service interface to be exported -->
<dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService"/>
</beans>
小结
以上就是Dubbo和Spring的XML配置进行融合的过程,与 Spring自定义标签 文章中的流程是一样的。总的来说,Dubbo框架先以流的形式装载Spring的XML配置文件,在将流解析成DOM的过程中会加载spring.schemas
文件,然后读取该文件中指定的的xsd约束文件,接着使用xsd中的约束规则对每个标签及其属性进行校验,不合法则抛出异常,整个配置文件符合约束规则则生成DOM对象。和spring.handlers
文件。spring.schema
文件指定了配置约束文件的位置,加载spring.schemas
文件的目的就是用来校验Spring的XML配置文件内容是否合法。加载spring.handlers
文件的目的是,当解析Spring的XML配置文件中的标签时,会查找该文件中指定的DubboNamespaceHandler类来进行自定义标签的初始化和解析。
解析准备
加载 spring.schemas 文件
AbstractXmlApplicationContext#loadBeanDefinitions(org.springframework.beans.factory.support.DefaultListableBeanFactory){
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
// 设置 'META-INF/spring.schemas' 到 ResourceEntityResolver
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);
}
上面代码就设置 spring.schemas 文件路径,为接下来加载 spring.schemas 文件做准备。
XmlBeanDefinitionReader#doLoadBeanDefinitions(InputSource inputSource, Resource resource){
try {
// 加载 META-INF/spring.schemas 中xsd文件,在构建Dom时进行校验XML配置内容是否正确
Document doc = doLoadDocument(inputSource, resource);
// 加载 META-INF/spring.handlers 中的命名空间处理器,初始化并放入缓存
return registerBeanDefinitions(doc, resource);
}
// 省略无关代码
}
上面的代码是注册XML中的Bean的大流程入口,分别是加载 META-INF/spring.schemas 中xsd文件,用于构建DOM时校验XML配置内容是否正确,加载 META-INF/spring.handlers 中的命名空间处理器,用于处理标签和BeanDefinitionParser的映射关系以及解析标签。下面我们来看Spring是如何加载spring.schemas文件内容的。
PluggableSchemaResolver#getSchemaMappings(){
if (this.schemaMappings == null) {
synchronized (this) {
if (this.schemaMappings == null) {
if (logger.isDebugEnabled()) {
logger.debug("Loading schema mappings from [" + this.schemaMappingsLocation + "]");
}
try {
// 从 META-INF/spring.schemas 中读取xsd文件路径
Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.schemaMappingsLocation, this.classLoader);
if (logger.isDebugEnabled()) {
logger.debug("Loaded schema mappings: " + mappings);
}
Map<String, String> schemaMappings = new ConcurrentHashMap<String, String>(mappings.size());
// 放入缓存中
CollectionUtils.mergePropertiesIntoMap(mappings, schemaMappings);
this.schemaMappings = schemaMappings;
}catch (IOException ex) {
throw new IllegalStateException("Unable to load schema mappings from location [" + this.schemaMappingsLocation + "]", ex);
}
}
}
}
return this.schemaMappings;
}
/**
* @param publicId
* @param systemId
*/
PluggableSchemaResolver#resolveEntity(String publicId, String systemId){
if (systemId != null) {
// 根据 spring.schemas中配置的xxx.xsd找到对应的xsd文件
String resourceLocation = getSchemaMappings().get(systemId);
if (resourceLocation != null) {
// 加载xsd文件
Resource resource = new ClassPathResource(resourceLocation, this.classLoader);
try {
InputSource source = new InputSource(resource.getInputStream());
source.setPublicId(publicId);
source.setSystemId(systemId);
if (logger.isDebugEnabled()) {
logger.debug("Found XML schema [" + systemId + "] in classpath: " + resourceLocation);
}
return source;
}catch (FileNotFoundException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Couldn't find XML schema [" + systemId + "]: " + resource, ex);
}
}
}
}
return null;
}
以上就是Spring在启动时加载spring.schemas中配置的xsd文件的几个代码片段,将XML配置文件解析成DOM的过程中,对每个标签及其属性进行校验,依据就是xsd中的约束条件。由于是Spring的源码部分,这里不进行深入分析,感兴趣的胖友可以自行调试。
加载 spring.handlers 文件
/**
* @param doc 配置文件对应的DOM对象
* @param resource 配置文件资源对象
*/
XmlBeanDefinitionReader#registerBeanDefinitions(Document doc, Resource resource){
// 创建Bean定义的DOMReader,用来读取、解析DOM,接着创建对应的Bean
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
// 读取、解析DOM、创建对应的Bean
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
registerBeanDefinitions方法是将Spring的XML配置文件中定义的有关标签进行创建并注册到Spring的注册表中。注意,这里所说的能够创建Bean的有关标签必须有对应的BeanDefinitionParser,否则不会对该标签进行处理。
/**
* @param resource 配置文件资源对象
*/
XmlBeanDefinitionReader#createReaderContext(Resource resource){
return new XmlReaderContext(
resource,
this.problemReporter,
this.eventListener,
this.sourceExtractor,
this,
// 读取 META-INF/spring.handlers 文件
getNamespaceHandlerResolver()
);
}
createReaderContext 方法用来创建 XmlReaderContext,该对象中包含的核心属性如下:
由XmlReaderContext对象中的属性可知,在创建该对象的过程中对 META-INF/spring.handlers 文件进行了读取。现在有了配置文件的DOM对象、Bean定义工厂以及spring.handlers文件中各种NamespaceHandler,接下来就可以解析DOM树,创建并注册相应的Bean。
DefaultBeanDefinitionDocumentReader#registerBeanDefinitions(Document doc, XmlReaderContext readerContext){
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
// 获取DOM的根元素,一般是 beans
Element root = doc.getDocumentElement();
// 解析入口
doRegisterBeanDefinitions(root);
}
上面的代码主要是获取DOM对象的根元素,然后以这个根元素作为起点进行解析,下面我们接着解析代码。
/**
* Parse the elements at the root level in the document:
* "import", "alias", "bean".
* @param root DOM的根元素
* @param delegate Bean定义解析器代理
*/
DefaultBeanDefinitionDocumentReader#parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
// 判读根元素是不是默认的命名空间 'http://www.springframework.org/schema/beans'
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;
// 当前元素的命名空间如果是默认的命名空间即Spring自身的命名空间,则通过Spring自身逻辑进行解析
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
// 当前元素的命名空间非默认的命名空间即自定义的标签,则通过自定义逻辑进行解析
else {
delegate.parseCustomElement(ele);
}
}
}
}else {
delegate.parseCustomElement(root);
}
}
上面代码的主要逻辑是判断要解析的DOM元素即标签,是否是Spring内置的,如果是Spring内置则整个解析逻辑使用Spring自身的那一套,如果是自定义的,则解析逻辑交给开发者。Spring自身的解析逻辑忽略,下面我们来分析下自定义的标签的处理流程。
BeanDefinitionParserDelegate#parseCustomElement(org.w3c.dom.Element ele){
return parseCustomElement(ele, null);
}
/**
* @param ele DOM的根元素
* @param containingBd
*/
BeanDefinitionParserDelegate#parseCustomElement(org.w3c.dom.Element ele, org.springframework.beans.factory.config.BeanDefinition containingBd){
// 获取元素即标签的命名空间
String namespaceUri = getNamespaceURI(ele);
// 使用 XmlReaderContext中的 DefaultNamespaceHandlerResolver获取命名空间对应的 NamespaceHandler对象
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
// 使用 NamespaceHandler 对象解析标签
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
上面代码先是获取当前元素的命名空间,然后通过该命名空间获取对应 NamespaceHandler对象,最后通过该对象解析当前元素。下面我们依次分析这两个步骤的代码。
DefaultNamespaceHandlerResolver#resolve(String namespaceUri){
// 获取 DefaultNamespaceHandlerResolver#handlerMappings属性,即命名空间到NamespaceHandler的映射,注意这里的NamespaceHandler可能是还没有进行实例化的字符串
Map<String, Object> handlerMappings = getHandlerMappings();
// 从缓存中获取 NamespaceHandler
Object handlerOrClassName = handlerMappings.get(namespaceUri);
if (handlerOrClassName == null) {
return null;
}
// 如果当前命名空间对应的 NamespaceHandler 就是 NamespaceHandler对象,则需要进行实例化,直接返回即可
else if (handlerOrClassName instanceof NamespaceHandler) {
return (NamespaceHandler) handlerOrClassName;
}
// 当前命名空间对应的 NamespaceHandler 还是字符串,需要反射创建对象
else {
String className = (String) handlerOrClassName;
try {
// 获取当前 当前命名空间对应的 NamespaceHandler 串 的 Class
Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
"] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
}
// 创建对象
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
// 执行 init 方法,进行标签和BeanDefinitionParser 的关联
namespaceHandler.init();
// 加入缓存
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
}catch (ClassNotFoundException ex) {
throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" +
namespaceUri + "] not found", ex);
}catch (LinkageError err) {
throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" +
namespaceUri + "]: problem with handler class file or dependent class", err);
}
}
}
上面代码核心是获取当前命名空间对应的 NamespaceHandler ,如果 NamespaceHandler 还是个字符串,那么就通过反射创建对象,接着调用该对象的 init()
,进行标签和 BeanDefinitionParser 的关联 ,方法如果已经创建过了对象则直接返回该 NamespaceHandler 对象。由于Dubbo自定义标签的命名空间对应的NamespaceHandler是 DubboNamespaceHandler,我们在前面已经分析过了它的源码,这里再详细说明下。
public class DubboNamespaceHandler extends NamespaceHandlerSupport {
static {
Version.checkDuplicate(DubboNamespaceHandler.class);
}
/**
* 方法中定义了每个<xsd:element/>对应的BeanDefinitionParser 【Dubbo Bean定义解析器】
*/
@Override
public void init() {
registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
}
}
上面的代码比较直观,一个标签对应一个 DubboBeanDefinitionParser 对象,同时也对应这一个Dubbo的配置承载类。我们接下主要看registerBeanDefinitionParser方法是怎么把标签和DubboBeanDefinitionParser关联到一起的。
public abstract class NamespaceHandlerSupport implements NamespaceHandler {
/**
* 标签名 到 BeanDefinitionParser 映射集合
*/
private final Map<String, BeanDefinitionParser> parsers = new HashMap<String, BeanDefinitionParser>();
/**
* 关联 标签名 到 BeanDefinitionParser
*/
protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
this.parsers.put(elementName, parser);
}
}
原来如此简单,就是调用父类 NamespaceHandlerSupport 的registerBeanDefinitionParser方法,将标签名到BeanDefinitionParser的映射保存到缓存中。到了这里所有解析前的工作已经准备就绪,终于可以进入到这篇文章的核心部分了。之所以用了那么多的铺垫,就是想把整个过程串起来,如果一下子进入到Dubbo自定义标签的解析感觉还是挺奇怪的,毕竟笔者对Spring的源码也不熟悉,就按部就班吧。
解析标签
解析准备是特意为解析标签做的铺垫,有了这个铺垫下面的解析逻辑就容易很多了。我们接着解析准备中的 parseCustomElement
方法继续分析。
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
// 1. 获取DOM元素即标签对应的命名空间
String namespaceUri = getNamespaceURI(ele);
// 2. 获取命名空间映射的 NamespaceHandler对象
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
// 3. 调用 NamespaceHandler对象 的parse方法进行解析
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
上面代码中的第3步才正式进入到标签的解析,这里的 NamespaceHandler 就 DubboNamespaceHandler对象,parse
方法是其父类 NamespaceHandlerSupport 中的方法,我们来看看逻辑。
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
/*
* 1. 获取标签的名称关联的 BeanDefinitionParser
* 2. 使用 BeanDefinitionParser解析标签
*/
return findParserForElement(element, parserContext).parse(element, parserContext);
}
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
// 获取标签名
String localName = parserContext.getDelegate().getLocalName(element);
// 从缓存中获取标签名对应的 BeanDefinitionParser对象,即 DubboBeanDefinitionParser对象
BeanDefinitionParser parser = this.parsers.get(localName);
if (parser == null) {
parserContext.getReaderContext().fatal(
"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
}
return parser;
}
上面代码就是从 标签名 到 BeanDefinitionParser 映射集合parsers中获取标签名对应的BeanDefinitionParser对象,该映射集合是在 DubboNamespaceHandler#init 方法执行时维护的。下面我们接着分析DubboBeanDefinitionParser类。
Dubbo Bean定义解析器
public class DubboBeanDefinitionParser implements BeanDefinitionParser {
/**
* 标签元素对应的对象类
*/
private final Class<?> beanClass;
/**
* 是否需要Bean的 id 属性
*/
private final boolean required;
/**
* @param beanClass Bean 对象的类
* @param required 是否需要在Bean对象的编号(id)不存在时自动生成编号。无需被其他应用引用的配置对象,无需自动生成编号。 eg:<dubbo:reference/>
*/
public DubboBeanDefinitionParser(Class<?> beanClass, boolean required) {
this.beanClass = beanClass;
this.required = required;
}
/**
* Spring解析标签的入口方法
*
* @param element 标签元素对象
* @param parserContext 解析上下文
* @return
*/
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
return parse(element, parserContext, beanClass, required);
}
// ${省略的代码}
}
DubboBeanDefinitionParser实现了Spring的BeanDefinitionParser接口,即Spring的Bean定义解析器。该类中有两个重要属性,beanClass
和 required
,这两个属性的值是在创建Dubbo的Bean定义解析器时通过构造方法传入的,分别是标签元素对应的配置类和在创建配置Bean的时候可能需要i的d属性。parse
方法是解析XML元素的主流程的入口,其中 parserContext 参数是XML解析的上下文,它包含了 XmlReaderContext 这个重要对象,而该对象中又包含了BeanFactory等信息,具体如下图:
有了BeanFactory就可以实现Bean的定义了,接下来我们继续分析Dubbo是如何处理自定义标签与对应的配置类之间的关系,以及怎样创建标签对应的Bean定义的。
创建Bean定义并注册到Spring上下文
/**
* @param element 标签对应的DOM
* @param parserContext spring 解析上下文
* @param beanClass 标签对应的配置类
* @param required 在创建Bean定义的时候是否需要id
* @return 标签对应的配置类的Bean定义
*/
@SuppressWarnings("unchecked")
private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {
// 生成Spring的Bean定义,指定beanClass交给Spring反射创建实例
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClass(beanClass);
/**
* 设置Bean初始化方式,默认设置为延迟加载。
* 需要说明的是,引用缺省是延迟初始化的,只有引用被注入到其它Bean或者getBean() 获取才会初始化。如果需要立即初始化可以配置: <dubbo:reference init="true"/>
*/
beanDefinition.setLazyInit(false);
//--------------------------- 确保Spring 容器中没有重复的Bean定义 开始 ------------------------/
// 解析标签对象的id属性
String id = element.getAttribute("id");
// 标签没有设置id属性,并且创建的Bean定义需要id时,就执行生成id的逻辑。需要注意的是,Dubbo的reference标签对应Bean定义不需要id
if ((id == null || id.length() == 0) && required) {
// 1. 取name属性值
String generatedBeanName = element.getAttribute("name");
if (generatedBeanName == null || generatedBeanName.length() == 0) {
// 2. 也没有设置name属性,此时如果当前标签是Protocol,那么id的值就直接设置为 'dubbo',非Protocol协议则尝试取标签的interface属性值
if (ProtocolConfig.class.equals(beanClass)) {
generatedBeanName = "dubbo";
} else {
generatedBeanName = element.getAttribute("interface");
}
}
// 3. 以上过程都没有生成id,则最后使用标签对应的配置类的类名
if (generatedBeanName == null || generatedBeanName.length() == 0) {
generatedBeanName = beanClass.getName();
}
id = generatedBeanName;
int counter = 2;
// 检查Spring注册表中是否存在标识id,存在就通过自增序列继续处理id,使其唯一
while (parserContext.getRegistry().containsBeanDefinition(id)) {
id = generatedBeanName + (counter++);
}
}
if (id != null && id.length() > 0) {
if (parserContext.getRegistry().containsBeanDefinition(id)) {
throw new IllegalStateException("Duplicate spring bean id " + id);
}
// 把标签对应的配置类的Bean定义注册到Spring,Bean 名称为id
parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);
// 为Bean追加id属性
beanDefinition.getPropertyValues().addPropertyValue("id", id);
}
// ${省略的代码}
}
特殊处理protocol标签
/**
* @param element 标签对应的DOM
* @param parserContext spring 解析上下文
* @param beanClass 标签对应的配置类
* @param required 在创建Bean定义的时候是否需要id
* @return 标签对应的配置类的Bean定义
*/
@SuppressWarnings("unchecked")
private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {
// ${省略的代码}
if (ProtocolConfig.class.equals(beanClass)) {
/**
* 以下代码逻辑需要满足:
* 顺序需要这样:
* 1 <dubbo:service interface="com.xxx.xxxService protocol="dubbo" ref="xxxServiceImpl"/>
* 2 <dubbo:protocol id ="dubbo" name="dubbo" port="20880"/>
*/
// 获取Bean注册表中所有的Bean id
for (String name : parserContext.getRegistry().getBeanDefinitionNames()) {
// 根据id获取Bean定义
BeanDefinition definition = parserContext.getRegistry().getBeanDefinition(name);
// 获取当前Bean定义的属性对象集合,并尝试获取属性名为 'protocol' 的属性对象
PropertyValue property = definition.getPropertyValues().getPropertyValue("protocol");
if (property != null) {
// 获取属性值
Object value = property.getValue();
// 如果当前遍历的Bean定义中的属性满足条件,就更新该Bean的 protocol 属性值,即名称为id的RuntimeBeanReference对象
if (value instanceof ProtocolConfig && id.equals(((ProtocolConfig) value).getName())) {
definition.getPropertyValues().addPropertyValue("protocol", new RuntimeBeanReference(id));
}
}
}
}
// ${省略的代码}
}
上面的代码用来处理框架中那些属性名为’protocol’且属性类型为为ProtocolConfig的Bean,如果该Bean符合条件就更新该Bean的protocol属性值。
特殊处理service标签
/**
* @param element 标签对应的DOM
* @param parserContext spring 解析上下文
* @param beanClass 标签对应的配置类
* @param required 在创建Bean定义的时候是否需要id
* @return 标签对应的配置类的Bean定义
*/
@SuppressWarnings("unchecked")
private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {
// ${省略的代码}
else if (ServiceBean.class.equals(beanClass)) {
// 如果<dubbo:service>配置了class属性,那么为具体class配置的类创建Bean定义,并且把该定义注入到Service的 ref属性。一般不这么使用。
// eg: <dubbo:service interface="com.alibaba.dubbo.demo.DemoService class="com.alibaba.dubbo.demo.provider.DemoServiceImpl"/>
String className = element.getAttribute("class");
if (className != null && className.length() > 0) {
RootBeanDefinition classDefinition = new RootBeanDefinition();
classDefinition.setBeanClass(ReflectUtils.forName(className));
classDefinition.setLazyInit(false);
// 解析 <dubbo:service class="xxx"/> 情况下内嵌的<property/>标签,然后设置到classDefinition的属性中
parseProperties(element.getChildNodes(), classDefinition);
// 设置ref属性,相当于设置 <dubbo:service ref=""/>属性
beanDefinition.getPropertyValues().addPropertyValue("ref", new BeanDefinitionHolder(classDefinition, id + "Impl"));
}
}
// ${省略的代码}
}
上面的代码用来处理 service标签
中有 class 属性的情况,处理逻辑就是创建class对应的Bean定义,然后设置到 service标签
对应的Bean的ref属性中。我们再来看看对service的子标签 property
的解析。
/**
* 解析 <dubbo:service class="xxx"/> 情况下内嵌的<property/>
*
* @param nodeList 子元素数组
* @param beanDefinition Bean定义对象
*/
private static void parseProperties(NodeList nodeList, RootBeanDefinition beanDefinition) {
if (nodeList != null && nodeList.getLength() > 0) {
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
// 只解析<property/>标签
if (node instanceof Element) {
if ("property".equals(node.getNodeName())
|| "property".equals(node.getLocalName())) {
String name = ((Element) node).getAttribute("name");
// 优先使用value属性,其次使用ref属性
if (name != null && name.length() > 0) {
String value = ((Element) node).getAttribute("value");
String ref = ((Element) node).getAttribute("ref");
if (value != null && value.length() > 0) {
beanDefinition.getPropertyValues().addPropertyValue(name, value);
} else if (ref != null && ref.length() > 0) {
beanDefinition.getPropertyValues().addPropertyValue(name, new RuntimeBeanReference(ref));
} else {
// 属性不全,抛出异常
throw new UnsupportedOperationException("Unsupported <property name=\"" + name + "\"> sub tag, Only supported <property name=\"" + name + "\" ref=\"...\" /> or <property name=\"" + name + "\" value=\"...\" />");
}
}
}
}
}
}
}
上面的代码用来解析service的property标签,目的是为service标签的class属性对应的Bean定义设置属性,比较简单。
特殊处理provider/consumer标签
/**
* @param element 标签对应的DOM
* @param parserContext spring 解析上下文
* @param beanClass 标签对应的配置类
* @param required 在创建Bean定义的时候是否需要id
* @return 标签对应的配置类的Bean定义
*/
@SuppressWarnings("unchecked")
private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {
// ${省略的代码}
else if (ProviderConfig.class.equals(beanClass)) {
// 解析 <dubbo:provider/> 的内嵌子元素<dubbo:service/>
parseNested(element, parserContext, ServiceBean.class, true, "service", "provider", id, beanDefinition);
}else if (ConsumerConfig.class.equals(beanClass)) {
// 解析 <dubbo:consumer/> 的内嵌子元素<dubbo:reference/>
parseNested(element, parserContext, ReferenceBean.class, false, "reference", "consumer", id, beanDefinition);
}
// ${省略的代码}
}
从上面的代码可以看出,特殊处理provider/consumer标签就是处理它有service/reference子标签的情况,代码过程如下:
/**
* 解析内嵌的标签
*
* @param element 父标签对象 - provider/consumer标签对象
* @param parserContext Spring解析上下文
* @param beanClass 内嵌子元素的Bean类 - ServiceBean/ReferenceBean
* @param required 是否需要Bean的id属性
* @param tag 子元素标签名 service/reference
* @param property 父Bean对象在子元素中的属性名 provider/consumer
* @param ref 父Bean的id
* @param beanDefinition 父Bean定义对象
*/
private static void parseNested(Element element, ParserContext parserContext, Class<?> beanClass, boolean required, String tag, String property, String ref, BeanDefinition beanDefinition) {
// 获取子节点列表
NodeList nodeList = element.getChildNodes();
if (nodeList != null && nodeList.getLength() > 0) {
boolean first = true;
for (int i = 0; i < nodeList.getLength(); i++) {
// 获取子节点
Node node = nodeList.item(i);
if (node instanceof Element) {
// 当前节点是否是指定的子节点,这里可能是service/reference节点
if (tag.equals(node.getNodeName()) || tag.equals(node.getLocalName())) {
if (first) {
first = false;
// 获取父节点的default的属性值 [暂时不知道有什么用]
String isDefault = element.getAttribute("default");
if (isDefault == null || isDefault.length() == 0) {
beanDefinition.getPropertyValues().addPropertyValue("default", "false");
}
}
// 解析子元素,创建BeanDefinition 对象 (递归)
BeanDefinition subDefinition = parse((Element) node, parserContext, beanClass, required);
// 设置子BeanDefinition的指向,指向父BeanDefinition
if (subDefinition != null && ref != null && ref.length() > 0) {
subDefinition.getPropertyValues().addPropertyValue(property, new RuntimeBeanReference(ref));
}
}
}
}
}
}
上面的代码主要处理provider/consumer标签内部嵌套的标签,内部嵌套的标签对象会自动持有外层标签的对象。
设置标签的属性到 BeanDefinition
前面处理的逻辑属于特殊的情况,接下来我们分析标签的属性是如何设置到配置对象中的。本质上是通过遍历配置对象的get、set和is前缀方法,通过反射将标签属性设置到配置对象中。总体上分为两种情况:
- 如果标签属性和方法名相同,则通过反射调用设置标签的值到配置对象中。
- 如果标签属性不能匹配到配置对象中的方法名称,则将标签属性当作parameter参数设置到配置对象中。
/**
* @param element 标签对应的DOM
* @param parserContext spring 解析上下文
* @param beanClass 标签对应的配置类
* @param required 在创建Bean定义的时候是否需要id
* @return 标签对应的配置类的Bean定义
*/
@SuppressWarnings("unchecked")
private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {
// ${省略的代码}
// 用来保存已遍历的配置对象的属性集合,用来判断标签中哪些属性没有匹配上
Set<String> props = new HashSet<String>();
// 专门存放<dubbo:parameters/> 标签下子标签属性信息。最后都设置到Bean定义中
ManagedMap parameters = null;
// 1. 获取配置对象所有方法
for (Method setter : beanClass.getMethods()) {
String name = setter.getName();
// 2. 选择所有set前缀方法,并且只有一个参数的 public 方法
if (name.length() > 3 && name.startsWith("set") && Modifier.isPublic(setter.getModifiers()) && setter.getParameterTypes().length == 1) {
// 获取方法的参数类型
Class<?> type = setter.getParameterTypes()[0];
// 3. 提取set对应的属性名字,eg: setTimeout->timeout,setBeanName->bean-name
String property = StringUtils.camelToSplitName(name.substring(3, 4).toLowerCase() + name.substring(4), "-");
// 保存到属性 props 集合中
props.add(property);
// 4. 尝试获取属性对应的getter方法
Method getter = null;
try {
getter = beanClass.getMethod("get" + name.substring(3), new Class<?>[0]);
} catch (NoSuchMethodException e) {
try { // 没有setter对应的getter方法,尝试获取is方法,is方法在功能上是同getter
getter = beanClass.getMethod("is" + name.substring(3), new Class<?>[0]);
} catch (NoSuchMethodException e2) {
}
}
// 5. 校验属性是否有对应的getter/is前缀方法,没有就跳过
if (getter == null
|| !Modifier.isPublic(getter.getModifiers())
|| !type.equals(getter.getReturnType())) {
continue;
}
// 6. 解析 <dubbo:parameter/> 标签,将当前标签element的子标签 <dubbo:parameter/> 的属性键值对保存到parameters中
if ("parameters".equals(property)) {
parameters = parseParameters(element.getChildNodes(), beanDefinition);
// 7. 解析 <dubbo:method/> 标签,将当前标签element的子标签 <dubbo:method/> 进行解析,将解析得到的对应BeanDefiniton放入到ManagedList集合中,最后作为 beanDefiniton的methods属性值。
} else if ("methods".equals(property)) {
parseMethods(id, element.getChildNodes(), beanDefinition, parserContext);
// 8. 解析 <dubbo:argument/>标签,将当前标签element的子标签 <dubbo:argument/> 进行解析,将解析得到的对应的BeanDefinition放入到ManagedList集合中,最后作为 beanDefinition的arguments属性值。
} else if ("arguments".equals(property)) {
parseArguments(id, element.getChildNodes(), beanDefinition, parserContext);
} else {
// 9. 获取标签属性的值 【前面的步骤之所以单独处理,是因为当前配置配置对象对应的属性不是一个标签属性,而是一个子标签】
String value = element.getAttribute(property);
if (value != null) {
value = value.trim();
if (value.length() > 0) {
// 9.1 标签中配置了 registry=N/A, 不想注册到的情况
if ("registry".equals(property) && RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(value)) {
RegistryConfig registryConfig = new RegistryConfig();
// RegistryConfig的地址设置 N/A
registryConfig.setAddress(RegistryConfig.NO_AVAILABLE);
beanDefinition.getPropertyValues().addPropertyValue(property, registryConfig);
// 9.2 多注册中心情况,将多个注册中心处理成一个集合,然后设置到 beanDefiniton 中,属性名为 'registries'
} else if ("registry".equals(property) && value.indexOf(',') != -1) {
parseMultiRef("registries", value, beanDefinition, parserContext);
// 9.3 多服务提供者情况,将多个服务提供者处理成一个集合,然后设置到 beanDefinition 中,属性为 'providers'
} else if ("provider".equals(property) && value.indexOf(',') != -1) {
parseMultiRef("providers", value, beanDefinition, parserContext);
// 9.4 多协议情况,将多个协议处理成一个集合,然后设置到 beanDefinition 中,属性为 'protocols'
} else if ("protocol".equals(property) && value.indexOf(',') != -1) {
parseMultiRef("protocols", value, beanDefinition, parserContext);
} else {
Object reference;
// 10. 属性类型为基本类型的情况
if (isPrimitive(type)) {
// 兼容性处理【一些设置了但是意义不大的属性就把值设置为null】
if ("async".equals(property) && "false".equals(value)
|| "timeout".equals(property) && "0".equals(value)
|| "delay".equals(property) && "0".equals(value)
|| "version".equals(property) && "0.0.0".equals(value)
|| "stat".equals(property) && "-1".equals(value)
|| "reliable".equals(property) && "false".equals(value)) {
// backward compatibility for the default value in old version's xsd
value = null;
}
reference = value;
//11. 处理在<dubbo:provider/> 或者 <dubbo:service/> 上定义了 protocol 属性的兼容性,目前已经不推荐这样使用了,应该单独配置 <dubbo:protocol/>
} else if ("protocol".equals(property)
&& ExtensionLoader.getExtensionLoader(Protocol.class).hasExtension(value)
&& (!parserContext.getRegistry().containsBeanDefinition(value)
|| !ProtocolConfig.class.getName().equals(parserContext.getRegistry().getBeanDefinition(value).getBeanClassName()))) {
if ("dubbo:provider".equals(element.getTagName())) {
logger.warn("Recommended replace <dubbo:provider protocol=\"" + value + "\" ... /> to <dubbo:protocol name=\"" + value + "\" ... />");
}
// backward compatibility
ProtocolConfig protocol = new ProtocolConfig();
protocol.setName(value);
reference = protocol;
//------- 12. 事件通知: 在调用前,调用后,出现异常,会触发oninvoke,onreturn,onthrow三个事件,可以配置当事件发生时,通知哪个类的哪个方法 ------//
/*
// 格式:实现Bean.方法
<bean id="demoCallBack" class = "com.alibaba.dubbo.callback.implicit.NofifyImpl"/>
<dubbo:reference id = "demoService" interface="com.alibaba.dubbo.IDemoService">
<dubbo:method name="get" onreturn="demoCallBack.xxxMethod" onthrow="demoCallBack.xMethod"/>
</dubbo:reference>
*/
// 12.1 处理 onreturn 属性
} else if ("onreturn".equals(property)) {
// 按照 . 拆分
int index = value.lastIndexOf(".");
// 获取实例名
String returnRef = value.substring(0, index);
// 获取实例的方法
String returnMethod = value.substring(index + 1);
// 创建 RuntimeBeanReference,指向回调的对象
reference = new RuntimeBeanReference(returnRef);
// 设置 onreturnMethod 到 BeanDefinition 的属性值
beanDefinition.getPropertyValues().addPropertyValue("onreturnMethod", returnMethod);
// 12.2 处理 onthrow 属性
} else if ("onthrow".equals(property)) {
int index = value.lastIndexOf(".");
String throwRef = value.substring(0, index);
String throwMethod = value.substring(index + 1);
// 创建 RuntimeBeanReference,指向回调的对象
reference = new RuntimeBeanReference(throwRef);
// 设置 onthrowMethod 到 BeanDefinition 的属性值
beanDefinition.getPropertyValues().addPropertyValue("onthrowMethod", throwMethod);
//12.3 处理oninvoke 属性
} else if ("oninvoke".equals(property)) {
int index = value.lastIndexOf(".");
String invokeRef = value.substring(0, index);
String invokeRefMethod = value.substring(index + 1);
reference = new RuntimeBeanReference(invokeRef);
beanDefinition.getPropertyValues().addPropertyValue("oninvokeMethod", invokeRefMethod);
//----------------------------- 事件通知结束 ------------------------------//
} else {
// 13. 属性名没有匹配到对应的标签名,都会到这里
//13.1 如果属性名是ref, ref 对应的Bean 必须是单例的
if ("ref".equals(property) && parserContext.getRegistry().containsBeanDefinition(value)) {
BeanDefinition refBean = parserContext.getRegistry().getBeanDefinition(value);
if (!refBean.isSingleton()) {
throw new IllegalStateException("The exported service ref " + value + " must be singleton! Please set the " + value + " bean scope to singleton, eg: <bean id=\"" + value + "\" scope=\"singleton\" ...>");
}
}
// 创建RuntimeBeanReference
reference = new RuntimeBeanReference(value);
}
// 设置Bean定义的属性
beanDefinition.getPropertyValues().addPropertyValue(property, reference);
}
}
}
}
}
}
// 将标签中自定义的属性(不是Dubbo Schema 约定好的)也加入到 parameters 集合中
NamedNodeMap attributes = element.getAttributes();
int len = attributes.getLength();
for (int i = 0; i < len; i++) {
Node node = attributes.item(i);
String name = node.getLocalName();
if (!props.contains(name)) {
if (parameters == null) {
parameters = new ManagedMap();
}
String value = node.getNodeValue();
parameters.put(name, new TypedStringValue(value, String.class));
}
}
if (parameters != null) {
beanDefinition.getPropertyValues().addPropertyValue("parameters", parameters);
}
return beanDefinition;
}
上面的代码是把属性注入到标签对应的BeanDefinition,如果属性是引用对象,Dubbo默认会创建 RuntimeBeanReference
类型注入,运行时由Spring注入引用对象。
总结
Dubbo框架解析配置文件生成BeanDefinition其实是生成标签对应的配置类的Bean定义,Bean定义中的属性值主要来源于标签的属性值,Dubbo对标签属性只是进行了提取,标签的内嵌标签处理也是如此,运行时属性注入和转换都还是Spring来完成的,Dubbo框架生成的BeanDefinition最终会委托Spring创建对应的对象,这个属于Spring的流程就不多说了。dubbo.xsd文件中定义的类型都会有与之对应的配置承载类中的属性,我们已经在API配置中介绍过了。XML配置解析还是挺复杂的,分支流比较多,下一章要分析的注解配置稍微比这个复杂一些。随着后面深入的分析就会发现这些东西都是基础,结合Dubbo的整个过程就很容易理解了。