在这个版本中dubbo会通过注解@PostConstruct
把ServiceBean
实例放到ConfigManager
中
public abstract class AbstractConfig implements Serializable {
@PostConstruct
public void addIntoConfigManager() {
ApplicationModel.getConfigManager().addConfig(this);
}
......
}
DubboBootstrapApplicationListener
会监听spring容器启动发布的ContextRefreshedEvent
,遍历ConfigManager
中的ServiceBean
并调用export
方法。
public class DubboBootstrapApplicationListener extends OnceApplicationContextEventListener implements Ordered {
@Override
public void onApplicationContextEvent(ApplicationContextEvent event) {
if (DubboBootstrapStartStopListenerSpringAdapter.applicationContext == null) {
DubboBootstrapStartStopListenerSpringAdapter.applicationContext = event.getApplicationContext();
}
//spring容器启动完事件
if (event instanceof ContextRefreshedEvent) {
onContextRefreshedEvent((ContextRefreshedEvent) event);
} else if (event instanceof ContextClosedEvent) {
onContextClosedEvent((ContextClosedEvent) event);
}
}
..........省略代码
}
调用ServiceBean
的export
方法前,会初始化配置的参数,默认配置的优先级为:
JVM环境变量->操作系统环境变量->配置中心APP配置->配置中心Global配置->本地dubbo.properties文件配置中心配置->dubbo.properties文件配置。(这里还没有涉及动态配置,在服务注册后会监听动态配置,这时候动态配置优先级最高)
最后根据配置文件的优先级对ServiceBean
对中的属性进行赋值。
执行到暴露服务的代码前有较多与主流程不相关的逻辑,直接跳过。调用流程如下:
org.apache.dubbo.config.bootstrap.DubboBootstrap#exportServices
--> org.apache.dubbo.config.bootstrap.DubboBootstrap#exportService
--> org.apache.dubbo.config.ServiceConfig#export
--> org.apache.dubbo.config.ServiceConfig#doExport
--> org.apache.dubbo.config.ServiceConfig#doExportUrls
private void doExportUrls() {
.............省略部分代码
//获得注册中心地址
List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true);
int protocolConfigNum = protocols.size();
// 根据协议循环注册(dubbo,http)
for (ProtocolConfig protocolConfig : protocols) {
String pathKey = URL.buildKey(getContextPath(protocolConfig)
.map(p -> p + "/" + path)
.orElse(path), group, version);
// In case user specified path, register service one more time to map it to path.
repository.registerService(pathKey, interfaceClass);
doExportUrlsFor1Protocol(protocolConfig, registryURLs, protocolConfigNum);
}
}
doExportUrls
主要做两点
- 获取注册中心地址。
- 根据协议注册。
doExportUrlsFor1Protocol
代码比较多,我这里删减了大部分代码,只留下关键逻辑
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs, int protocolConfigNum) {
.....省略
URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);
.....省略
exportLocal(url);
.....省略
if (CollectionUtils.isNotEmpty(registryURLs)) {
Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass,
registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
exporters.add(exporter);
}
.....省略
}
- 根据拼接的map生成提供者的URL
- 进行本地暴露
- 进行远程暴露
本地暴露
private void exportLocal(URL url) {
URL local = URLBuilder.from(url)
//替换protocol替换为injvm
.setProtocol(LOCAL_PROTOCOL)
//设置host为127.0.0.1
.setHost(LOCALHOST_VALUE)
.setPort(0)
.build();
Exporter<?> exporter = PROTOCOL.export(
PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, local));
exporters.add(exporter);
}
exportLocal处理步骤为
- 替换protocol为injvm和host为127.0.0.1
-
PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, local)
生成invoker
3.PROTOCOL.export()
进行本地暴露
PROXY_FACTORY
是dubbo
的Adaptive
类,默认SPI扩展为javassist
。
private static final ProxyFactory PROXY_FACTORY = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
public class JavassistProxyFactory extends AbstractProxyFactory {
。。。。。。。。。。。。省略
@Override
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
// 范围Wrapper 的实现类
final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
return new AbstractProxyInvoker<T>(proxy, type, url) {
@Override
protected Object doInvoke(T proxy, String methodName,
Class<?>[] parameterTypes,
Object[] arguments) throws Throwable {
//这里可以理解invokeMethod调用的是我们提供者具体的业务处理类。
return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
}
};
}
}
入参proxy
是ServiceBean
中的ref,指向的是我们服务提供者的业务处理类(如demoServiceImpl
)。getInvoker
首先是动态生成Wrapper
的实现类。然后返回一个AbstractProxyInvoker
匿名内部类,
wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments)
可以理解调用的具体的服务提供者业务处理类。
调用PROTOCOL.export()进行本地服务暴露。
PROTOCOL
也是dubbo的Adaptiv
类,因为protocol
为injvm
所以最终调用的是InjvmProtocol
的export方法
private static final Protocol PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
public class InjvmProtocol extends AbstractProtocol implements Protocol{
public static final String NAME = LOCAL_PROTOCOL;
public static final int DEFAULT_PORT = 0;
private static InjvmProtocol INSTANCE;
public InjvmProtocol() {
INSTANCE = this;
}
public static InjvmProtocol getInjvmProtocol() {
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
//本地暴露serviceKey就是接口的全限定类名。如org.apache.dubbo.demo.DemoService
String serviceKey = invoker.getUrl().getServiceKey();
// 将invoker ,serviceKey 封装在 InjvmExporter
InjvmExporter<T> tInjvmExporter = new InjvmExporter<>(invoker, serviceKey, exporterMap);
// 放入exporterMap
exporterMap.addExportMap(serviceKey, tInjvmExporter);
return tInjvmExporter;
}
}
本地暴露的逻辑比较简单serviceKey
就是接口的全限定类名org.apache.dubbo.demo.DemoService
。
最后invoker被封装在InjvmExporter
,并serviceKey
为key存储在exporterMap
远程暴露
远程暴露与本地暴露一样,调用的也是JavassistProxyFactory
的getInvoker
方法。但传入的URL是注册中心+服务提供者URL
Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass,
registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
// DelegateProviderMetaDataInvoker也表示服务提供者,包括了Invoker和服务的配置
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
- .注册中心URL: registry://127.0.0.1:2181/xxxxxxxxxx
- 服务提供者URL : dubbo://192.168.2.19:20880/xxxxxxxxxx
- 调用 registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString())合并后的URL。export后面的是拼装了服务提供者URL部分。registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-demo-annotation-provider&dubbo=2.0.2&
export=dubbo%3A%2F%2F192.168.2.19%3A20880%xxxxxxxxxx
其中对应的各自SPI处理类如下
registry:// ---> RegistryProtocol
zookeeper:// ---> ZookeeperRegistry
dubbo:// ---> DubboProtocol
合并URL后先执行RegistryProtocol
的export
方法
public class RegistryProtocol implements Protocol {
..........省略
@Override
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
// registry://xxx?xx=xx®istry=zookeeper 转换为 zookeeper://xxx?xx=xx 表示注册中心
URL registryUrl = getRegistryUrl(originInvoker);
// 得到服务提供者url,表示服务提供者
URL providerUrl = getProviderUrl(originInvoker);
// overrideSubscribeUrl是老版本的动态配置监听url,表示了需要监听的服务以及监听的类型(configurators, 这是老版本上的动态配置)
// 在服务提供者url的基础上,生成一个overrideSubscribeUrl,协议为provider://,增加参数category=configurators&check=false
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
// 一个overrideSubscribeUrl对应一个OverrideListener,用来监听变化事件,监听到overrideSubscribeUrl的变化后,
// OverrideListener就会根据变化进行相应处理,具体处理逻辑看OverrideListener的实现
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
// 在这个方法里会利用providerConfigurationListener和serviceConfigurationListener去重写providerUrl
// providerConfigurationListener表示应用级别的动态配置监听器,providerConfigurationListener是RegistyProtocol的一个属性
// serviceConfigurationListener表示服务级别的动态配置监听器,serviceConfigurationListener是在每暴露一个服务时就会生成一个
// 这两个监听器都是新版本中的监听器
// 新版本监听的zk路径是:
// 服务: /dubbo/config/dubbo/org.apache.dubbo.demo.DemoService.configurators节点的内容
// 应用: /dubbo/config/dubbo/dubbo-demo-provider-application.configurators节点的内容
// 注意,要喝配置中心的路径区分开来,配置中心的路径是:
// 应用:/dubbo/config/dubbo/org.apache.dubbo.demo.DemoService/dubbo.properties节点的内容
// 全局:/dubbo/config/dubbo/dubbo.properties节点的内容
providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
// 根据动态配置重写了providerUrl之后,就会调用DubboProtocol或HttpProtocol去进行导出服务了
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);
// // 得到注册中心-ZookeeperRegistry
final Registry registry = getRegistry(originInvoker);
//得到存入到注册中心去的providerUrl,会对服务提供者url中的参数进行简化
final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl);
//是否需要注册到注册中心
boolean register = providerUrl.getParameter(REGISTER_KEY, true);
if (register) {
// 注册服务,把简化后的服务提供者url注册到registryUrl中去
registry.register(registeredProviderUrl);
}
// 针对老版本的动态配置,需要把overrideSubscribeListener绑定到overrideSubscribeUrl上去进行监听
registerStatedUrl(registryUrl, registeredProviderUrl, register);
exporter.setRegisterUrl(registeredProviderUrl);
exporter.setSubscribeUrl(overrideSubscribeUrl);
// Deprecated! Subscribe to override rules in 2.6.x or before.
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
notifyExport(exporter);
//Ensure that a new exporter instance is returned every time export
return new DestroyableExporter<>(exporter);
}
}
这是服务暴露比较重要分方法,主要步骤如下
1.获取注册中心的URL,并把 registry://xxx?xx=xx®istry=zookeeper 转换为 zookeeper://xxx?xx=xx 表示注册中心
- 得到服务提供者url,表示服务提供者。
- 构建老板本dubbo监听的URL.
overrideSubscribeUrl
是老版本的动态配置监听url,表示了需要监听的服务以及监听的类型(configurators
, 这是老版本上的动态配置) - 监听动态配置,其中动态配置分为应用动态配置和服务动态配置并重写
providerUrl
。(注意不是配置中心)
服务配置路径: /dubbo/config/dubbo/org.apache.dubbo.demo.DemoService.configurators
应用配置路径: /dubbo/config/dubbo/dubbo-demo-provider-application.configurators - 根据动态配置重写了
providerUrl
之后,就会调用DubboProtocol
或HttpProtocol
去进行导出服务了 - 得到注册中心实现-
ZookeeperRegistry
- 注册服务,把简化后的服务提供者url注册到
registryUrl
中去 - 得到存入到注册中心去的
providerUrl
,会对服务提供者url中的参数进行简化 - 针对老版本的动态配置,需要把
overrideSubscribeListener
绑定到overrideSubscribeUrl
上去进行监听
监听动态配置和重写提供者URL
private URL overrideUrlWithConfig(URL providerUrl, OverrideListener listener) {
// 应用配置,providerConfigurationListener是在属性那里直接初始化好的,providerConfigurationListener会监听配置中心的应用配置信息变动
providerUrl = providerConfigurationListener.overrideUrl(providerUrl);
// 服务配置,new ServiceConfigurationListener的时候回初始化,ServiceConfigurationListener会监听配置中心的服务信息配置信息变动
ServiceConfigurationListener serviceConfigurationListener = new ServiceConfigurationListener(providerUrl, listener);
serviceConfigurationListeners.put(providerUrl.getServiceKey(), serviceConfigurationListener);
return serviceConfigurationListener.overrideUrl(providerUrl);
}
应用动态配置由 ProviderConfigurationListener
实现,它是RegistryProtocol
的一个成员变量,在RegistryProtocol
实例化是同时也实例化ProviderConfigurationListener
,在ProviderConfigurationListener
的构造函数中调用initWith
方法获取应用动态配置(initWith在AbstractConfiguratorListener
类构造方法中调用,AbstractConfiguratorListener
也是ServiceConfigurationListener
的父类,所以服务监听实例化时也会调用这个方法获取服务动态配置)。
protected final void initWith(String key) {
// 添加Listener,进行了订阅
ruleRepository.addListener(key, this);
// 从配置中心ConfigCenter获取属于当前应用的动态配置数据,从zk中拿到原始数据(主动从配置中心获取数据)
String rawConfig = ruleRepository.getRule(key, DynamicConfiguration.DEFAULT_GROUP);
if (!StringUtils.isEmpty(rawConfig)) {
// 如果存在应用配置信息则根据配置信息生成Configurator
genConfiguratorsFromRawRule(rawConfig);
}
}
在overrideUrlWithConfig
方法中首先根据应用动态配置调用overrideUrl
方法重写提供者URL,再实例化ServiceConfigurationListener
获取服务动态配置,调用overrideUrl
方法重写提供者URL。
服务导出
在doLocalExport
方法中,会调用protocol.export()
服务导出,默认使用的是DubboProtocol
public class DubboProtocol extends AbstractProtocol {
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
URL url = invoker.getUrl();
// export service.
String key = serviceKey(url);
// 构造一个Exporter进行服务导出
DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
exporterMap.addExportMap(key, exporter);
......省略
// 开启NettyServer
openServer(url);
// 特殊的一些序列化机制,比如kryo提供了注册机制来注册类,提高序列化和反序列化的速度
optimizeSerialization(url);
return exporter;
}
}
把传入的Invoker封装成DubboExporter
放入到exporterMap
中。然后开启Netty Server。
开启netty流程不在赘述了,有兴趣可以执行阅读,流程如下。
org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#openServer
-->org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#createServer
--> org.apache.dubbo.remoting.exchange.Exchangers#bind
--> org.apache.dubbo.remoting.exchange.support.header.HeaderExchanger#bind
--> org.apache.dubbo.remoting.Transporters#bind
--> org.apache.dubbo.remoting.transport.netty.NettyTransporter#bind
--> org.apache.dubbo.remoting.transport.netty.NettyServer
--> org.apache.dubbo.remoting.transport.netty.NettyServer#doOpen
服务注册
服务注册根据不同的注册中心有不同实现,ZK实现为ZookeeperRegistry
,所以 registry.register(registeredProviderUrl)方法最终会调用到ZookeeperRegistry
的doRegister
方法。
public void doRegister(URL url) {
try {
zkClient.create(toUrlPath(url), url.getParameter(DYNAMIC_KEY, true));
} catch (Throwable e) {
throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
至此,服务导出源码解析的源码就分析完了