文章目录
1. Dubbo服务导出概述
前面已经解读过Dubbo SPI相关的源码见:一篇短文就能搞定Dubbo SPI 源码及示例。本文主要研究一下 Dubbo 导出服务的过程。Dubbo 服务导出过程始于 Spring 容器发布刷新事件,Dubbo 在接收到事件后,会立即执行服务导出逻辑。整个逻辑大致可分为三个部分,第一部分是前置工作,主要用于检查参数,组装 URL。第二部分是导出服务,包含导出服务到本地 (JVM),和导出服务到远程两个过程。第三部分是向注册中心注册服务,用于服务发现。本文将基于dubbo-2.7.7
源码对这三个部分代码进行详细的分析。
2. Dubbo服务导出源码解读
2.1 服务导出入口
在2.7.5之前服务导出的入口方法是 ServiceBean 的 onApplicationEvent。onApplicationEvent 是一个事件响应方法,该方法会在收到 Spring 上下文刷新事件后执行服务导出操作。但是在2.7.5开始新增了DubboBootstrapApplicationListener,该类继承了OneTimeExecutionApplicationContextEventListener抽象类,OneTimeExecutionApplicationContextEventListener又实现了ApplicationListener接口onApplicationEvent方法,同之前版本一样,该方法会在收到 Spring 上下文刷新事件后执行。两个类的关键源码如下:
abstract class OneTimeExecutionApplicationContextEventListener implements ApplicationListener, ApplicationContextAware {
private ApplicationContext applicationContext;
public final void onApplicationEvent(ApplicationEvent event) {
//判断事件源是持有的ApplicationContext并且是应用上下文事件
if (isOriginalEventSource(event) && event instanceof ApplicationContextEvent) {
onApplicationContextEvent((ApplicationContextEvent) event);
}
}
/**
* The subclass overrides this method to handle {@link ApplicationContextEvent}
*
* @param event {@link ApplicationContextEvent}
*/
protected abstract void onApplicationContextEvent(ApplicationContextEvent event);
//省略部分源码
}
public class DubboBootstrapApplicationListener extends OneTimeExecutionApplicationContextEventListener
implements Ordered {
/**
* The bean name of {@link DubboBootstrapApplicationListener}
*/
public static final String BEAN_NAME = "dubboBootstrapApplicationListener";
private final DubboBootstrap dubboBootstrap;
public DubboBootstrapApplicationListener() {
this.dubboBootstrap = DubboBootstrap.getInstance();
}
@Override
public void onApplicationContextEvent(ApplicationContextEvent event) {
if (event instanceof ContextRefreshedEvent) {
//是上下文刷新事件则调用DubboBootstrap的start方法
onContextRefreshedEvent((ContextRefreshedEvent) event);
} else if (event instanceof ContextClosedEvent) {
//如果是上下文关闭事件则调用DubboBootstrap的stop方法
onContextClosedEvent((ContextClosedEvent) event);
}
}
private void onContextRefreshedEvent(ContextRefreshedEvent event) {
dubboBootstrap.start();
}
private void onContextClosedEvent(ContextClosedEvent event) {
dubboBootstrap.stop();
}
@Override
public int getOrder() {
return LOWEST_PRECEDENCE;
}
}
2.2 DubboBootstrap#start
根据上面的源码可知,在DubboBootstrapApplicationListener 的构造函数中会先去获取DubboBootstrap 的实例,在监听到ContextRefreshedEvent事件时触发DubboBootstrap 的start方法,因此接着我们看看DubboBootstrap 的实例化及开始方法的源码。
DubboBootstrap 实例化
public class DubboBootstrap extends GenericEventListener {
private static DubboBootstrap instance;
private final ConfigManager configManager;
private final Environment environment;
/**
* 加锁构造单例
* See {@link ApplicationModel} and {@link ExtensionLoader} for why DubboBootstrap is designed to be singleton.
*/
public static synchronized DubboBootstrap getInstance() {
if (instance == null) {
instance = new DubboBootstrap();
}
return instance;
}
//私有化构造函数,保证只能被自己实例化
private DubboBootstrap() {
//通过SPI方式获取环境配置和环境管理实例,继承关系如下图
configManager = ApplicationModel.getConfigManager();
environment = ApplicationModel.getEnvironment();
//注册shutdown事件,回调DubboBootstrap的destroy方法,销毁所有导出及引用的服务等
DubboShutdownHook.getDubboShutdownHook().register();
ShutdownHookCallbacks.INSTANCE.addCallback(new ShutdownHookCallback() {
@Override
public void callback() throws Throwable {
DubboBootstrap.this.destroy();
}
});
}
org.apache.dubbo.config.bootstrap.DubboBootstrap.start()
/**
* Start the bootstrap
*/
public DubboBootstrap start() {
//原子操作,保证只启动一次
if (started.compareAndSet(false, true)) {
//初始化操作
initialize();
if (logger.isInfoEnabled()) {
logger.info(NAME + " is starting...");
}
// 导出dubbo服务(最终调用ServiceConfig的export方法)
exportServices();
// 不仅仅是注册服务提供者或者已经导出元数据
if (!isOnlyRegisterProvider() || hasExportedServices()) {
// 导出元数据服务(最终构建了ServiceConfig实例,然后调用export方法)
exportMetadataService();
// 如果有则需要注册本地服务实例,通过SPI方式获取服务发现注册中心,然后调用他们的注册方法(默认自适应拓展实现是zookeeper)
registerServiceInstance();
}
//执行服务引入
referServices();
if (logger.isInfoEnabled()) {
logger.info(NAME + " has started.");
}
}
return this;
}
2.3 DubboBootstrap#start内部
根据上面start方法源码可以看到,里面一次执行了服务初始化以服务导出和引入的方法,接下来我们追踪下个方法的具体实现。
org.apache.dubbo.config.bootstrap.DubboBootstrap.initialize()
/**
* Initialize
*/
private void initialize() {
// 原子操作确保只初始化一次
if (!initialized.compareAndSet(false, true)) {
return;
}
//初始化Dubbo组件的生命周期,这里主要是对环境配置初始化
ApplicationModel.iniFrameworkExts();
//开始构建配置中心
startConfigCenter();
//如果是zookeeper作为注册中心且没有指定配置中心时,使用注册中心做配置中心
useRegistryAsConfigCenterIfNecessary();
//加载协议ID到协议配置中并加载注册id到注册中心配置
loadRemoteConfigs();
//全局配置校验(应用、元数据、提供者、消费者、监控等)
checkGlobalConfigs();
//初始化元数据服务
initMetadataService();
//初始化事件监听器(将当前实例添加到事件监听器中)
initEventListener();
if (logger.isInfoEnabled()) {
logger.info(NAME + " has been initialized!");
}
}
org.apache.dubbo.config.bootstrap.DubboBootstrap.exportServices()
private void exportServices() {
configManager.getServices().forEach(sc -> {
//设置ServiceConfig的启动器为当前实例
ServiceConfig serviceConfig = (ServiceConfig) sc;
serviceConfig.setBootstrap(this);
if (exportAsync) {
//异步导出,将导出任务提交到线程池异步完成
ExecutorService executor = executorRepository.getServiceExporterExecutor();
Future<?> future = executor.submit(() -> {
sc.export();
});
asyncExportingFutures.add(future);
} else {
//同步导出 则直接调用ServiceConfig的导出方法
sc.export();