0
点赞
收藏
分享

微信扫一扫

dubbo xml配置解析入口源码分析


1、撕开真面目

首先,dubbo是依赖于spring进行配置的,那么,配置dubbo的其中一种方式就是通过spring的applicationContext.xml文件。

不过问题是,spring容器启动时,一般只会解析applicationContext.xml中spring自己认识的元素,如bean元素,并将其注册为beanDefinition,那么spring是如何识别dubbo元素的呢?如dubbo:service。

答案是,spring在解析applicationContext.xml配置文件时,将其分为两类,一类是自己认识的元素,如bean元素,另一类则为自定义的元素,需要第三方框架自定义解析实现,本文所关注的dubbo xml配置的解析入口就在这里啦。

那么spring是如何找到第三方框架的自定义元素的解析实现的呢?答案是,spring会读取classpath:META-INF/spring.handlers文件,文件内容为key-value对的形式,key为namespace,如http://dubbo.apache.org/schema/dubbo,value是一个全限定类名,如com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler,该类就是自定义元素的解析实现。spring会将这些key-value对写入类型为ConcurrentHashMap的handlerMappings变量,之后会将解析实现的全限定类名实例化,并覆盖为新的value。

另外,在自定义元素时,META-INF/spring.schemas文件也必不可少,该文件说明了xsd文件的位置,如META-INF/dubbo.xsd

其实说到这里,大家也就明白咋回事了,不过下面还是通过搭建一个简单的dubbo项目,并进行具体的源码分析,让大家看清楚。

2、dubbo项目搭建

1、项目依赖

首先,通过IDE创建一个Maven项目,然后在pom.xml中引入spring、dubbo等依赖,dubbo使用2.6版本。完整的pom.xml如下所示。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.bobo</groupId>
<artifactId>spring-dubbo</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<resource.delimiter>@</resource.delimiter>
<maven.compiler.source>${java.version}</maven.compiler.source>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.target>${java.version}</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.12</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.42.Final</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.6.3</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.2.0</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.1.11</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.11</version>
</dependency>
</dependencies>
</project>

2、日志配置

上面已经引入了logback的依赖,接下来需要在src/main/resources目录下新建logback.xml文件,内容如下。

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>

<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>

3、配置zookeeper

下载、解压,​​https://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/stable/​​

在zookeeper根目录下新建data文件夹,然后在conf目录下将zoo_sample.cfg复制一份命名为zoo.cfg。

将zoo.cfg中dataDir配置项的值改为刚刚新建的data文件夹路径,dataLogDir配置项的值设置为log文件夹路径,注意:路径要采用正斜杠不要用反斜杠或双反斜杠。

最后双击bin目录下的zkServer.cmd,zookeeper就启动成功了。

4、服务提供者

为了方便,我会将服务提供者和服务消费者放到一个项目,并用package区分。

定义接口:

package com.bobo.springdubbo.api;

public interface HelloService {
void sayHello(String name);
}

接口实现:

package com.bobo.springdubbo.provider;

import com.bobo.springdubbo.api.HelloService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloServiceImpl implements HelloService {
private static final Logger log = LoggerFactory.getLogger(HelloServiceImpl.class);
@Override
public void sayHello(String name) {
log.info("hello,{}",name);
}
}

applicationContext.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
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd"

xmlns:dubbo="http://dubbo.apache.org/schema/dubbo">

<dubbo:application name="spring-dubbo-provider"></dubbo:application>

<dubbo:registry address="zookeeper://localhost:2181"></dubbo:registry>

<dubbo:protocol name="dubbo" port="20880"></dubbo:protocol>

<bean id="helloService" class="com.bobo.springdubbo.provider.HelloServiceImpl"></bean>

<dubbo:service interface="com.bobo.springdubbo.api.HelloService" ref="helloService"></dubbo:service>
</beans>

启动spring容器,提供dubbo服务:

package com.bobo.springdubbo.provider;

import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.io.IOException;

public class Provider {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/dubbo-provider.xml");
context.refresh();
System.in.read();
}
}

运行main方法,启动服务提供者。

5、服务消费者

applicationContext.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
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd"

xmlns:dubbo="http://dubbo.apache.org/schema/dubbo">

<dubbo:application name="spring-dubbo-consumer"></dubbo:application>

<dubbo:registry address="zookeeper://localhost:2181"></dubbo:registry>

<dubbo:reference id="helloService" interface="com.bobo.springdubbo.api.HelloService"></dubbo:reference>
</beans>

启动spring容器,消费dubbo服务:

package com.bobo.springdubbo.consumer;

import com.bobo.springdubbo.api.HelloService;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Consumer {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/dubbo-consumer.xml");
context.refresh();
HelloService helloService = context.getBean(HelloService.class);
helloService.sayHello("张三");
}
}

运行main方法,服务提供者控制台打印日志如下:

20:36:23.075 [DubboServerHandler-192.168.211.1:20880-thread-5] INFO  c.b.s.provider.HelloServiceImpl - hello,张三

6 、项目结构图

dubbo xml配置解析入口源码分析_dubbo

3、源码分析

一个简答完整的dubbo项目搭建并运行成功后,下面就来带大家分析一下服务提供者启动时源码。

new ClassPathXmlApplicationContext(“classpath:spring/dubbo-provider.xml”),这行代码标志着spring容器启动,通过跟踪其构造方法,发现会走其父类AbstractApplicationContext的refresh()方法,该方法做了很多事,我们关注其中一行代码:obtainFreshBeanFactory(),该方法用于获取bean工厂,其中包含了解析xml文件并注册beanDefinition的代码,跟踪进去。

dubbo xml配置解析入口源码分析_dubbo_02


此时已经跟踪到如上图所示的位置,想要知道spring解析dubbo xml配置的时机,那么加载beanDefinition是尤为重要的,因为spring会解析xml配置文件并将其注册为beanDefinition,继续跟踪进去。

dubbo xml配置解析入口源码分析_dubbo_03


现在已经跟踪到了上图的位置,该类为XmlBeanDefinitionReader,这是毋庸置疑的,它表明会从xml文件中读取beanDefition。继续跟踪到下图的位置。

dubbo xml配置解析入口源码分析_xml_04


createReaderContext方法很重要,该方法会构造一个XmlReaderContext对象,该对象有一个类型为NamespaceHandlerResolver的属性namespaceHandlerResolver,这个属性就是namespace处理器的解析器,它的resolve方法会接收一个String类型的参数namespaceUri,如dubbo的namespace为http://dubbo.apache.org/schema/dubbo,然后返回一个NamespaceHandler,如dubbo的com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler。当有了对应的namespaceHandler之后,就能够解析该namespace下的元素了,如前面一直提到的dubbo:service。

dubbo xml配置解析入口源码分析_dubbo_05


当然,我们的xml配置文件有很多的namespace,比如spring aop、spring tx,dubbo等等,那么DefaultNamespaceHandlerResolver是如何找到并且装下这么多的namespace与对应的namespaceHandler的呢,答案是通过handlerMappingsLocation,如上图所示,默认路径为:META-INF/spring.handlers。接下来看一下dubbo的jar包中是否有这样的文件。

dubbo xml配置解析入口源码分析_java_06


嗯。没问题,确实有。spring会扫描classpath:META-INF路径下所有的spring.handlers文件,并写入Map中,如下图所示。

dubbo xml配置解析入口源码分析_dubbo_07

那什么时候用到这些handler呢,接着往下跟踪。

dubbo xml配置解析入口源码分析_spring_08


再继续跟踪到上图的位置,到这里就尤为关键了,可以看到通过isDefaultNamespace方法的判断,将xml元素的解析分为了两路:default和custom。default就是spring自己的元素,如bean元素,而custom就是第三方框架提供的元素,如dubbo:service,点进parseCustomElement方法继续跟踪。

dubbo xml配置解析入口源码分析_apache_09


如上图所示,这里可以看到,会根据namespaceUri找到对应的handler,此时的handler已经从一个全限定类名字符串实例化为一个真正的NamespaceHandler类型的实例了,最后再调用handler的parse方法,再往里跟踪就会跟踪到dubbo解析xml配置的源码了,本文只分析dubbo xml配置解析的入口,真正的解析过程不做分析。


举报

相关推荐

0 条评论