核心概念
Dubbo 通过动态代理技术,为 Consumer 端的服务接口生成代理对象,将远程调用伪装成本地方法调用。这种透明化设计让开发者无需关心网络通信、序列化等底层细节,像调用本地方法一样调用远程服务。
核心技术:
- 动态代理:Javassist(默认)或 JDK Proxy
- Invoker 抽象:统一的调用接口
- 透明化封装:屏蔽网络通信细节
实现原理
1. 动态代理生成
用户代码:
// Consumer 端引用远程服务
@Reference
private UserService userService;
// 像调用本地方法一样使用
public void test() {
User user = userService.getUser(123L); // 实际是远程调用
System.out.println(user.getName());
}
底层实现:
// Spring 启动时,为 @Reference 注解的字段注入代理对象
@Reference
private UserService userService;
// ↓ 实际注入的是
// userService = ProxyFactory.getProxy(UserService.class, invoker);
// 调用 userService.getUser(123L) 时:
// 1. 拦截方法调用
// 2. 转换为 RPC 请求
// 3. 发送到远程服务器
// 4. 接收响应并返回结果
2. 代理对象创建流程
// Dubbo 代理工厂
public class ProxyFactory {
/**
* 为服务接口创建代理对象
* @param interfaceClass 服务接口
* @param invoker 调用器(封装了远程调用逻辑)
*/
public static <T> T getProxy(Class<T> interfaceClass, Invoker<T> invoker) {
// 1. 使用 Javassist 生成代理类(默认方式)
return JavassistProxyFactory.getProxy(interfaceClass, invoker);
// 或者使用 JDK 动态代理
// return JdkProxyFactory.getProxy(interfaceClass, invoker);
}
}
// Javassist 代理工厂(性能更好)
public class JavassistProxyFactory {
public <T> T getProxy(Class<T> interfaceClass, Invoker<T> invoker) {
// 动态生成代理类字节码
String proxyClassName = interfaceClass.getName() + "$Proxy";
ClassPool pool = ClassPool.getDefault();
CtClass proxyClass = pool.makeClass(proxyClassName);
// 实现接口
proxyClass.addInterface(pool.get(interfaceClass.getName()));
// 添加 invoker 字段
proxyClass.addField(CtField.make("private Invoker invoker;", proxyClass));
// 为接口的每个方法生成实现
for (Method method : interfaceClass.getMethods()) {
String methodCode = generateMethodCode(method);
proxyClass.addMethod(CtMethod.make(methodCode, proxyClass));
}
// 加载类并创建实例
Class<?> clazz = proxyClass.toClass();
T proxy = (T) clazz.newInstance();
setInvoker(proxy, invoker); // 注入 invoker
return proxy;
}
// 生成方法实现代码
private String generateMethodCode(Method method) {
return String.format(
"public %s %s(%s) {" +
" RpcInvocation invocation = new RpcInvocation();" +
" invocation.setMethodName(\"%s\");" +
" invocation.setParameterTypes($sig);" +
" invocation.setArguments($args);" +
" Result result = invoker.invoke(invocation);" +
" return (%s) result.getValue();" +
"}",
method.getReturnType().getName(),
method.getName(),
getParameterString(method),
method.getName(),
method.getReturnType().getName()
);
}
}
// 生成的代理类示例(伪代码)
public class UserService$Proxy implements UserService {
private Invoker invoker;
@Override
public User getUser(Long userId) {
// 构造 RPC 调用信息
RpcInvocation invocation = new RpcInvocation();
invocation.setMethodName("getUser");
invocation.setParameterTypes(new Class[]{Long.class});
invocation.setArguments(new Object[]{userId});
// 通过 Invoker 发起远程调用
Result result = invoker.invoke(invocation);
// 返回结果
return (User) result.getValue();
}
@Override
public void updateUser(User user) {
RpcInvocation invocation = new RpcInvocation();
invocation.setMethodName("updateUser");
invocation.setParameterTypes(new Class[]{User.class});
invocation.setArguments(new Object[]{user});
invoker.invoke(invocation);
}
}
3. Invoker 设计
Invoker 是 Dubbo 核心的统一调用接口:
/**
* Invoker 是 Dubbo 的核心抽象
* 封装了服务的调用逻辑(本地调用或远程调用)
*/
public interface Invoker<T> {
/**
* 获取服务接口
*/
Class<T> getInterface();
/**
* 执行调用
*/
Result invoke(Invocation invocation) throws RpcException;
}
// Consumer 端的 DubboInvoker(远程调用)
public class DubboInvoker<T> implements Invoker<T> {
private final Class<T> type;
private final ExchangeClient[] clients; // Netty 客户端
@Override
public Result invoke(Invocation invocation) throws RpcException {
// 1. 选择一个可用的 Client
ExchangeClient client = clients[index++ % clients.length];
// 2. 发送 RPC 请求
CompletableFuture<Result> future = client.request(invocation, timeout);
// 3. 等待响应(同步)
try {
return future.get(timeout, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
throw new RpcException("调用超时");
}
}
}
// Provider 端的 DelegateProviderMetaDataInvoker(本地调用)
public class DelegateProviderMetaDataInvoker<T> implements Invoker<T> {
private final T serviceImpl; // 真实的服务实现对象
@Override
public Result invoke(Invocation invocation) throws RpcException {
// 直接调用本地方法(反射)
Method method = getMethod(invocation.getMethodName());
Object result = method.invoke(serviceImpl, invocation.getArguments());
return new RpcResult(result);
}
}
4. 完整调用链路
// ==================== 用户代码 ====================
User user = userService.getUser(123L);
// ==================== 代理层 ====================
// userService 实际是 UserService$Proxy 对象
UserService$Proxy.getUser(123L) {
// 构造调用信息
RpcInvocation invocation = new RpcInvocation();
invocation.setMethodName("getUser");
invocation.setParameterTypes(new Class[]{Long.class});
invocation.setArguments(new Object[]{123L});
// 委托给 Invoker
Result result = invoker.invoke(invocation);
return (User) result.getValue();
}
// ==================== Invoker 层 ====================
DubboInvoker.invoke(invocation) {
// 1. 获取 Netty 客户端
ExchangeClient client = getClient();
// 2. 发送请求
Request request = new Request();
request.setData(invocation);
// 3. 异步转同步
DefaultFuture future = new DefaultFuture(channel, request);
client.send(request);
// 4. 等待响应
return future.get(timeout);
}
// ==================== 网络传输 ====================
// Netty 发送数据到 Provider
// ==================== Provider 端 ====================
DelegateProviderMetaDataInvoker.invoke(invocation) {
// 1. 获取真实的服务实现对象
UserServiceImpl serviceImpl = getServiceImpl();
// 2. 反射调用方法
Method method = UserServiceImpl.class.getMethod("getUser", Long.class);
User user = (User) method.invoke(serviceImpl, 123L);
// 3. 返回结果
return new RpcResult(user);
}
// ==================== 响应返回 ====================
// Provider 将结果序列化并通过 Netty 返回
// Consumer 接收响应,唤醒 Future,返回结果给用户代码
透明化设计细节
1. 异常透明化
// Provider 端抛出的异常,会传递到 Consumer 端
@Service
public class UserServiceImpl implements UserService {
@Override
public User getUser(Long userId) {
if (userId == null) {
throw new IllegalArgumentException("用户ID不能为空");
}
// ...
}
}
// Consumer 端捕获异常
try {
User user = userService.getUser(null);
} catch (IllegalArgumentException e) {
// 能够捕获到 Provider 端抛出的异常
System.out.println(e.getMessage()); // "用户ID不能为空"
}
// 底层实现:异常序列化传输
Response response = new Response();
response.setStatus(Response.SERVICE_ERROR);
response.setErrorMessage(exception.getMessage());
response.setException(exception); // 异常对象序列化
2. 泛型支持
// 泛型方法调用
public interface UserService {
<T> T query(Query<T> query);
}
// Consumer 端调用
Query<User> query = new Query<>();
User user = userService.query(query); // 泛型信息保留
// 底层实现:保存泛型信息
RpcInvocation invocation = new RpcInvocation();
invocation.setMethodName("query");
invocation.setParameterTypes(new Class[]{Query.class});
invocation.setArguments(new Object[]{query});
// 附加泛型信息
invocation.setAttachment("generic", "true");
invocation.setAttachment("actualType", User.class.getName());
3. 上下文传递
// Consumer 端设置上下文
RpcContext.getContext().setAttachment("traceId", "123456");
User user = userService.getUser(123L);
// Provider 端获取上下文
String traceId = RpcContext.getContext().getAttachment("traceId");
System.out.println("TraceId: " + traceId); // "123456"
// 底层实现:通过 RpcInvocation 传递
RpcInvocation invocation = new RpcInvocation();
invocation.setAttachments(RpcContext.getContext().getAttachments());
// 序列化时一起传输
性能优化
1. Javassist vs JDK Proxy
// Javassist(默认,性能更好)
// - 生成真实的字节码类
// - 直接方法调用,无反射开销
// - 性能约为 JDK Proxy 的 10 倍
// JDK Proxy(兼容性好)
// - 使用 Proxy.newProxyInstance()
// - 每次调用都通过 InvocationHandler.invoke()
// - 有反射开销
// 配置代理方式
dubbo:
provider:
proxy: javassist # 或 jdk
2. 代理缓存
// Dubbo 会缓存生成的代理类,避免重复生成
private static final Map<Class<?>, Object> PROXY_CACHE = new ConcurrentHashMap<>();
public <T> T getProxy(Class<T> interfaceClass, Invoker<T> invoker) {
Object proxy = PROXY_CACHE.get(interfaceClass);
if (proxy == null) {
proxy = createProxy(interfaceClass, invoker);
PROXY_CACHE.put(interfaceClass, proxy);
}
return (T) proxy;
}
3. 异步调用
// 同步调用(阻塞)
User user = userService.getUser(123L);
// 异步调用(非阻塞,性能更好)
CompletableFuture<User> future = RpcContext.getContext()
.asyncCall(() -> userService.getUser(123L));
future.thenAccept(user -> {
System.out.println(user.getName());
});
与其他框架对比
Spring Cloud OpenFeign
// OpenFeign 也是通过动态代理实现
@FeignClient(name = "user-service")
public interface UserService {
@GetMapping("/user/{id}")
User getUser(@PathVariable Long id);
}
// 底层使用 JDK Proxy
UserService proxy = Proxy.newProxyInstance(
classLoader,
new Class[]{UserService.class},
new FeignInvocationHandler()
);
// 区别:
// - Dubbo:基于 RPC(二进制协议)
// - Feign:基于 HTTP(REST 风格)
答题总结
Dubbo 实现本地调用体验的核心机制:
1. 动态代理技术:
- Javassist(默认):生成字节码,性能高
- JDK Proxy:基于反射,兼容性好
- 为服务接口生成代理对象,拦截方法调用
2. Invoker 抽象:
- 统一的调用接口,屏蔽本地/远程差异
- Consumer 端:
DubboInvoker→ 发起网络调用 - Provider 端:
DelegateProviderMetaDataInvoker→ 反射调用本地方法
3. 透明化设计:
- 异常透明:Provider 端异常序列化传递到 Consumer
- 泛型支持:保留泛型信息
- 上下文传递:通过
RpcContext传递附加信息
4. 完整流程:
用户调用 → 代理拦截 → 构造 RpcInvocation
→ Invoker.invoke() → 网络传输
→ Provider 接收 → 反射调用本地方法
→ 返回结果 → Consumer 接收 → 返回给用户
性能优化:
- Javassist 比 JDK Proxy 快 10 倍
- 代理类缓存,避免重复生成
- 支持异步调用,提升性能
面试技巧:强调动态代理 + Invoker 抽象是核心,可以画出调用链路图,对比 Feign 的实现差异,体现对 RPC 框架设计的深入理解。