1.ServiceConfig–结构图
2.ServiceConfig-服务暴露
2.1.export()
export()先判断是否需要延迟暴露(这里我们使用的是不延迟暴露),然后执行doExport方法。
/**
* 暴露服务 同步方法
*/
public synchronized void export() {
// 当 export 或者 delay 为null,从 ProviderConfig 对象读取。 @ sjt 1
if (provider != null) {
if (export == null) {
export = provider.getExport();
}
if (delay == null) {
delay = provider.getDelay();
}
}
// 不暴露服务( export = false ) ,则不进行暴露服务逻辑。@ sjt 2
if (export != null && !export) { //
return;
}
// //延迟暴露 通过delay参数配置的,延迟暴露,放入单独线程中。@sjt 3
if (delay != null && delay > 0) {
delayExportExecutor.schedule(new Runnable() {
@Override
public void run() {
doExport();
}
}, delay, TimeUnit.MILLISECONDS);
// 立即暴露 @sjt 4
} else {
doExport();
}
}
- @sjt 1: 如果当前
export
,delay
数据为null,从providerConfig
中读取。 - @sjt 2: 判断是否暴露服务,由
dubbo:service export=“true|false”
来指定。 - @sjt 3: 如果启用了
delay
机制,如果delay
大于0,表示延迟多少毫秒后暴露服务,使用ScheduledExecutorService
[TODO] 延迟调度,最终调用doExport()
方法。 - @sjt 4: 执行具体的暴露逻辑
doExport()
,delay=-1
的处理逻辑( 基于Spring事件机制触发 )。
2.2.doExport()
doExport()
方法先执行一系列的检查方法,然后调用doExportUrls
方法。检查方法会检测dubbo的配置是否在Spring
配置文件中声明,没有的话读取properties
文件初始化。
@From:博客-Dubbo中暴露服务的过程解析
调用链:
ServiceBean#afterPropertiesSet
-->ServiceConfig#export
–>ServiceConfig#doExport
@From:《博客-聊聊Dubbo(九):核心源码-服务端启动流程-2.2 第二步:ServiceConfig#doExport() 暴露服务》
/**
* 检查填充配置项,按优先级覆盖
*/
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!");
}
// 拼接属性配置(环境变量 + properties 属性)到 ProviderConfig 对象 @sjt 1
checkDefault();
// 从 ProviderConfig 对象中,读取 application、module、registries、monitor、protocols 配置对象。
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();
}
}
// 从 ModuleConfig 对象中,读取 registries、monitor 配置对象。
if (module != null) {
if (registries == null) {
registries = module.getRegistries();
}
if (monitor == null) {
monitor = module.getMonitor();
}
}
// 从 ApplicationConfig 对象中,读取 registries、monitor 配置对象。
if (application != null) {
if (registries == null) {
registries = application.getRegistries();
}
if (monitor == null) {
monitor = application.getMonitor();
}
}
// 泛化接口的实现 @sjt 2
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);
}
// 最后处理ServiceConfig,优先级最高
checkInterfaceAndMethods(interfaceClass, methods);
// 校验指向的 service 对象
checkRef();
generic = Boolean.FALSE.toString();
}
// 处理服务接口客户端本地代理( `local` )相关。实际目前已经废弃,使用 `stub` 属性,参见 `AbstractInterfaceConfig#setLocal` 方法。 @sjt 3
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` )相关 @sjt 4
if (stub != null) {
// 设为 true,表示使用缺省代理类名,即:接口名 + Stub 后缀
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);
}
} // @sjt 5
// 校验 ApplicationConfig 配置。
checkApplication();
// 校验 RegistryConfig 配置。
checkRegistry();
// 校验 ProtocolConfig 配置数组。
checkProtocol();
// 读取环境变量和 properties 配置到 ServiceConfig 对象。
appendProperties(this);
// 校验 Stub 和 Mock 相关的配置 @sjt 6
checkStubAndMock(interfaceClass);
// 服务路径,缺省为接口名
if (path == null || path.length() == 0) {
path = interfaceName;
}
// 暴露服务 @sjt 7
doExportUrls();
/**
* @From:[Dubbo源码阅读笔记1](https://www.cnblogs.com/amwyyyy/p/8353504.html)
* 把要服务提供者信息封装成model,并设置方法可见性
*/
ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), ref, interfaceClass);
/**
* @From:[Dubbo源码阅读笔记1](https://www.cnblogs.com/amwyyyy/p/8353504.html)
* 放进全局的context中
*/
ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
}
-
@sjt 1: 如果
dubbo:servce
标签也就是ServiceBean
的provider
属性为null,调用appendProperties
方法,填充默认属性,其具体加载顺序:- 从系统属性加载对应参数值,参数键:
dubbo.provider.属性名
,System.getProperty
。 - 从属性配置文件加载对应参数值,可通过系统属性指定属性配置文件: dubbo.properties.file。
- 如果
dubbo.properties.file未配置
,则默认取 dubbo.properties 属性配置文件。
- 从系统属性加载对应参数值,参数键:
-
@sjt 2: 校验
ref
与interface
属性。如果ref
是GenericService
,则为Dubbo的泛化实现,然后验证interface
接口与ref
引用的类型是否一致。《dubbo文档-示例-泛化引用》 -
@sjt 3:
dubbo:service local
机制,已经废弃,被stub
属性所替换。 -
@sjt 4 处理本地存根
Stub
。 -
@sjt 5 校验
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
-
@sjt 6 校验
stub
、mock
类的合理性,是否是interface
的实现类。 -
@sjt 7 执行
doExportUrls()
方法暴露服务,重头戏! -
@sjt 8 将服务提供者信息注册到
ApplicationModel
实例中。
@From:《[博客]-聊聊Dubbo(九):核心源码-服务端启动流程-2.2 第二步:ServiceConfig#doExport 暴露服务》
@From:《[vip]-芋道源码-精尽 Dubbo 源码分析-API 配置(二)之服务提供者-8. ServiceConfig》
@From 《[源码]-org.apache.dubbo.configServiceConfig#doExport()》
2.3.doExportUrls()
@From:《博客-Dubbo中暴露服务的过程解析》
doExportUrls()
先调用loadRegistries
获取所有的注册中心url
,然后遍历调用doExportUrlsFor1Protocol
方法。对于在标签中指定了registry
属性的Bean
,会在加载BeanDefinition
的时候就加载了注册中心。
调用链:
ServiceBean#afterPropertiesSet
-->ServiceConfig#export
–>ServiceConfig#doExport
@From:《博客-聊聊Dubbo(九):核心源码-服务端启动流程-2.3 第三步:ServiceConfig#doExportUrls 暴露服务》
/**
* @From: [dubbo服务暴露过程源码分析(https://my.oschina.net/u/146130/blog/1630870)
* 暴露url
*/
@SuppressWarnings({"unchecked", "rawtypes"})
private void doExportUrls() {
获取注册中心信息 @sjt 1
List<URL> registryURLs = loadRegistries(true);
//多个协议,暴露多次 @sjt 2
for (ProtocolConfig protocolConfig : protocols) {
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
- @sjt 1: 首先遍历
ServiceBean
的List registries
(所有注册中心的配置信息),然后将地址封装成URL
对象,关于注册中心的所有配置属性,最终转换成url
的属性(?属性名=属性值),loadRegistries(true)
,参数的意思:true
,代表服务提供者,false
:代表服务消费者,如果是服务提供者,则检测注册中心的配置,如果配置了register=“false”
,则忽略该地址,如果是服务消费者,并配置了subscribe=“false”
则表示不从该注册中心订阅服务,故也不返回。 - @sjt 2: 遍历配置的所有协议,根据每个协议,向注册中心暴露服务,重点:
doExportUrlsFor1Protocol()
@From:《[博客]-聊聊Dubbo(九):核心源码-服务端启动流程-2.2第二步:ServiceConfig#doExportUrls
-暴露服务》
@From:《[vip]-芋道源码-精尽 Dubbo 源码分析-API 配置(二)之服务提供者-8. ServiceConfig》
@From 《[源码]-org.apache.dubbo.configServiceConfig#doExport()》
2.3.doExportUrlsFor1Protocol()
@From:《博客-Dubbo中暴露服务的过程解析》
根据不同的协议将服务以URL形式暴露。如果scope配置为none则不暴露,如果服务未配置成remote,则本地暴露exportLocal,如果未配置成local,则注册服务registryProcotol。
/**
* 根据不同的协议将服务以URL形式暴露
* @param protocolConfig 协议配置对象
* @param registryURLs 注册中心链接对象数组
*/
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
// @ sjt 1
// 协议名 协议名为null是,缺省“dubbo”
String name = protocolConfig.getName();
if (name == null || name.length() == 0) {
name = "dubbo";
}
// 将 `side`,`dubbo`,`timestamp`,`pid` 参数,添加到 `map` 集合中。
Map<String, String> map = new HashMap<String, String>();
map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
map.put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion());
map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
if (ConfigUtils.getPid() > 0) {
map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
}
// 将各种配置对象,添加到 `map` 集合中。
appendParameters(map, application);
appendParameters(map, module);
appendParameters(map, provider, Constants.DEFAULT_KEY);// ProviderConfig ,为 ServiceConfig 的默认属性,因此添加 `default` 属性前缀。
appendParameters(map, protocolConfig);
appendParameters(map, this);
- @sjt 1: 用
Map
存储该协议的所有配置参数.包括:协议名称、Dubbo版本、当前系统时间戳、进程ID、application配置、module配置、默认服务提供者参数(ProviderConfig
)、协议配置、服务提供Dubbo:service
的属性。
// @ sjt 2
// 将 MethodConfig 对象数组,添加到 `map` 集合中。
//目的是将每个 MethodConfig 和其对应的 ArgumentConfig 对象数组,添加到 map 中。
if (methods != null && !methods.isEmpty()) {
for (MethodConfig method : methods) {
appendParameters(map, method, method.getName());
// 当 配置了 `MethodConfig.retry = false` 时,强制禁用重试
String retryKey = method.getName() + ".retry";
if (map.containsKey(retryKey)) {
String retryValue = map.remove(retryKey);
if ("false".equals(retryValue)) {
map.put(method.getName() + ".retries", "0");
}
}
// 将 ArgumentConfig 对象数组,添加到 `map` 集合中。
List<ArgumentConfig> 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())) {
// 将 ArgumentConfig 对象,添加到 `map` 集合中。
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())) {
// 将 ArgumentConfig 对象,添加到 `map` 集合中。
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) {// 指定单个参数的位置
// 将 ArgumentConfig 对象,添加到 `map` 集合中。
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
}
- @sjt 2: 如果
dubbo:service
有dubbo:method
子标签,则dubbo:method
以及其子标签的配置属性,都存入到Map
中,属性名称加上对应的方法名作为前缀。dubbo:method
的子标签dubbo:argument
,其键为方法名.参数序号。
//@ sjt 3
//将 generic methods 【revision】 到 map 中。
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<String>(Arrays.asList(methods)), ","));
}
}
- sjt 3: 添加
methods
键值对,存放dubbo:service
的所有方法名,多个方法名用,
隔开,如果是泛化实现,填充genric=true
,methods
为“*”
。
// @ sjt 4
//将 token 添加到 map 中。
//From:[令牌验证](http://dubbo.apache.org/zh-cn/docs/user/demos/token-authorization.html)
if (!ConfigUtils.isEmpty(token)) {
if (ConfigUtils.isDefault(token)) {
map.put(Constants.TOKEN_KEY, UUID.randomUUID().toString());
} else {
map.put(Constants.TOKEN_KEY, token);
}
}
- @sjt4: 是否开启令牌机制,如果开启,设置 token 键,值为静态值或 uuid。
- @应用:《dubbo文档-示例-令牌验证》
- @参考:《博客-聊聊Dubbo(九):核心源码-服务端启动流程2》
//@sjt 5
//From:[本地调用](http://dubbo.apache.org/zh-cn/docs/user/demos/local-call.html)
if (Constants.LOCAL_PROTOCOL.equals(protocolConfig.getName())) {//Constants.LOCAL_PROTOCOL='injvm'
protocolConfig.setRegister(false);
map.put("notify", "false");
}
- @sjt5: 如果协议为本地协议(
injvm
),则设置protocolConfig#register
属性为false
,表示不向注册中心注册服务,在map
中存储键为notify
,值为false
,表示当注册中心监听到服务提供者发生变化(服务提供者增加、服务提供者减少等)事件时不通知。
// export service
//@sjt 6
// contextPath ,基础路径
String contextPath = protocolConfig.getContextpath();
if ((contextPath == null || contextPath.length() == 0) && provider != null) {
contextPath = provider.getContextpath();
}
- @sjt6: 设置协议的
contextPath
,如果未配置,默认为/interfacename
。
//@sjt 7
//获得注册到注册中心的服务提供者 Host 。
//From:[主机绑定](http://dubbo.apache.org/zh-cn/docs/user/demos/hostname-binding.html)
String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
//得注册到注册中心的服务提供者 Port
Integer port = this.findConfigedPorts(protocolConfig, name, map);
- @sjt 7: 解析服务提供者的IP地址与端口。
- @应用:《dubbo文档-示例-主机绑定》
//@sjt 8
// 创建 Dubbo URL 对象
URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);
if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.hasExtension(url.getProtocol())) {
url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.getExtension(url.getProtocol()).getConfigurator(url).configure(url);
}
- @sjt8: 根据协议名称、协议
host
、协议端口、contextPath
、相关配置属性(application
、module
、provider
、protocolConfig
、service
及其子标签)构建服务提供者URI
。
//@sjt 9
String scope = url.getParameter(Constants.SCOPE_KEY);
// don't export when none is configured
if (!Constants.SCOPE_NONE.equalsIgnoreCase(scope)) { //@sjt 9.1
// export to local if the config is not remote (export to remote only when config is remote)
if (!Constants.SCOPE_REMOTE.equalsIgnoreCase(scope)) { //@sjt 9.2
exportLocal(url);
}
// export to remote if the config is not local (export to local only when config is local)
if (!Constants.SCOPE_LOCAL.equalsIgnoreCase(scope)) {
if (logger.isInfoEnabled()) {
logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
}
if (registryURLs != null && !registryURLs.isEmpty()) { //@sjt 9.3
for (URL registryURL : registryURLs) {
url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_KEY));//@stj 9.4
URL monitorUrl = loadMonitor(registryURL); //@stj 9.5
if (monitorUrl != null) {
url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
}
if (logger.isInfoEnabled()) {
logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
}
// For providers, this is used to enable custom proxy to generate invoker
String proxy = url.getParameter(Constants.PROXY_KEY);
if (StringUtils.isNotEmpty(proxy)) {
registryURL = registryURL.addParameter(Constants.PROXY_KEY, proxy);
}
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));//@stj 9.6
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = protocol.export(wrapperInvoker);//@sjt 9.7
exporters.add(exporter);
}
} else {
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
}
}
}
this.urls.add(url);
}
- @sjt9: 获取
dubbo:service
标签的scope
属性,其可选值为none
(不暴露)、local
(本地)、remote
(远程),如果配置为none
,则不暴露。默认为local
。- @sjt9.1: 如果
scope
不为remote
,则先在本地暴露(injvm
),具体暴露服务的具体实现,将在remote
模式中详细分析。 - @sjt9.2: 如果
scope
不为local
,则将服务暴露在远程。 - @sjt9.3:
remote
方式,检测当前配置的所有注册中心,如果注册中心不为空,则遍历注册中心,将服务依次在不同的注册中心进行注册。 - @sjt9.4: 如果
dubbo:service
的dynamic
属性未配置, 尝试取dubbo:registry
的dynamic
属性,该属性的作用是否启用动态注册,如果设置为false
,服务注册后,其状态显示为disable
,需要人工启用,当服务不可用时,也不会自动移除,同样需要人工处理,此属性不要在生产环境上配置。 - @sjt9.5: 根据注册中心
URL
,构建监控中心的URL
,如果监控中心URL
不为空,则在服务提供者URL
上追加monitor
,其值为监控中心URL
(已编码) - @sjt9.6: 通过动态代理机制创建
Invoker
,Dubbo
的远程调用实现类。 - @sjt9.7: 根据代码6的分析,将调用
RegistryProtocol#export
方法。
- @sjt9.1: 如果