这篇文章是Dubbo源码分析的开端,与其说这篇文章是Dubbo源码分析,不如是spring源码分析,因为大部分都是在分析spring如何解析xml配置文件的,为了与后面的Dubbo源码分析保持一致,姑且这样命名了。

使用Dubbo框架开发分布式服务时,一般使用spring进行管理,在spring的配置文件中进行配置,例如服务提供者Provider端配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
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"> <!-- 提供方应用信息,用于计算依赖关系 -->
<dubbo:application name="hello-world-app" /> <!-- 使用multicast广播注册中心暴露服务地址 -->
<dubbo:registry address="multicast://224.5.6.7:1234" /> <!-- 用dubbo协议在20880端口暴露服务 -->
<dubbo:protocol name="dubbo" port="20880" /> <!-- 声明需要暴露的服务接口 -->
<dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService" /> <!-- 和本地bean一样实现服务 -->
<bean id="demoService" class="com.alibaba.dubbo.demo.provider.DemoServiceImpl" />
</beans>

对于标签我们都熟悉了,这是spring提供给使用者实例化PoJo类的。那么dubbo定义的标签spring是如何识别的呢?

其实spring-beans jar包中提供了一个口,那就是NamespaceHandler,它的定义如下:

(省略描述....)
public interface NamespaceHandler {
void init(); BeanDefinition parse(Element element, ParserContext parserContext); BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder definition, ParserContext parserContext); }

可以看出其中有一个parse方法,它的作用就是用于解析Xml文档中的节点。这个接口有一个抽象的实现类NamespaceHandlerSupport,具体的标签解析器都继承这个抽象类,我们来看下有哪些:

可以看见我们熟悉的Aop还有Dubbo NamespaceHandler.那Spring是如何知道要使用Dubbo定义的handler来解析自定义的标签呢?他们的结合点就在一个配置文件,spring留了一个配置文件,只要我们配置了它,spring就可以找到。这个配置文件名字叫做spring.handlers,spring在解析xml文件时,会去加载spring.handlers配置文件,然后寻找能够解析自定义标签的handler。

spring.handlers在Dubbo中是怎样的内容呢,我们一起来看下:

路径在:dubbo-config/dubbon-config-spring/META-INF/spring.handlers

内容如下:

http://dubbo.apache.org/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler

http://code.alibabatech.com/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler

我们来看一下spring是如何找到这个文件并生成对应标签namespaceHandler的:

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"application.xml"});

我们使用spring框架,启动的时候都会写这么一句话,加载xml配置文件.当执行到AbstractXmlApplicationContext类的loadBeanDefinitions方法时,会创建一个XmlBeanDefinitionReader对象,读取读取并解析xml.

	/**
* Loads the bean definitions via an XmlBeanDefinitionReader.
* @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
* @see #initBeanDefinitionReader
* @see #loadBeanDefinitions
*/
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// 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);
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);
}

最后一句话loadBeanDefinitions(beanDefinitionReader),是使用XmlBeanDefinitionReader将xml解析成BeanDefinition.

接下来调用XmlBeanDefinitionReader的registerBeanDefinitions方法创建BeanDefinitionDocumentReader对象,这个对象才是真正解析XML的对象。

	public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
//创建ReaderContext,并调用documentReader的registerBeanDefinitions方法
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}

大家再来看下createReaderContext方法

	/**
* Create the {@link XmlReaderContext} to pass over to the document reader.
*/
public XmlReaderContext createReaderContext(Resource resource) {
return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
this.sourceExtractor, this, getNamespaceHandlerResolver());
}

终于出现了NamespaceHandler,那就是getNamespaceHandlerResolver()方法,再来看下这个方法

	public NamespaceHandlerResolver getNamespaceHandlerResolver() {
if (this.namespaceHandlerResolver == null) {
this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
}
return this.namespaceHandlerResolver;
} protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
return new DefaultNamespaceHandlerResolver(getResourceLoader().getClassLoader());
} public DefaultNamespaceHandlerResolver(ClassLoader classLoader) {
this(classLoader, DEFAULT_HANDLER_MAPPINGS_LOCATION);
} //最终会调用这个构造器实例化DefaultNamespaceHandlerResolver类,此时handlerMappingsLocation成员变量的值为META-INF/spring.handlers了
public DefaultNamespaceHandlerResolver(ClassLoader classLoader, String handlerMappingsLocation) {
Assert.notNull(handlerMappingsLocation, "Handler mappings location must not be null");
this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
this.handlerMappingsLocation = handlerMappingsLocation;
} /**
* The location to look for the mapping files. Can be present in multiple JAR files.
*/
public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers"; //读取META-INF/spring.handlers配置文件的内容
private Map<String, Object> getHandlerMappings() {
if (this.handlerMappings == null) {
synchronized (this) {
if (this.handlerMappings == null) {
try {
Properties mappings =
PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
if (logger.isDebugEnabled()) {
logger.debug("Loaded NamespaceHandler mappings: " + mappings);
}
Map<String, Object> handlerMappings = new ConcurrentHashMap<String, Object>(mappings.size());
CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
this.handlerMappings = handlerMappings;
}
catch (IOException ex) {
throw new IllegalStateException(
"Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
}
}
}
}
return this.handlerMappings;
}

会创建一个默认的DefaultNamespaceHandlerResolver对象,其中有一个变量DEFAULT_HANDLER_MAPPINGS_LOCATION,其值是META-INF/spring.handlers,此时可以看到spring会去加载spring.hadlers中的内容。

那加载完后,会在什么地方使用呢?

我们来看一下DefaultBeanDefinitionDocumentReader类中的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;
//判断标签是标准的标准还是自定义的,判断的依据就是标准的namespace是否是http://www.springframework.org/schema/beans
//如果是,则是标准的标签,如果不是则不是标准的标签
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}

接下来看一下BeanDefinitionParserDelegate类的parseCustomElement方法,BeanDefinitionParserDelegate对象是在DefaultBeanDefinitionDocumentReader的doRegisterBeanDefinitions方法中生成的

,它的作用是用来解析标签的。

	public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
//根据标签得到标签的namespace
//dubbo自定义的标签得到的namespace是http://dubbo.apache.org/schema/dubbo
String namespaceUri = getNamespaceURI(ele);
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

接下来看下DefaultBeanDefinitionDocumentReader的resolve方法

	@Override
public NamespaceHandler resolve(String namespaceUri) {
//得到加载的内容
Map<String, Object> handlerMappings = getHandlerMappings();
//根据标签的namspace得到handler类名
//dubbo的namespace为http://dubbo.apache.org/schema/dubbo
Object handlerOrClassName = handlerMappings.get(namespaceUri);
if (handlerOrClassName == null) {
return null;
}
else if (handlerOrClassName instanceof NamespaceHandler) {
return (NamespaceHandler) handlerOrClassName;
}
else {
String className = (String) handlerOrClassName;
try {
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 = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
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);
}
}
}

得到DubboNamespaceHandler实例后,调用其init()方法

    @Override
public void init() {
//将application、module等名称做为key注册到解析器Map中
//这些key名称正是dubbo自定义标签的localName,例如<dubbo:application />标签的LocalName是application
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());
}

可以看到所有的key(除了annotation)对应的标签解析器都是DubboBeanDefinitionParser实例对象,至于DubboBeanDefinitionParser是如何解析标签的,这里就不做分析了,如果想要了解,可以看看他的源码。

如果不做特别说明,此篇往后所有的Dubbo源码分析都是基于Dubbo2.7.0

Dubbo2.7源码分析-Dubbo是如何整合spring-framework的的更多相关文章

  1. Spring Security 源码分析(四):Spring Social实现微信社交登录

    社交登录又称作社会化登录(Social Login),是指网站的用户可以使用腾讯QQ.人人网.开心网.新浪微博.搜狐微博.腾讯微博.淘宝.豆瓣.MSN.Google等社会化媒体账号登录该网站. 前言 ...

  2. Dubbo2.7源码分析-如何发布服务

    Dubbo的服务发布逻辑是比较复杂的,我还是以Dubbo自带的示例讲解,这样更方便和容易理解. Provider配置如下: <?xml version="1.0" encod ...

  3. 源码分析Dubbo服务消费端启动流程

    通过前面文章详解,我们知道Dubbo服务消费者标签dubbo:reference最终会在Spring容器中创建一个对应的ReferenceBean实例,而ReferenceBean实现了Spring生 ...

  4. ABP源码分析二十七:ABP.Entity Framework

    IRepository:接口定义了Repository常见的方法 AbpRepositoryBase:实现了IRepository接口的常见方法 EfRepositoryBase:实现了AbpRepo ...

  5. MyBatis源码分析(六):Spring整合分析

    一.Mybatis-Spring源码结构 二.Myabtis交给Spring管理的组件 1. dataSource 数据源 配置一个数据源,只要是实现了javax.sql.DataSource接口就可 ...

  6. dubbo源码分析--dubbo spi解析

    1. 什么叫SPI? 简单总结就是一种使用类名字符串来动态实例化java类的方式,也就是反射. 2. java SPI与Dubbo SPI有什么区别 (此图来自网上,我没有刻意去截图) 然后在这个文件 ...

  7. Dubbo2.7源码分析-SPI的应用

    SPI简介 SPI是Service Provider Interface的缩写,即服务提供接口(翻译出来好绕口,还是不翻译的好),实质上是接口,作用是对外提供服务. SPI是Java的一种插件机制,可 ...

  8. spring5源码分析系列(二)——spring核心容器体系结构

    首先我们来认识下IOC和DI: IOC(Inversion of Control)控制反转:控制反转,就是把原先代码里面需要实现的对象创建.依赖的代码,反转给容器来帮忙实现.所以需要创建一个容器,并且 ...

  9. 源码分析--dubbo服务端暴露

    服务暴露的入口方法是 ServiceBean 的 onApplicationEvent.onApplicationEvent 是一个事件响应方法,该方法会在收到 Spring 上下文刷新事件后执行服务 ...

随机推荐

  1. Android 如何修改gen下包的名字

    前言 当将项目中包进行重命名后,所有包名字修改了,但是在gen目录下android sdk 自动生成的包名并没有修改,如果要引用R.java 中的字段, 又得import以前的包名字.   原因 出现 ...

  2. 曲演杂坛--重建索引后,还使用混合分区么?(Are mixed pages removed by an index rebuild?)

    原文来自:http://www.sqlskills.com/blogs/paul/mixed-pages-removed-index-rebuild/ 在SQL SERVER 中,区是管理空间的基本单 ...

  3. 爬虫浅谈一:一个简单c#爬虫程序

    这篇文章只是简单展示一个基于HTTP请求如何抓取数据的文章,如觉得简单的朋友,后续我们再慢慢深入研究探讨. 图1: 如图1,我们工作过程中,无论平台网站还是企业官网,总少不了新闻展示.如某天产品经理跟 ...

  4. 阿里云ECS服务器windows环境下配置redis

    一.下载解压redis github下载地址:https://github.com/MSOpenTech/redis/tags 下载的是Redis-x64-3.2.100版本,Redis-x64-3. ...

  5. 关于gridview改变行内容事件需要点击别的行或控件才能执行

    一般的datagridview控件: this.dgvAssetList.CurrentCell = null;//取消datagridview行的编辑状态 Dev gridcontrol控件  Gr ...

  6. 反射获取属性DisplayName特性名字以及属性值

    /// <summary> /// 反射获取所有DisplayName标记值 /// </summary> /// <typeparam name="T&quo ...

  7. Oracle数据库设置Scott登录

    Oracle数据库Scott登录 在安装数据库时,用户登录选项中,Scott用户默认是未解锁的. 用户名填写as sysdba:密码是原来设置的,登录进去,新建SQL窗口,输入命令: alert us ...

  8. 《JavaScript高级程序设计》3.7 函数

    位于return语句之后的代码不会执行; return语句也可以不带有任何返回值. 这种情况下, 函数在停止执行后会返回undefined值. 这种用法一般用在需要提前停止函数执行而又不需要返回值的情 ...

  9. Spring Boot项目搭建

    1.Spring Boot概述 Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框架使用了特定的方式来进行配置,从而使开发人 ...

  10. HTML、CSS

    表格标签: 表格标签有:<table> <tr> <th> <td> 让内容居中的标签:<center> 按钮标签:<button&g ...