因为我也很多不明白的地方,所以这篇文章中很多没有说明白的地方。 我会在后面的文章中逐步弄明白的。
目前我们项目中使用Dubbo的方式都是整合Spring一起使用,在Spring启动之后再启动Dubbo. 关于dubbo整合Spring的源码我前面已经写了几篇文章。 本次为了更好的说明白dubbo的启动原理,会剥离Spring,单独的讲解dubbo的启动过程。
Dubbo源码中提供了单独启动Dubbo的demo, 我就从这里开始
public class Application {
public static void main(String[] args) throws Exception {
if (isClassic(args)) {
startWithExport();
} else {
startWithBootstrap();
}
}
private static boolean isClassic(String[] args) {
return args.length > 0 && "classic".equalsIgnoreCase(args[0]);
}
//新的启动方法, 主要讲解这里启动方式的源码
private static void startWithBootstrap() {
//封装服务提供者, ServiceConfig大家应该很熟悉
ServiceConfig<DemoServiceImpl> service = new ServiceConfig<>();
service.setInterface(DemoService.class);
service.setRef(new DemoServiceImpl());
//dubbo的一些全局配置
ApplicationConfig applicationConfig = new ApplicationConfig("dubbo-demo-api-provider");
DubboBootstrap bootstrap = DubboBootstrap.getInstance();
//设置启动的一些配置
bootstrap.application(applicationConfig)
.registry(new RegistryConfig("zookeeper://www.xiezc.xyz:8181"))
.service(service)
.start()
.await();
}
//兼容以前的启动方法,最新的已经不推荐使用这种方式
private static void startWithExport() throws InterruptedException {
ServiceConfig<DemoServiceImpl> service = new ServiceConfig<>();
service.setInterface(DemoService.class);
service.setRef(new DemoServiceImpl());
service.setApplication(new ApplicationConfig("dubbo-demo-api-provider"));
service.setRegistry(new RegistryConfig("zookeeper://www.xiezc.xyz:8181"));
service.export();
System.out.println("dubbo service started");
new CountDownLatch(1).await();
}
}
主要启动方法,方法内部先封装了服务启动者信息ServiceConfig, 在配置了应用信息ApplicationConfig, 最后将这些信息前部注册进DubboBootstrap中, DubboBootstrap是dubbo最新的启动类。 我们可以看下
bootstrap.application(applicationConfig)
.registry(new RegistryConfig("zookeeper://www.xiezc.xyz:8181"))
.service(service)
.start()
.await();
registry方法就是将注册中心信息放入configManager中
public DubboBootstrap registry(RegistryConfig registryConfig) {
configManager.addRegistry(registryConfig);
return this;
}
service方法也是提供者信息放入configManager
public DubboBootstrap service(ServiceConfig<?> serviceConfig) {
configManager.addService(serviceConfig);
return this;
}
DubboBootstrap中还有很多的方法都是将信息放入configManager中, 从他的名字知道这个应该是专门管理dubbo的配置信息的类。ConfigManager类在后面会讲解。
public DubboBootstrap start() {
if (started.compareAndSet(false, true)) {
ready.set(false);
//初始化一些信息等
initialize();
if (logger.isInfoEnabled()) {
logger.info(NAME + " is starting...");
}
// 1. export Dubbo Services
//暴露dubbo服务提供者
exportServices();
// Not only provider register
if (!isOnlyRegisterProvider() || hasExportedServices()) {
// 2. export MetadataService
exportMetadataService();
//3. Register the local ServiceInstance if required
registerServiceInstance();
}
//订阅服务
referServices();
if (asyncExportingFutures.size() > 0) {
new Thread(() -> {
try {
this.awaitFinish();
} catch (Exception e) {
logger.warn(NAME + " exportAsync occurred an exception.");
}
ready.set(true);
if (logger.isInfoEnabled()) {
logger.info(NAME + " is ready.");
}
}).start();
} else {
ready.set(true);
if (logger.isInfoEnabled()) {
logger.info(NAME + " is ready.");
}
}
if (logger.isInfoEnabled()) {
logger.info(NAME + " has started.");
}
}
return this;
}
从启动中可以看到十分重要的有四个类:
初始化中会做还多的准备工作, 是必不可少的。
private void initialize() {
if (!initialized.compareAndSet(false, true)) {
return;
}
//初始化框架的代码,这里是静态类,使用的是SPI的加载方式
ApplicationModel.initFrameworkExts();
startConfigCenter();
useRegistryAsConfigCenterIfNecessary();
loadRemoteConfigs();
checkGlobalConfigs();
initMetadataService();
initEventListener();
if (logger.isInfoEnabled()) {
logger.info(NAME + " has been initialized!");
}
}
//其实在这个静态方法中已经加载了FrameworkExt的类
private static final ExtensionLoader<FrameworkExt> LOADER = ExtensionLoader.getExtensionLoader(FrameworkExt.class);
//这里只是遍历调用下FrameworkExt的initialize方法。
public static void initFrameworkExts() {
Set<FrameworkExt> exts = ExtensionLoader.getExtensionLoader(FrameworkExt.class).getSupportedExtensionInstances();
for (FrameworkExt ext : exts) {
ext.initialize();
}
}
可以看到就是使用ExtensionLoader加载FrameworkExt的实现类,
FrameworkExt的实现类从图中可以看到三个, 其中ConfigManager应该很熟悉, 就是前面的提到的。 initFrameworkExts中会分别调用这三个实现类的的initialize方法, 可是从源码看到只有Environment实现了这个方法,其他的都是空实现。
private void startConfigCenter() {
//从配置管理器中获取配置中心的配置,
Collection<ConfigCenterConfig> configCenters = configManager.getConfigCenters();
// check Config Center
if (CollectionUtils.isEmpty(configCenters)) {
//如果没有配置中心,则生成一个默认配置中心放入配置管理器中
ConfigCenterConfig configCenterConfig = new ConfigCenterConfig();
configCenterConfig.refresh();
//这一步校验肯定通不过, 因为configCenterConfig是直接new出来的, 没有配置信息
if (configCenterConfig.isValid()) {
configManager.addConfigCenter(configCenterConfig);
configCenters = configManager.getConfigCenters();
}
} else {
//如果有配置中信息的设置,则遍历每个配置中信息,并刷新refresh配置中心
for (ConfigCenterConfig configCenterConfig : configCenters) {
configCenterConfig.refresh();
ConfigValidationUtils.validateConfigCenterConfig(configCenterConfig);
}
}
//再将每个配置中心放入一个组合的CompositeDynamicConfiguration中,之后CompositeDynamicConfiguration方法放入 environment中
if (CollectionUtils.isNotEmpty(configCenters)) {
CompositeDynamicConfiguration compositeDynamicConfiguration = new CompositeDynamicConfiguration();
for (ConfigCenterConfig configCenter : configCenters) {
compositeDynamicConfiguration.addConfiguration(prepareEnvironment(configCenter));
}
environment.setDynamicConfiguration(compositeDynamicConfiguration);
}
configManager.refreshAll();
}
这个方法主要是处理多个配置中心的情况,最后就多个配置中心整合成一个放入配置管理器中。 配置中心的configCenterConfig的 refresh方法会十分重要。
会在后面的Environment类中讲解。
从这个方法的名称意思理解,如果需要则把注册中心作为配置中心,我们看下dubbo是如何判断是否需要的
private void useRegistryAsConfigCenterIfNecessary() {
// we use the loading status of DynamicConfiguration to decide whether ConfigCenter has been initiated.
//判断上一步 是否已经组合处理好了配置中心, 如果处理好了,就直接返回了。
if (environment.getDynamicConfiguration().isPresent()) {
return;
}
//判断配置管理器中是否有配置中心,如果有就直接返回。
if (CollectionUtils.isNotEmpty(configManager.getConfigCenters())) {
return;
}
// 遍历注册中心, 并根据每个注册中心生成一个配置中心的配置
configManager.getDefaultRegistries().stream()
.filter(registryConfig -> registryConfig.getUseAsConfigCenter() == null || registryConfig.getUseAsConfigCenter())
.forEach(registryConfig -> {
String protocol = registryConfig.getProtocol();
String id = "config-center-" + protocol + "-" + registryConfig.getPort();
ConfigCenterConfig cc = new ConfigCenterConfig();
cc.setId(id);
if (cc.getParameters() == null) {
cc.setParameters(new HashMap<>());
}
if (registryConfig.getParameters() != null) {
cc.getParameters().putAll(registryConfig.getParameters());
}
cc.getParameters().put(CLIENT_KEY, registryConfig.getClient());
cc.setProtocol(registryConfig.getProtocol());
cc.setPort(registryConfig.getPort());
cc.setAddress(getRegistryCompatibleAddress(registryConfig.getAddress()));
cc.setNamespace(registryConfig.getGroup());
cc.setUsername(registryConfig.getUsername());
cc.setPassword(registryConfig.getPassword());
if (registryConfig.getTimeout() != null) {
cc.setTimeout(registryConfig.getTimeout().longValue());
}
cc.setHighestPriority(false);
configManager.addConfigCenter(cc);
});
startConfigCenter();
}
这个方法就是检查是否已经存在配置中心的信息了, 如果没有就会使用注册中心作为配置中心, 会将注册中心的很多配置信息放入ConfigCenterConfig中。并在次初始化配置中心, 这个时候至少已经有一个配置中心。
从方法名称理解是加载远程的配置, 这个demo中没有找到远程的配置
private void loadRemoteConfigs() {
// registry ids to registry configs
//
List<RegistryConfig> tmpRegistries = new ArrayList<>();
Set<String> registryIds = configManager.getRegistryIds();
registryIds.forEach(id -> {
if (tmpRegistries.stream().noneMatch(reg -> reg.getId().equals(id))) {
tmpRegistries.add(configManager.getRegistry(id).orElseGet(() -> {
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setId(id);
registryConfig.refresh();
return registryConfig;
}));
}
});
configManager.addRegistries(tmpRegistries);
// protocol ids to protocol configs
List<ProtocolConfig> tmpProtocols = new ArrayList<>();
Set<String> protocolIds = configManager.getProtocolIds();
protocolIds.forEach(id -> {
if (tmpProtocols.stream().noneMatch(prot -> prot.getId().equals(id))) {
tmpProtocols.add(configManager.getProtocol(id).orElseGet(() -> {
ProtocolConfig protocolConfig = new ProtocolConfig();
protocolConfig.setId(id);
protocolConfig.refresh();
return protocolConfig;
}));
}
});
configManager.addProtocols(tmpProtocols);
}
校验一些全局的配置是否正确,比如一些配置长度的限制,一些必须的配置是否缺失, 一些缺失的配置使用默认的配置等。 具体代码就不粘贴了。
初始化源数据信息,并加载本地元数据服务提供者类
初始化事件监听器, dubbo 有实现一套自己的事件监听机制, 具体的会在后面文章中说明。
到这里dubbo启动的初始化流程结束, 但是具体配置的加载细节还是不明白, 比如配置文件的信息如何加载到程序中, 三个核心类中 ConfigManager 和Environment 是如何作用的机制还是不太明白, 这些疑问会留在后面的文章中说明的。