摘要:
本文从提供者小例子入手,剖析ServiceConfig如何导出服务使得接口可以被引用,最终是通过PROTOCOL接口进行导出的。
先从提供者小例子入手
接口和实现类:
public class DemoServiceImpl implements DemoService {
private static final Logger logger = LoggerFactory.getLogger(DemoServiceImpl.class);
@Override
public String sayHello(String name) {
logger.info("Hello " + name + ", request from consumer: " + RpcContext.getContext().getRemoteAddress());
return "Hello " + name + ", response from provider: " + RpcContext.getContext().getLocalAddress();
}
@Override
public CompletableFuture<String> sayHelloAsync(String name) {
return null;
}
}
主程序入口:
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 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://127.0.0.1:2181"));
// 最重要的入口,将服务暴露出去
service.export();
System.out.println("dubbo service started");
// 卡住主进程
new CountDownLatch(1).await();
}
}
开始发散思维的猜想
DemoServiceImpl 这个类只是简单实现,没什么内容。
Application中startWithBootstrap()和startWithExport(),2个只是写法不同,意思一样而已。
因此主要看看这个方法startWithExport(),ServiceConfig 看起来应该就是一些配置信息的设置,把DemoService接口和DemoServiceImpl实现类设置一下。
看来重点就是这个了: service.export(); 然后跟进去。
ServiceConfig类
public synchronized void export() {
// 是否需要导出服务
if (!shouldExport()) {
return;
}
if (bootstrap == null) {
bootstrap = DubboBootstrap.getInstance();
bootstrap.initialize();
}
checkAndUpdateSubConfigs();
//init serviceMetadata
// 初始化服务配置元数据
serviceMetadata.setVersion(getVersion());
serviceMetadata.setGroup(getGroup());
serviceMetadata.setDefaultGroup(getGroup());
serviceMetadata.setServiceType(getInterfaceClass());
serviceMetadata.setServiceInterfaceName(getInterface());
serviceMetadata.setTarget(getRef());
// 是否延迟发布
if (shouldDelay()) {
DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
} else {
// 直接发布
doExport();
}
exported();
}
这个方法前面一堆都是在继续初始化内部的配置元数据,重点应该是doExport()方法。是否延迟发布只是加入了调度线程而已,最终还是执行doExport()。所以我们继续深入探究。
protected synchronized void doExport() {
//......
// 服务导出
doExportUrls();
}
继续忽略前面的判断,这里的重点就是doExportUrls()。继续跟进。
private void doExportUrls() {
// ......
for (ProtocolConfig protocolConfig : protocols) {
//......
// 使用指定协议导出服务
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
上面的ServiceRepository服务仓库配置,看起来集中对服务进行管理的组件。最后在遍历协议中,将服务按照指定协议进行暴露。继续跟进doExportUrlsFor1Protocol()方法。
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
String name = protocolConfig.getName();
if (StringUtils.isEmpty(name)) {
name = DUBBO;
}
// 使用map将所有的参数记录起来
Map<String, String> map = new HashMap<String, String>();
map.put(SIDE_KEY, PROVIDER_SIDE);
//......
// 遍历并解析方法的配置,记录到map
//......
// 如果为泛型调用,设置泛型类型
if (ProtocolUtils.isGeneric(generic)) {
map.put(GENERIC_KEY, generic);
map.put(METHODS_KEY, ANY_VALUE);
}
// 正常调用设置拼接url的参数
else {
String revision = Version.getVersion(interfaceClass, version);
if (revision != null && revision.length() > 0) {
map.put(REVISION_KEY, revision);
}
String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
if (methods.length == 0) {
logger.warn("No method found in service interface " + interfaceClass.getName());
map.put(METHODS_KEY, ANY_VALUE);
} else {
map.put(METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
}
}
//......
// 拼接url对象
// export service
String host = findConfigedHosts(protocolConfig, registryURLs, map);
Integer port = findConfigedPorts(protocolConfig, name, map);
URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);
// You can customize Configurator to append extra parameters
if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.hasExtension(url.getProtocol())) {
url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.getExtension(url.getProtocol()).getConfigurator(url).configure(url);
}
// scope判断导出服务的形式
String scope = url.getParameter(SCOPE_KEY);
// don't export when none is configured
// 如果scope为SCOPE_NONE则不导出服务
if (!SCOPE_NONE.equalsIgnoreCase(scope)) {
// export to local if the config is not remote (export to remote only when config is remote)
// 如果scope不是SCOPE_REMOTE,则导出本地服务
if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
exportLocal(url);
}
// export to remote if the config is not local (export to local only when config is local)
// 如果scope不是SCOPE_LOCAL,则导出远程服务
if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
// 如果有服务注册中心地址
if (CollectionUtils.isNotEmpty(registryURLs)) {
for (URL registryURL : registryURLs) {
//......
// For providers, this is used to enable custom proxy to generate invoker
String proxy = url.getParameter(PROXY_KEY);
if (StringUtils.isNotEmpty(proxy)) {
registryURL = registryURL.addParameter(PROXY_KEY, proxy);
}
// 生成动态代理对象invoker
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);
}
}
// 没有注册中心则是直连方式
else {
if (logger.isInfoEnabled()) {
logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
}
Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
exporters.add(exporter);
}
}
}
this.urls.add(url);
}
这个方法巨长,后面的分为几篇来讲解。
这里我们大概知道这个方法,其实就是先将方法级别的配置解析并存储到map,然后拼凑成有规则的url,根据url进行服务导出,导出的时候看看是导出成本地还是远程服务。
这篇先大概知道个情况,后面我们接着分析。