源码分析Dubbo服务提供者启动流程-上篇

本文详细分析了Dubbo中ServiceConfig暴露服务的过程,包括服务延迟策略、doExport方法的执行逻辑,以及服务注册到注册中心的详细步骤。重点讲解了`isDelay()`方法、`export()`和`doExport()`函数,展示了Dubbo服务暴露的内部工作机制。
摘要由CSDN通过智能技术生成

private boolean isDelay() {

Integer delay = getDelay();

ProviderConfig provider = getProvider();

if (delay == null && provider != null) {

delay = provider.getDelay();

}

return supportedApplicationListener && (delay == null || delay == -1);

}

如果有设置dubbo:service或dubbo:provider的属性delay,或配置delay为-1,都表示启用延迟机制,单位为毫秒,设置为-1,表示等到Spring容器初始化后再暴露服务。从这里也可以看出,Dubbo暴露服务的处理入口为ServiceBean#export—》ServiceConfig#export。

1.1 源码分析ServiceConfig#export 暴露服务

调用链:ServiceBean#afterPropertiesSet------>ServiceConfig#export

public synchronized void export() {

if (provider != null) {

if (export == null) {

export = provider.getExport();

}

if (delay == null) {

delay = provider.getDelay();

}

}

if (export != null && !export) { // @1

return;

}

if (delay != null && delay > 0) { // @2

delayExportExecutor.schedule(new Runnable() {

@Override

public void run() {

doExport();

}

}, delay, TimeUnit.MILLISECONDS);

} else {

doExport(); //@3

}

}

代码@1:判断是否暴露服务,由dubbo:service export="true|false"来指定。

代码@2:如果启用了delay机制,如果delay大于0,表示延迟多少毫秒后暴露服务,使用ScheduledExecutorService延迟调度,最终调用doExport方法。

代码@3:执行具体的暴露逻辑doExport,需要大家留意:delay=-1的处理逻辑(基于Spring事件机制触发)。

1.2 源码分析ServiceConfig#doExport暴露服务

调用链:ServiceBean#afterPropertiesSet—调用------>ServiceConfig#export------>ServiceConfig#doExport

ServiceConfig#checkDefault

private void checkDefault() {

if (provider == null) {

provider = new ProviderConfig();

}

appendProperties(provider);

}

Step1:如果dubbo:servce标签也就是ServiceBean的provider属性为空,调用appendProperties方法,填充默认属性,其具体加载顺序:

  1. 从系统属性加载对应参数值,参数键:dubbo.provider.属性名,System.getProperty。

  2. 加载属性配置文件的值。属性配置文件,可通过系统属性:dubbo.properties.file,如果该值未配置,则默认取dubbo.properties属性配置文件。

ServiceConfig#doExport

if (ref instanceof GenericService) {

interfaceClass = GenericService.class;

if (StringUtils.isEmpty(generic)) {

generic = Boolean.TRUE.toString();

}

} else {

try {

interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()

.getContextClassLoader());

} catch (ClassNotFoundException e) {

throw new IllegalStateException(e.getMessage(), e);

}

checkInterfaceAndMethods(interfaceClass, methods);

checkRef();

generic = Boolean.FALSE.toString();

}

Step2:校验ref与interface属性。如果ref是GenericService,则为dubbo的泛化实现,然后验证interface接口与ref引用的类型是否一致。

ServiceConfig#doExport

if (local != null) {

if (“true”.equals(local)) {

local = interfaceName + “Local”;

}

Class<?> localClass;

try {

localClass = ClassHelper.forNameWithThreadContextClassLoader(local);

} catch (ClassNotFoundException e) {

throw new IllegalStateException(e.getMessage(), e);

}

if (!interfaceClass.isAssignableFrom(localClass)) {

throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName);

}

}

Step3:dubbo:service local机制,已经废弃,被stub属性所替换。

Step4:处理本地存根Stub,<dubbo:service 的stub属性,可以设置为true,此时Stub的类名为:interface+Stub,stub也可以指定自定义的全类名。本地存根说明如图所示(Dubbo官方文档)

这里写图片描述

ServiceConfig#doExport

checkApplication();

checkRegistry();

checkProtocol();

appendProperties(this);

Step5:校验ServiceBean的application、registry、protocol是否为空,并从系统属性(优先)、资源文件中填充其属性。

系统属性、资源文件属性的配置如下:

application dubbo.application.属性名,例如 dubbo.application.name

registry dubbo.registry.属性名,例如 dubbo.registry.address

protocol dubbo.protocol.属性名,例如 dubbo.protocol.port

service dubbo.service.属性名,例如 dubbo.service.stub

ServiceConfig#doExport

checkStubAndMock(interfaceClass);

Step6:校验stub、mock类的合理性,是否是interface的实现类。

ServiceConfig#doExport

doExportUrls();

Step7:执行doExportUrls()方法暴露服务,接下来会重点分析该方法。

ServiceConfig#doExport

ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), this, ref);

ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);

Step8:将服务提供者信息注册到ApplicationModel实例中。

1.3 源码分析ServiceConfig#doExportUrls暴露服务具体实现逻辑

调用链:ServiceBean#afterPropertiesSet------>ServiceConfig#export------>ServiceConfig#doExport

private void doExportUrls() {

List registryURLs = loadRegistries(true); // @1

for (ProtocolConfig protocolConfig : protocols) {

doExportUrlsFor1Protocol(protocolConfig, registryURLs); // @2

}

}

代码@1:首先遍历ServiceBean的List< RegistryConfig> registries(所有注册中心的配置信息),然后将地址封装成URL对象,关于注册中心的所有配置属性,最终转换成url的属性(?属性名=属性值),loadRegistries(true),参数的意思:true,代表服务提供者,false:代表服务消费者,如果是服务提供者,则检测注册中心的配置,如果配置了register=“false”,则忽略该地址,如果是服务消费者,并配置了subscribe="false"则表示不从该注册中心订阅服务,故也不返回,一个注册中心URL示例:

registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.0&pid=7072&qos.port=22222&registry=zookeeper&timestamp=1527308268041

代码@2:然后遍历配置的所有协议,根据每个协议,向注册中心暴露服务,接下来重点分析doExportUrlsFor1Protocol方法的实现细节。

1.4 源码分析doExportUrlsFor1Protocol

调用链:ServiceBean#afterPropertiesSet------>ServiceConfig#export------>ServiceConfig#doExport------>ServiceConfig#doExportUrlsFor1Protocol

ServiceConfig#doExportUrlsFor1Protocol

String name = protocolConfig.getName();

if (name == null || name.length() == 0) {

name = “dubbo”;

}

Map<String, String> map = new HashMap<String, String>();

map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);

map.put(Constants.DUBBO_VERSION_KEY, Version.getVersion());

map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));

if (ConfigUtils.getPid() > 0) {

map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));

}

appendParameters(map, application);

appendParameters(map, module);

appendParameters(map, provider, Constants.DEFAULT_KEY);

appendParameters(map, protocolConfig);

appendParameters(map, this);

Step1:用Map存储该协议的所有配置参数,包括协议名称、dubbo版本、当前系统时间戳、进程ID、application配置、module配置、默认服务提供者参数(ProviderConfig)、协议配置、服务提供Dubbo:service的属性。

ServiceConfig#doExportUrlsFor1Protocol

if (methods != null && !methods.isEmpty()) {

for (MethodConfig method : methods) {

appendParameters(map, method, method.getName());

String retryKey = method.getName() + “.retry”;

if (map.containsKey(retryKey)) {

String retryValue = map.remove(retryKey);

if (“false”.equals(retryValue)) {

map.put(method.getName() + “.retries”, “0”);

}

}

List arguments = method.getArguments();

if (arguments != null && !arguments.isEmpty()) {

for (ArgumentConfig argument : arguments) {

// convert argument type

if (argument.getType() != null && argument.getType().length() > 0) {

Method[] methods = interfaceClass.getMethods();

// visit all methods

if (methods != null && methods.length > 0) {

for (int i = 0; i < methods.length; i++) {

String methodName = methods[i].getName();

// target the method, and get its signature

if (methodName.equals(method.getName())) {

Class<?>[] argtypes = methods[i].getParameterTypes();

// one callback in the method

if (argument.getIndex() != -1) {

if (argtypes[argument.getIndex()].getName().equals(argument.getType())) {

appendParameters(map, argument, method.getName() + “.” + argument.getIndex());

} else {

throw new IllegalArgumentException(“argument config error : the index attribute and type attribute not match :index :” + argument.getIndex() + “, type:” +

argument.getType());

}

} else {

// multiple callbacks in the method

for (int j = 0; j < argtypes.length; j++) {

Class<?> argclazz = argtypes[j];

if (argclazz.getName().equals(argument.getType())) {

appendParameters(map, argument, method.getName() + “.” + j);

if (argument.getIndex() != -1 && argument.getIndex() != j) {

throw new IllegalArgumentException(“argument config error : the index attribute and type attribute not match :index :” + argument.getIndex() + ",

type:" + argument.getType());

}

}

}

}

}

}

}

} else if (argument.getIndex() != -1) {

appendParameters(map, argument, method.getName() + “.” + argument.getIndex());

} else {

throw new IllegalArgumentException(“argument config must set index or type attribute.eg: <dubbo:argument index=‘0’ …/> or <dubbo:argument type=xxx …/>”);

}

}

}

} // end of methods for

}

Step2:如果dubbo:service有dubbo:method子标签,则dubbo:method以及其子标签的配置属性,都存入到Map中,属性名称加上对应的方法名作为前缀。dubbo:method的子标签dubbo:argument,其键为方法名.参数序号。

ServiceConfig#doExportUrlsFor1Protocol

if (ProtocolUtils.isGeneric(generic)) {

map.put(Constants.GENERIC_KEY, generic);

map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);

} else {

String revision = Version.getVersion(interfaceClass, version);

if (revision != null && revision.length() > 0) {

map.put(“revision”, revision);

}

String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();

if (methods.length == 0) {

logger.warn("NO method found in service interface " + interfaceClass.getName());

map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);

} else {

map.put(Constants.METHODS_KEY, StringUtils.join(new HashSet(Arrays.asList(methods)), “,”));

}

}

Step3:添加methods键值对,存放dubbo:service的所有方法名,多个方法名用,隔开,如果是泛化实现,填充genric=true,methods为"*";

ServiceConfig#doExportUrlsFor1Protocol

if (!ConfigUtils.isEmpty(token)) {

if (ConfigUtils.isDefault(token)) {

map.put(Constants.TOKEN_KEY, UUID.randomUUID().toString());

} else {

map.put(Constants.TOKEN_KEY, token);

}

}

Step4:根据是否开启令牌机制,如果开启,设置token键,值为静态值或uuid。

ServiceConfig#doExportUrlsFor1Protocol

if (Constants.LOCAL_PROTOCOL.equals(protocolConfig.getName())) {

protocolConfig.setRegister(false);

map.put(“notify”, “false”);

}

Step5:如果协议为本地协议(injvm),则设置protocolConfig#register属性为false,表示不向注册中心注册服务,在map中存储键为notify,值为false,表示当注册中心监听到服务提供者发送变化(服务提供者增加、服务提供者减少等事件时不通知。

ServiceConfig#doExportUrlsFor1Protocol

// export service

String contextPath = protocolConfig.getContextpath();

if ((contextPath == null || contextPath.length() == 0) && provider != null) {

contextPath = provider.getContextpath();

}

Step6:设置协议的contextPath,如果未配置,默认为/interfacename

ServiceConfig#doExportUrlsFor1Protocol

String host = this.findConfigedHosts(protocolConfig, registryURLs, map);

Integer port = this.findConfigedPorts(protocolConfig, name, map);

Step7:解析服务提供者的IP地址与端口。

服务IP地址解析顺序:(序号越小越优先)

  1. 系统环境变量,变量名:DUBBO_DUBBO_IP_TO_BIND

  2. 系统属性,变量名:DUBBO_DUBBO_IP_TO_BIND

  3. 系统环境变量,变量名:DUBBO_IP_TO_BIND

  4. 系统属性,变量名:DUBBO_IP_TO_BIND

  5. dubbo:protocol 标签的host属性 --》 dubbo:provider 标签的host属性

  6. 默认网卡IP地址,通过InetAddress.getLocalHost().getHostAddress()获取,如果IP地址不符合要求,继续下一个匹配。

判断IP地址是否符合要求的标准是:

public static boolean isInvalidLocalHost(String host) {

return host == null

|| host.length() == 0

|| host.equalsIgnoreCase(“localhost”)

|| host.equals(“0.0.0.0”)

|| (LOCAL_IP_PATTERN.matcher(host).matches());

}

复习的面试资料

这些面试全部出自大厂面试真题和面试合集当中,小编已经为大家整理完毕(PDF版)

  • 第一部分:Java基础-中级-高级

image

  • 第二部分:开源框架(SSM:Spring+SpringMVC+MyBatis)

image

  • 第三部分:性能调优(JVM+MySQL+Tomcat)

image

  • 第四部分:分布式(限流:ZK+Nginx;缓存:Redis+MongoDB+Memcached;通讯:MQ+kafka)

image

  • 第五部分:微服务(SpringBoot+SpringCloud+Dubbo)

image

  • 第六部分:其他:并发编程+设计模式+数据结构与算法+网络

image

进阶学习笔记pdf

  • Java架构进阶之架构筑基篇(Java基础+并发编程+JVM+MySQL+Tomcat+网络+数据结构与算法

image

  • Java架构进阶之开源框架篇(设计模式+Spring+SpringMVC+MyBatis

image

image

image

  • Java架构进阶之分布式架构篇 (限流(ZK/Nginx)+缓存(Redis/MongoDB/Memcached)+通讯(MQ/kafka)

image

image

image

  • Java架构进阶之微服务架构篇(RPC+SpringBoot+SpringCloud+Dubbo+K8s)

image

image

26)]

  • 第六部分:其他:并发编程+设计模式+数据结构与算法+网络

[外链图片转存中…(img-dnsIYIie-1714751429726)]

进阶学习笔记pdf

  • Java架构进阶之架构筑基篇(Java基础+并发编程+JVM+MySQL+Tomcat+网络+数据结构与算法

[外链图片转存中…(img-vkjz5Dv1-1714751429727)]

  • Java架构进阶之开源框架篇(设计模式+Spring+SpringMVC+MyBatis

[外链图片转存中…(img-PLQ639gL-1714751429727)]

[外链图片转存中…(img-i0IeWD9p-1714751429727)]

[外链图片转存中…(img-jrNpPvte-1714751429728)]

  • Java架构进阶之分布式架构篇 (限流(ZK/Nginx)+缓存(Redis/MongoDB/Memcached)+通讯(MQ/kafka)

[外链图片转存中…(img-sJliMNE3-1714751429728)]

[外链图片转存中…(img-D3jhEw74-1714751429728)]

[外链图片转存中…(img-FAr8nijQ-1714751429729)]

  • Java架构进阶之微服务架构篇(RPC+SpringBoot+SpringCloud+Dubbo+K8s)

[外链图片转存中…(img-5P2k2VCN-1714751429729)]

[外链图片转存中…(img-olAPttzG-1714751429729)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 12
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值