写完这个迷你版 Feign,你再也不怕面试官问它的基本原理了

27 阅读4分钟

第 10 篇 · 共100篇|用代码丈量成长 —— 坚持写下去,就是最好的成长。

你有没有被问过 Feign 的底层原理
如果你也曾想过它到底是怎么把一个“接口”变成真正的 HTTP 调用,其实可以把它拆开来看:

  1. 我们只需要定义一个接口,写好请求方式和参数,Feign 会自动生成对应的 HTTP 请求。
  2. 接口本身没有实现类,Feign 会在运行时通过 动态代理 给你生成实现,所以你只管调用。
  3. 使用上看起来就像调用本地方法一样,完全不需要关心 HTTP 细节。
  4. 如果配置了 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 看得更清楚、更透彻。

你的阅读与同行,让路途更有意义

愿我们一路向前,成为更好的自己