首先来看一下服务启动时所打印的日志:
本地服务暴露
INFO config.AbstractConfig: [DUBBO] Export dubbo service com.alibaba.dubbo.demo.DemoService to local registry, dubbo version: , current host: 192.168.43.34
远程服务暴露
INFO config.AbstractConfig: [DUBBO] Export dubbo service com.alibaba.dubbo.demo.DemoService to url dubbo://192.168.43.34:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&bean.name=com.alibaba.dubbo.demo.DemoService&bind.ip=192.168.43.34&bind.port=20880&dubbo=2.0.2&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=83769&qos.port=22222&side=provider×tamp=1678792780010, dubbo version: , current host: 192.168.43.34
本地服务注册
INFO config.AbstractConfig: [DUBBO] Register dubbo service com.alibaba.dubbo.demo.DemoService url dubbo://192.168.43.34:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&bean.name=com.alibaba.dubbo.demo.DemoService&bind.ip=192.168.43.34&bind.port=20880&dubbo=2.0.2&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=83769&qos.port=22222&side=provider×tamp=1678792780010 to registry registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&pid=83769&qos.port=22222®istry=zookeeper×tamp=1678792779998, dubbo version: , current host: 192.168.43.34
启动netty监听
INFO transport.AbstractServer: [DUBBO] Start NettyServer bind /0.0.0.0:20880, export /192.168.43.34:20880, dubbo version: , current host: 192.168.43.34
将注册信息缓存在本地
INFO zookeeper.ZookeeperRegistry: [DUBBO] Load registry store file /Users/didi/.dubbo/dubbo-registry-demo-provider-127.0.0.1:2181.cache, data: {com.alibaba.dubbo.demo.DemoService=empty://192.168.43.34:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&bean.name=com.alibaba.dubbo.demo.DemoService&category=configurators&check=false&dubbo=2.0.2&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=51040&side=provider×tamp=1678605917764}, dubbo version: , current host: 192.168.43.34
注册到zookeeper
INFO zookeeper.ZookeeperRegistry: [DUBBO] Register: dubbo://192.168.43.34:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&bean.name=com.alibaba.dubbo.demo.DemoService&dubbo=2.0.2&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=83769&side=provider×tamp=1678792780010, dubbo version: , current host: 192.168.43.34
监听zookeeper
INFO zookeeper.ZookeeperRegistry: [DUBBO] Subscribe: provider://192.168.43.34:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&bean.name=com.alibaba.dubbo.demo.DemoService&category=configurators&check=false&dubbo=2.0.2&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=83769&side=provider×tamp=1678792780010, dubbo version: , current host: 192.168.43.34
INFO zookeeper.ZookeeperRegistry: [DUBBO] Notify urls for subscribe url provider://192.168.43.34:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&bean.name=com.alibaba.dubbo.demo.DemoService&category=configurators&check=false&dubbo=2.0.2&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=83769&side=provider×tamp=1678792780010, urls: [empty://192.168.43.34:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&bean.name=com.alibaba.dubbo.demo.DemoService&category=configurators&check=false&dubbo=2.0.2&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=83769&side=provider×tamp=1678792780010], dubbo version: , current host: 192.168.43.34
从日志的输出上可以大概看出服务提供者在服务暴露和注册的大概流程;
1. 服务暴露
先抛开dubbo和spring的整理原理,更关注dubbo内部的实现,我们直接从ServiceBean开始,一个dubbo:service代表着一个ServiceBean,如果有多个,则需要分别处理每一个ServiceBean
:
ServiceBean实现了
ApplicationListener<ContextRefreshedEvent>
表示当ApplicationContext被初始化或刷新时,该事件会发生,这是典型的发布订阅模式:
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (isDelay() && !isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
export();
}
}
@Override
public void export() {
// 调用父类的export;
super.export();
// 发布该事件 ServiceBeanExportedEvent
publishExportEvent();
}
从export开始往下分析,首先看一下export的代码:
首先会调用ServiceConfig#export,它会调用doExport方法:
public synchronized void export() {
// 第一次进来时,这些都为null,直接会进入doExport;
if (provider != null) {
if (export == null) {
export = provider.getExport();
}
if (delay == null) {
delay = provider.getDelay();
}
}
if (export != null && !export) {
return;
}
if (delay != null && delay > 0) {
delayExportExecutor.schedule(new Runnable() {
@Override
public void run() {
doExport();
}
}, delay, TimeUnit.MILLISECONDS);
} else {
doExport();
}
}
下面这段代码,主要是在开始暴露之前的一些属性初始化,其中涉及到一个点,就是配置的优先级:
第二个点是,配置的加载顺序:
- JVM 启动 -D 参数优先,这样可以使用户在部署和启动时进行参数重写,比如在启动时需改变协议的端口。
- XML 次之,如果在 XML 中有配置,则 dubbo.properties 中的相应配置项无效。
- Properties 最后,相当于缺省值,只有 XML 没有配置时,dubbo.properties 的相应配置项才会生效,通常用于共享公共配置,比如应用名。
- 代码内置的默认配置;
protected synchronized void doExport() {
if (unexported) {
throw new IllegalStateException("Already unexported!");
}
if (exported) {
return;
}
exported = true;
if (interfaceName == null || interfaceName.length() == 0) {
throw new IllegalStateException("<dubbo:service interface=\"\" /> interface not allow null!");
}
// provider为空,则创建它,并且给它添加一些属性
checkDefault();
// provider已经在checkDefault方法中创建,它在这里不为null;
if (provider != null) {
if (application == null) {
application = provider.getApplication();
}
if (module == null) {
module = provider.getModule();
}
if (registries == null) {
registries = provider.getRegistries();
}
if (monitor == null) {
monitor = provider.getMonitor();
}
if (protocols == null) {
protocols = provider.getProtocols();
}
}
if (module != null) {
if (registries == null) {
registries = module.getRegistries();
}
if (monitor == null) {
monitor = module.getMonitor();
}
}
if (application != null) {
if (registries == null) {
registries = application.getRegistries();
}
if (monitor == null) {
monitor = application.getMonitor();
}
}
//在这里对上面的变量做下说明:
// 1. provider它所代表的是配置的<dubbo:provider/>标签
// 2. application它代表的是配置的<dubbo:application/>标签
// 3. module它代表的是配置的<dubbo:module/>标签
// 4. registries它代表的是配置的<dubbo:registry/>标签
// 5. protocols它代表的是配置的<dubbo:protocol/>标签,其中reistries和protocols是可以配置多个的;
// 6. monitor 用于配置连接监控中心相关信息<dubbo:monitor/>
// 判断当前暴露类是否是GenericService类型的
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);
}
// 这个methods,它是指:允许在dubbo:service的子标签中指定method,可以为指定方法级的配置;
// 检查这些methods是否属于这个接口中的方法;
checkInterfaceAndMethods(interfaceClass, methods);
// 检查实现类是否为该接口类型 interfaceClass.isInstance(ref)
checkRef();
generic = Boolean.FALSE.toString();
}
// 如果dubbo:service配置的local属性
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);
}
}
// 如果有配置stub属性
if (stub != null) {
if ("true".equals(stub)) {
stub = interfaceName + "Stub";
}
Class<?> stubClass;
try {
stubClass = ClassHelper.forNameWithThreadContextClassLoader(stub);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
if (!interfaceClass.isAssignableFrom(stubClass)) {
throw new IllegalStateException("The stub implementation class " + stubClass.getName() + " not implement interface " + interfaceName);
}
}
// 为application、registry、protocol、service设置属性;
checkApplication();
checkRegistry();
checkProtocol();
// this代表的是ServiceBean,而ServiceBean代表的是dubbo:service标签;
appendProperties(this);
checkStub(interfaceClass);
checkMock(interfaceClass);
if (path == null || path.length() == 0) {
path = interfaceName;
}
// 到了这里,重点方法;
doExportUrls();
CodecSupport.addProviderSupportedSerialization(getUniqueServiceName(), getExportedUrls());
ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), this, ref);
ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
}
private void checkDefault() {
// 如果provider为空,则创建
// 当 ProtocolConfig 和 ServiceConfig 某属性没有配置时,采用此缺省值,可选
if (provider == null) {
provider = new ProviderConfig();
}
// 设置属性
appendProperties(provider);
}
protected static void appendProperties(AbstractConfig config) {
// 这个config表示的是:<dubbo:provider />标签
if (config == null) {
return;
}
// dubbo.provider.
String prefix = "dubbo." + getTagName(config.getClass()) + ".";
// 获取到config所有的方法
Method[] methods = config.getClass().getMethods();
for (Method method : methods) {
try {
// 获取每个方法的名称
String name = method.getName();
// set开头的方法 && 是public && 方法参数只有一个
if (name.length() > 3 && name.startsWith("set") && Modifier.isPublic(method.getModifiers())
&& method.getParameterTypes().length == 1 && isPrimitive(method.getParameterTypes()[0])) {
// 获取属性名称,比如host
String property = StringUtils.camelToSplitName(name.substring(3, 4).toLowerCase() + name.substring(4), ".");
String value = null;
if (config.getId() != null && config.getId().length() > 0) {
String pn = prefix + config.getId() + "." + property;
value = System.getProperty(pn);
if (!StringUtils.isBlank(value)) {
logger.info("Use System Property " + pn + " to config dubbo");
}
}
// value为空,从System.properties中获取值
if (value == null || value.length() == 0) {
// dubbo.provider.host
String pn = prefix + property;
// 第一次:从系统参数中获取,系统参数是在启动时通过 -D设置的参数;
value = System.getProperty(pn);
if (!StringUtils.isBlank(value)) {
logger.info("Use System Property " + pn + " to config dubbo");
}
}
// 如果value还为空
// 第二次查找:从配置的<dubbo:provider>标签中获取;
if (value == null || value.length() == 0) {
// 获取该变更的get或is方法;
Method getter;
try {
getter = config.getClass().getMethod("get" + name.substring(3));
} catch (NoSuchMethodException e) {
try {
getter = config.getClass().getMethod("is" + name.substring(3));
} catch (NoSuchMethodException e2) {
getter = null;
}
}
// 如果有get或者is方法;
if (getter != null) {
// 则执行它,如果返回值为null
if (getter.invoke(config) == null) {
if (config.getId() != null && config.getId().length() > 0) {
value = ConfigUtils.getProperty(prefix + config.getId() + "." + property);
}
// 获取-D指定的dubbo.properties.file目录中的文件,如果未配置该参数,则默认获取dubbo.properties文件
// 第三次:从dubbo.properties文件中获取
if (value == null || value.length() == 0) {
// properties里面是一个map,通过key可以查找value;
value = ConfigUtils.getProperty(prefix + property);
}
// 第四次:legacyProperties是在static{}静态块中配置的几个参数
if (value == null || value.length() == 0) {
String legacyKey = legacyProperties.get(prefix + property);
if (legacyKey != null && legacyKey.length() > 0) {
value = convertLegacyValue(legacyKey, ConfigUtils.getProperty(legacyKey));
}
}
}
}
}
// 如果获取到值,则将其设置到对应属性中;
if (value != null && value.length() > 0) {
method.invoke(config, convertPrimitive(method.getParameterTypes()[0], value));
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
}
接下来开始解析doExportUrls的代码逻辑:
private void doExportUrls() {
// 一个服务可以注册到多个注册中心中;
List<URL> registryURLs = loadRegistries(true);
for (ProtocolConfig protocolConfig : protocols) {
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
String name = protocolConfig.getName();
if (name == null || name.length() == 0) {
name = "dubbo";
}
// 将application、provider、protocol、service等配置信息加载到map
// 如果有重名的,不会进行覆盖,比如:application的会用application.xxx,provider的属性会用default.xxx
Map<String, String> map = new HashMap<String, String>();
map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
map.put(Constants.DUBBO_VERSION_KEY, Version.