找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

3229

积分

0

好友

428

主题
发表于 2026-2-10 18:45:58 | 查看: 30| 回复: 0

前言

在微服务架构中,服务消费者如何发现并调用服务提供者是核心环节之一。Apache Dubbo作为一款高性能的Java RPC框架,其服务引用流程设计精巧,与Spring容器深度集成,为用户提供了近乎透明的远程调用体验。本文将深入源码,解析Dubbo服务引用的完整流程,理解@DubboReference标注的属性如何一步步转化为一个具备服务发现、集群容错能力的动态代理对象。

整体架构与入口

在Spring启动过程中,Dubbo通过其扩展点与Spring的依赖注入机制协同工作。当容器初始化单例Bean时,所有被@DubboReference注解标注的属性,其值的注入并非一个简单的Bean,而是一个由Dubbo生成的代理对象。这个代理对象是后续所有Dubbo远程调用的总入口。

ReferenceBean与Spring Bean生命周期

ReferenceBean是实现Dubbo服务与Spring整合的关键类,它是一个FactoryBean。当Spring向@DubboReference属性注入值时,实际会调用ReferenceBean.getObject()方法来获取真正的对象。

为了深入理解其与Spring容器的关系,我们可以看下它的继承体系。ReferenceBean本身实现了FactoryBean<T>接口,这意味着它负责生产目标Bean的实例。同时,它还继承了ReferenceConfig<T>,这是Dubbo服务引用的核心配置类。此外,它实现了多个Spring的Aware接口(如ApplicationContextAwareBeanNameAware),以便在Spring环境中感知上下文信息,以及InitializingBeanDisposableBean接口,以参与Spring Bean的生命周期管理。

这个设计使得ReferenceBean既能作为Spring Bean被管理,又能承载Dubbo服务引用的所有配置与逻辑。

getObject():代理对象的诞生

getObject()方法是获取代理对象的入口。其核心逻辑是创建一个支持懒加载的代理。

public T getObject() {
    if (lazyProxy == null) {
        // 创建代理
        createLazyProxy();
    }
    return (T) lazyProxy;
}

// 创建懒加载代理
private void createLazyProxy() {
    ProxyFactory proxyFactory = new ProxyFactory();
    // 实现一个懒初始化Source,这里会调用ReferenceConfig.get方法,实现服务引用客户端的创建
    proxyFactory.setTargetSource(new DubboReferenceLazyInitTargetSource());
    proxyFactory.addInterface(interfaceClass);
    Class<?>[] internalInterfaces = AbstractProxyFactory.getInternalInterfaces();
    for (Class<?> anInterface : internalInterfaces) {
        proxyFactory.addInterface(anInterface);
    }
    if (!StringUtils.isEquals(interfaceClass.getName(), interfaceName)) {
        //add service interface
        try {
            Class<?> serviceInterface = ClassUtils.forName(interfaceName, beanClassLoader);
            proxyFactory.addInterface(serviceInterface);
        } catch (ClassNotFoundException e) {
            // generic call maybe without service interface class locally
        }
    }

    this.lazyProxy = proxyFactory.getProxy(this.beanClassLoader);
}

这里的DubboReferenceLazyInitTargetSource是一个懒加载目标源,它内部封装了真正的服务引用创建逻辑。其createObject()方法最终会调用referenceConfig.get()

private Object getCallProxy() throws Exception {
    if (referenceConfig == null) {
        throw new IllegalStateException("ReferenceBean is not ready yet, please make sure to call reference interface method after dubbo is started.");
    }
    //get reference proxy
    return referenceConfig.get();
}

private class DubboReferenceLazyInitTargetSource extends AbstractLazyCreationTargetSource {
    @Override
    protected Object createObject() throws Exception {
        return getCallProxy();
    }
    @Override
    public synchronized Class<?> getTargetClass() {
        return getInterfaceClass();
    }
}

当配置了lazy=true时,代理对象的真实创建(即服务发现、连接建立)会延迟到第一次方法调用时。而对于非懒加载的引用,真正的初始化由DubboDeployApplicationListener监听Spring应用事件触发,其核心是调用DefaultModuleDeployer.referServices()

服务引用核心流程

referServices()方法中,Dubbo遍历所有ReferenceConfig,并调用其get()方法。为了优化性能,这里引入了引用缓存ReferenceCache

public <T> T get(ReferenceConfigBase<T> rc) {
    String key = generator.generateKey(rc);
    Class<?> type = rc.getInterfaceClass();
    Object proxy = rc.get();

    references.computeIfAbsent(rc, _rc -> {
        List<ReferenceConfigBase<?>> referencesOfType = referenceTypeMap.computeIfAbsent(type, _t -> Collections.synchronizedList(new ArrayList<>()));
        referencesOfType.add(rc);
        List<ReferenceConfigBase<?>> referenceConfigList = referenceKeyMap.computeIfAbsent(key, _k -> Collections.synchronizedList(new ArrayList<>()));
        referenceConfigList.add(rc);
        return proxy;
    });

    return (T) proxy;
}

最终,流程汇聚到ReferenceConfig.get()和其内部的init()方法。init()方法完成了大量的准备工作:设置所属模块、刷新配置、初始化元数据、注册消费者模型等。其最核心的一步是调用createProxy(Map<String, String> referenceParameters)

创建代理(createProxy)

createProxy方法根据配置决定是进行本地JVM调用还是远程调用。对于远程调用,它主要做两件事:

  1. 根据配置(直连URL或注册中心)聚合服务提供者URL列表。
  2. 为这些URL创建Invoker(调用器),并最终通过ProxyFactoryInvoker包装成动态代理。
private T createProxy(Map<String, String> referenceParameters) {
    // 是否为本地调用 同一个jvm
    if (shouldJvmRefer(referenceParameters)) {
        createInvokerForLocal(referenceParameters);
    } else {
        urls.clear();
        if (url != null && url.length() > 0) {
            // user specified URL, could be peer-to-peer address, or register center's address.
            // 解析配置的直连URL?
            parseUrl(referenceParameters);
        } else {
            // if protocols not in jvm checkRegistry
            if (!LOCAL_PROTOCOL.equalsIgnoreCase(getProtocol())) {
                aggregateUrlFromRegistry(referenceParameters);
            }
        }
        //     创建invoker
        createInvokerForRemote();
    }

    if (logger.isInfoEnabled()) {
        logger.info("Referred dubbo service " + interfaceClass.getName());
    }

    URL consumerUrl = new ServiceConfigURL(CONSUMER_PROTOCOL, referenceParameters.get(REGISTER_IP_KEY), 0,
        referenceParameters.get(INTERFACE_KEY), referenceParameters);
    consumerUrl = consumerUrl.setScopeModel(getScopeModel());
    consumerUrl = consumerUrl.setServiceModel(consumerModel);
    MetadataUtils.publishServiceDefinition(consumerUrl);

    // create service proxy
    return (T) proxyFactory.getProxy(invoker, ProtocolUtils.isGeneric(generic));
}

构建远程调用器(createInvokerForRemote)

createInvokerForRemote()中,Dubbo根据获取到的URL数量(单个或多个注册中心)进行不同的处理。核心是调用protocolSPI.refer(interfaceClass, url)。这里的protocolSPI是一个经过Wrapper包装的代理,默认是RegistryProtocol

以单个注册中心地址为例:

registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-demo-annotation-consumer&dubbo=2.0.2&pid=20168®istry=zookeeper×tamp=1767441310587

RegistryProtocol.refer()方法会解析出真正的注册中心地址(如ZooKeeper),获取Registry实例(如ZookeeperRegistry),然后根据集群配置调用doRefer方法。

doRefer方法会构造一个包含消费方信息的URL,例如调试信息中显示的消费URL:

zookeeper://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-demo-annotation-consumer&dubbo=2.0.2×tamp=1767441431935

这个URL会被设置到原始的注册URL属性中,并用于后续创建MigrationInvoker(用于处理服务迁移场景)。最终,通过调用interceptInvoker触发注册中心监听器,完成服务目录(Directory)的订阅。

订阅服务提供者

RegistryProtocol.doCreateInvoker中,关键的步骤是directory.subscribe(toSubscribeUrl(urlToRegistry))。这会调用到ZookeeperRegistry.doSubscribe

订阅逻辑分为两类:

  1. 订阅所有服务:当服务接口为*时,监听注册中心根路径下的子节点变化。
  2. 订阅指定服务:监听指定服务接口下的分类路径(如providersconfiguratorsrouters)。

对于指定服务的订阅,Dubbo会为每个分类路径创建ZooKeeper子节点监听器(ChildListener)。当providers目录下的节点(即服务提供者URL)发生变化时,监听器会收到通知,并将新的提供者列表转换为URL格式,通过notify方法更新服务目录(RegistryDirectory)。RegistryDirectory内部维护着可用的Invoker列表,从而实现了服务的动态发现与更新。

集群整合与代理生成

服务目录(Directory)代表某个服务所有可用的服务提供者。cluster.join(directory)方法将目录与集群容错策略(如FailoverCluster)结合起来,生成一个ClusterInvoker。这个ClusterInvokerinvoke方法包含了负载均衡、容错重试等逻辑。

最后,proxyFactory.getProxy(invoker, ...)将这个最终的ClusterInvoker包装成目标接口的动态代理。默认使用Javassist生成代理,其InvocationHandlerInvokerInvocationHandler

// JavassistProxyFactory.java
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
    // 生成代理类(Javassist动态生成),InvocationHandler为InvokerInvocationHandler
    return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}

// InvokerInvocationHandler.java
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (method.getDeclaringClass() == Object.class) {
        return method.invoke(invoker, args);
    }
    // 构建RPC调用上下文,转发给Invoker.invoke()
    RpcInvocation invocation = new RpcInvocation(method, args);
    invocation.setInvoker(invoker);
    return invoker.invoke(invocation).recreate();
}

当业务代码调用代理对象的方法时,请求被InvokerInvocationHandler拦截,它封装一个RpcInvocation,然后调用clusterInvoker.invoke(),从而进入Dubbo的远程调用链,经过过滤器、路由、负载均衡,最终通过网络发送到服务提供者。

总结

Dubbo的服务引用流程是一个将声明式配置(@DubboReference)转化为具备完整RPC能力的动态代理的复杂过程。它深度整合Spring生命周期,在应用启动或首次调用时,完成从注册中心发现服务、建立服务目录、集成集群容错策略到生成最终代理对象的一系列动作。整个流程充分体现了Dubbo开源实战中面向扩展的设计,各模块(Protocol, Cluster, Directory, Registry)职责清晰,通过SPI机制灵活组装,共同支撑了高效、稳定的服务调用。

参考资料

[1] 【Dubbo】服务引用流程源码分析, 微信公众号:mp.weixin.qq.com/s/TYVCynC07Dd0EX1MwMMLxw

版权声明:本文由 云栈社区 整理发布,版权归原作者所有。




上一篇:AI Agent系统设计实战:从Prompt工程到多Agent协同的工业级落地指南
下一篇:藏在电路板里的美学:丰田、华为、荣耀等产品拆解揭秘
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-2-23 12:57 , Processed in 0.427942 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表