第 10 篇 · 共100篇|用代码丈量成长 —— 坚持写下去,就是最好的成长。
你有没有被问过 Feign 的底层原理?
如果你也曾想过它到底是怎么把一个“接口”变成真正的 HTTP 调用,其实可以把它拆开来看:
- 我们只需要定义一个接口,写好请求方式和参数,Feign 会自动生成对应的 HTTP 请求。
- 接口本身没有实现类,Feign 会在运行时通过 动态代理 给你生成实现,所以你只管调用。
- 使用上看起来就像调用本地方法一样,完全不需要关心 HTTP 细节。
- 如果配置了 Fallback,当远程服务异常时,会自动走降级逻辑。
理解到这里,其实已经发现了:Feign 的核心就是一层非常巧妙的代理。
迷你版 Feign 功能说明
今天,我们就一起实现一个“迷你版 Feign”。虽然是简化版本,但核心能力可不少:
- 支持多次重试
- 重试失败后触发 callback 兜底
- 通过代理包装真实服务,模拟远程调用
这些能力能够成立,靠的都是同一个武器——代理模式。借助代理,我们就能实现“狸猫换太子”:看起来是调用本地方法,实际背后却是一次远程调用。
接下来直接上代码,一步步把原理拆给你看。
定义服务接口
/**
* 模拟远程服务接口(类似 Feign 接口)
*/
interface RemoteService {
/**
* 根据订单ID获取订单信息
*/
String getOrder(String orderId);
}
真实实现类(本地模拟远程调用,有一定几率失败)
/**
* 真实实现类(模拟远程调用,有一定几率失败)
*/
class RemoteServiceImpl implements RemoteService {
@Override
public String getOrder(String orderId) {
// 模拟“远程调用不太稳定”:70% 概率抛异常
if (Math.random() < 0.7) {
System.out.println("[RemoteServiceImpl] 调用远程服务失败,orderId = " + orderId);
throw new RuntimeException("远程服务异常");
}
System.out.println("[RemoteServiceImpl] 远程服务成功,orderId = " + orderId);
return "【真实订单信息】orderId=" + orderId;
}
}
定义兜底策略
/**
* 兜底回调接口:所有重试失败后,由它来决定返回什么
*/
@FunctionalInterface
interface FallbackCallback {
/**
* 所有重试都失败后,执行兜底逻辑
*
* @param method 调用的方法
* @param args 调用参数
* @param e 最后一次异常
* @return 兜底返回结果
*/
Object onFallback(Method method, Object[] args, Throwable e);
}
/**
* :
* 不区分方法、参数,统一返回一个默认提示
*/
class SimpleFallbackCallback implements FallbackCallback {
@Override
public Object onFallback(Method method, Object[] args, Throwable e) {
System.out.println("[SimpleFallbackCallback] 触发兜底逻辑");
System.out.println("[SimpleFallbackCallback] 方法: " + method.getName());
System.out.println("[SimpleFallbackCallback] 参数: " + Arrays.toString(args));
System.out.println("[SimpleFallbackCallback] 原因: " + e.getMessage());
// 统一固定的兜底输出,不做任何业务区分
return "【Fallback 默认返回】系统繁忙,请稍后再试";
}
}
动态代理:为任意接口增加【重试 + Callback 兜底】能力
public class RetryWithCallbackProxy implements InvocationHandler {
/**
* 被代理的真实对象
*/
private final Object target;
/**
* 重试次数
*/
private final int retryTimes;
/**
* 兜底回调
*/
private final FallbackCallback callback;
public RetryWithCallbackProxy(Object target, int retryTimes, FallbackCallback callback) {
this.target = target;
this.retryTimes = retryTimes;
this.callback = callback;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 对 toString、equals 等 Object 自带方法,直接透传,不做重试
if (method.getDeclaringClass() == Object.class) {
return method.invoke(target, args);
}
int remain = retryTimes;
Throwable lastEx = null;
while (remain-- > 0) {
try {
System.out.println("[Proxy] 开始调用,method=" + method.getName()
+ ", 剩余重试次数=" + remain);
return method.invoke(target, args);
} catch (InvocationTargetException e) {
// 真实业务异常
lastEx = e.getTargetException();
System.out.println("[Proxy] 调用失败: " + lastEx.getMessage());
if (remain <= 0) {
System.out.println("[Proxy] 重试结束,准备走兜底逻辑");
return callback.onFallback(method, args, lastEx);
}
} catch (Exception e) {
// 其他异常
lastEx = e;
System.out.println("[Proxy] 调用失败: " + lastEx.getMessage());
if (remain <= 0) {
System.out.println("[Proxy] 重试结束,准备走兜底逻辑");
return callback.onFallback(method, args, lastEx);
}
}
}
// 理论上不会走到这里,多一层保险
return callback.onFallback(method, args, lastEx);
}
}
模拟 Spring 容器
public class ServiceContainer {
/**
* 模拟 Spring 的 Bean 容器
*/
private static final Map<Class<?>, Object> SERVICE_MAP = new ConcurrentHashMap<>();
/**
* 注册对象
*/
public static <T> void register(Class<T> clazz, T instance) {
SERVICE_MAP.put(clazz, instance);
}
/**
* 获取对象
*/
@SuppressWarnings("unchecked")
public static <T> T get(Class<T> clazz) {
return (T) SERVICE_MAP.get(clazz);
}
}
代码测试
public class DemoMain {
public static void main(String[] args) {
// 1) 创建真实服务
RemoteService realService = new RemoteServiceImpl();
// 2) 创建兜底 callback
FallbackCallback callback = new SimpleFallbackCallback();
// 3) 用代理包装真实服务
RemoteService client = createProxy(
realService,
3,
callback
);
// 4) 注册到 “ServiceContainer” 中,模拟 Spring 容器
ServiceContainer.register(RemoteService.class, client);
// 5) 从容器获取,在任何地方都像用 Bean 一样使用
RemoteService serviceFromContainer = ServiceContainer.get(RemoteService.class);
// 6) 调用
String result = serviceFromContainer.getOrder("ORDER-2001");
System.out.println("最终返回:" + result);
}
/**
* 工具方法:对外暴露一个简洁的创建 Proxy 的入口
*/
@SuppressWarnings("unchecked")
public static <T> T createProxy(T target, int retryTimes, FallbackCallback callback) {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new RetryWithCallbackProxy(target, retryTimes, callback)
);
}
}
测试结果
[Proxy] 开始调用,method=getOrder, 剩余重试次数=2
[RemoteServiceImpl] 远程服务成功,orderId = ORDER-2001
最终返回:【真实订单信息】orderId=ORDER-2001
Process finished with exit code 0
分析和总结
// 5) 从容器获取,在任何地方都像用 Bean 一样使用
RemoteService serviceFromContainer = ServiceContainer.get(RemoteService.class);
// 6) 调用
String result = serviceFromContainer.getOrder("ORDER-2001");
上面这两行代码,其实就是我们日常使用时的核心逻辑。而我们实现的这个简易代理,与真正的 Feign 本质上只差一步:真实框架会在代理对象的实现中,根据注解去解析请求方式、路径和参数,并自动完成一次远程调用。
但通过这个例子,相信大家不仅能更直观地理解代理模式的应用,也能把原本有些神秘的 Feign 看得更清楚、更透彻。
你的阅读与同行,让路途更有意义
愿我们一路向前,成为更好的自己