dubbo服务导出
常见的使用dubbo的方式就是通过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"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
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">
<dubbo:application name="helloService-provider"/>
<dubbo:registry address="zookeeper://localhost:2181"/>
<dubbo:reference interface="com.zhuge.learn.dubbo.services.HelloService"
check="false" id="helloService">
<dubbo:method name="sayHello" retries="2"/>
</dubbo:reference>
</beans>
spring对于非默认命名空间的标签的解析是通过NamespaceHandlerResolver实现的,NamespaceHandlerResolver也算是一种SPI机制,通过解析jar包中的META-INF/spring.handlers文件,将所有的NamespaceHandler实现类以k-v的形式解析出来并放到内存中。所以要想扩展spring的命名空间,就要实现一个NamespaceHandler。
dubbo实现了自己的命名空间,对应的NamespaceHandler实现类是com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler。这个类也很简单,就是定义了用于解析不同标签的BeanDefinition解析类。但是dubbo的实现稍有不同,它将所有标签的解析都放到同一个类同一个方法中,个人认为这种设计欠妥,不利于扩展新的标签。
如果我们要创建一个服务提供者,我们需要在配置文件中配置service标签,所以dubbo的服务导出一定与这个标签相关。查看DubboNamespaceHandler代码会发现,服务导出的逻辑主要是由ServiceBean实现的,所以接下来我们就以ServiceBean为入口,一步步来分析dubbo的服务导出过程。
ServiceBean概览
ServiceBean继承了ServiceConfig类,同时实现了一大堆接口,这些接口基本上都与spring框架相关。其中ApplicationListener
onApplicationEvent
public void onApplicationEvent(ContextRefreshedEvent event) {
// 如果已经导出或者关闭服务,就忽略该事件
if (!isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
export();
}
}
ServiceConfig.export
真正的导出服务的逻辑在父类方法中
// 这是一个同步方法,保证多线程情况下不会同时进行服务导出
public synchronized void export() {
// 检查一些配置是否为空,对于空的配置创建默认的配置
checkAndUpdateSubConfigs();
if (!shouldExport()) {
return;
}
if (shouldDelay()) {
// 延迟导出服务
delayExportExecutor.schedule(this::doExport, delay, TimeUnit.MILLISECONDS);
} else {
doExport();
}
}
protected synchronized void doExport() {
// 首先做一些状态检查
// 如果已经反导出服务,说明服务已经被关闭
if (unexported) {
throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!");
}
// 如果已经导出过了,就不需要重复导出了
if (exported) {
return;
}
exported = true;
// 如果服务名为空,以服务接口名作为服务名称
if (StringUtils.isEmpty(path)) {
path = interfaceName;
}
doExportUrls();
}
doExportUrls
我们直接进入核心代码,
private void doExportUrls() {
// 加载所有的注册中心的URL
List
// 如果配置了多个协议,那么每种协议都要导出,并且是对所有可用的注册url进行注册
for (ProtocolConfig protocolConfig : protocols) {
// 拼接服务名称,这里的path一般就是服务名
String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version);
// 服务提供者模型,用于全面描述服务提供者的信息
ProviderModel providerModel = new Provi