容器启动
Container
官方文档介绍说:Dubbo
的服务容器负责启动加载运行服务。
这里的Container
指的是Dubbo
定义的接口:
package org.apache.dubbo.container;
import org.apache.dubbo.common.extension.SPI;
/**
* Container. (SPI, Singleton, ThreadSafe)
*/
@SPI("spring")
public interface Container {
/**
* start method to load the container.
*/
void start();
/**
* stop method to unload the container.
*/
void stop();
}
Container
的两个方法start()
,stop()
比较好理解,@SPI("spring")
这段代码是什么意思?
Dubbo
是一款Java
开源框架,它的设计理念就是:微核插件,作者把自己也当作扩展者,这样做保证了框架的可持续性、稳定性,实现了对第三方插件一视同仁。
SPI
的全称为Service Provider Interface
,这里的SPI
是Dubbo
自己实现的一套插件扩展机制,和Java SPI
大致相同,我们先来了解下Java SPI
:
package com.kaiyuan.qyd.topic.dubbo.spi;
// 定义四季接口
public interface FourSeasonsSpiService {
void print();
}
public class AutumnSpiService implements FourSeasonsSpiService {
public void print() {System.out.println("秋");}
}
// 另外三个省略...
public static void main(String[] args) {
// 从META-INF/services/中寻找同包名文件获取接口实现
ServiceLoader<FourSeasonsSpiService> load = ServiceLoader.load(FourSeasonsSpiService.class);
load.forEach(it->it.print());// 按照文件顺序打印 春夏秋冬
}
SPI定义文件描述:
// 文件路径:META-INF/services/com.kaiyuan.qyd.topic.dubbo.spi.FourSeasonsSpiService
com.kaiyuan.qyd.topic.dubbo.spi.SpringSpiService
com.kaiyuan.qyd.topic.dubbo.spi.SummerSpiService
com.kaiyuan.qyd.topic.dubbo.spi.AutumnSpiService
com.kaiyuan.qyd.topic.dubbo.spi.WinterSpiService
看完上面的示例我们对Java SPI
的打开方式做个小结:
1.创建业务接口以及方法
2.在META-INF/services/
下创立定义文件,名称为包路径全名
3.在定义文件中按需写入该接口实现类
4.使用ServiceLoader.load()
获取接口列表
5.按需调用接口方法
6.用户要扩展只需要写实现类并修改定义文件即可
Dubbo SPI
在Java SPI
思想上重新创造扩展了注解功能,允许使用key=>value
的形势按需选择接口实现类,@SPI("spring")
的意思是使用SpringContainer
作为默认Container
实现类(本章只需要了解,后面有专题介绍)
// 文件路径: META-INF/dubbo/internal/org.apache.dubbo.container.Container
spring=org.apache.dubbo.container.spring.SpringContainer
log4j=org.apache.dubbo.container.log4j.Log4jContainer
logback=org.apache.dubbo.container.logback.LogbackContainer
SpringContainer
的代码很简单,实现父类start()/stop()
之后启动/关闭Spring
容器。现在的问题是:
SpringContainer在哪启动?
这个问题很好找,因为Container
的下铺就是Main
,这俩类都在org.apache.dubbo.container
包目录结构里
Main
这个类就是SpringContainer
的调用者,代码逻辑如下
1.获取Container
默认实现:通过ExtensionLoader.getExtensionLoader()
2.一些参数验证,因为允许应用程序通过参数控制多容器
3.创建SHUTDOWN_HOOK
停机逻辑,内部调用了Container.stop()
4.依次执行Container.start()
启动容器
5.静态全局锁等待,直到Condition#STOP
停机信号被触发(可能永远等待)
package org.apache.dubbo.container;
import org.apache.dubbo.common.extension.ExtensionLoader;
import ...
/* 代码有修剪 */
public class Main {
// SPI获取Container.class的默认实现,这里是SpringContainer实现类
private static final ExtensionLoader<Container> loader = ExtensionLoader.getExtensionLoader(Container.class);
private static final ReentrantLock LOCK = new ReentrantLock();
// 条件锁,唤醒受代码控制
private static final Condition STOP = LOCK.newCondition();
public static void main(String[] args) {
try {
// 省略一些参数验证,因为dubbo允许应用程序通过参数控制多容器
final List<Container> containers = new ArrayList<Container>();
for (int i = 0; i < args.length; i++) {
// 默认只有一个容器SpringContainer
containers.add(loader.getExtension(args[i]));
}
logger.info("Use container type(" + Arrays.toString(args) + ") to run dubbo serivce.");
if ("true".equals(System.getProperty(SHUTDOWN_HOOK_KEY))) {
// SHUTDOWN_HOOK停机代码
}
for (Container container : containers) {
container.start();// 依次执行start()
logger.info("Dubbo " + container.getClass().getSimpleName() + " started!");
}
} catch (RuntimeException e) {
logger.error(e.getMessage(), e);
System.exit(1);
}
try {
LOCK.lock(); // 静态可重入锁
STOP.await();// 挂起等待停机信号,当前释放lock锁
} catch (InterruptedException e) {
logger.warn("Dubbo service server stopped, interrupted by other thread!", e);
} finally {
LOCK.unlock();
}
}
}
SHUTDOWN_HOOK:
这段代码说明如果SHUTDOWN_HOOK_KEY
为false
那么Condition#STOP
永远不会触发,也就说明Main()
永远等待。
if ("true".equals(System.getProperty(SHUTDOWN_HOOK_KEY))) {
// 监听Shutdown事件
Runtime.getRuntime().addShutdownHook(new Thread("dubbo-container-shutdown-hook") {
@Override
public void run() {
for (Container container : containers) {
try {
container.stop();
logger.info("Dubbo " + container.getClass().getSimpleName() + " stopped!");
} catch (Throwable t) {
logger.error(t.getMessage(), t);
}
try {
LOCK.lock(); // 可重入锁
STOP.signal(); // 等container.stop()执行完毕触发停机信号
} finally {
LOCK.unlock();
}
}
}
});
}
小结:
以上是Dubbo
的容器启动代码分析,但是我们完全可以自己从Spring
启动Dubbo
程序,无需依赖jar包中的Main()
。