从一个Dubbo Provider小例子入门

本文通过一个Dubbo提供者的小例子,深入解析ServiceConfig类如何导出服务。从接口和实现类开始,探讨ServiceConfig的角色,重点在于service.export()方法及后续的doExport()和doExportUrlsFor1Protocol()方法,这些方法涉及到服务的初始化、配置管理和导出过程。
摘要由CSDN通过智能技术生成

摘要:
本文从提供者小例子入手,剖析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进行服务导出,导出的时候看看是导出成本地还是远程服务。

这篇先大概知道个情况,后面我们接着分析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Gemini技术窝

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值