前言
在微服务架构中,服务消费者如何发现并调用服务提供者是核心环节之一。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接口(如ApplicationContextAware、BeanNameAware),以便在Spring环境中感知上下文信息,以及InitializingBean和DisposableBean接口,以参与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调用还是远程调用。对于远程调用,它主要做两件事:
- 根据配置(直连URL或注册中心)聚合服务提供者URL列表。
- 为这些URL创建
Invoker(调用器),并最终通过ProxyFactory将Invoker包装成动态代理。
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。
订阅逻辑分为两类:
- 订阅所有服务:当服务接口为
*时,监听注册中心根路径下的子节点变化。
- 订阅指定服务:监听指定服务接口下的分类路径(如
providers, configurators, routers)。
对于指定服务的订阅,Dubbo会为每个分类路径创建ZooKeeper子节点监听器(ChildListener)。当providers目录下的节点(即服务提供者URL)发生变化时,监听器会收到通知,并将新的提供者列表转换为URL格式,通过notify方法更新服务目录(RegistryDirectory)。RegistryDirectory内部维护着可用的Invoker列表,从而实现了服务的动态发现与更新。
集群整合与代理生成
服务目录(Directory)代表某个服务所有可用的服务提供者。cluster.join(directory)方法将目录与集群容错策略(如FailoverCluster)结合起来,生成一个ClusterInvoker。这个ClusterInvoker的invoke方法包含了负载均衡、容错重试等逻辑。
最后,proxyFactory.getProxy(invoker, ...)将这个最终的ClusterInvoker包装成目标接口的动态代理。默认使用Javassist生成代理,其InvocationHandler为InvokerInvocationHandler。
// 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
版权声明:本文由 云栈社区 整理发布,版权归原作者所有。