文章

FeignRPC源码分析

FeignRPC源码分析

1. 简介

Feign 是一个声明式的 HTTP 客户端库,由 Netflix 开发,现属于 OpenFeign 项目。它简化了 HTTP API 客户端的编写,让你可以用简单的接口和注解来定义 REST 服务调用。

1. 核心特点

  1. 声明式风格 :通过 Java 接口和注解定义 HTTP 请求
  2. 简化开发 :自动处理 HTTP 请求/响应序列化
  3. 集成友好 :与 Ribbon、Hystrix、Eureka 等 Netflix 组件无缝集成
  4. 可扩展 :支持自定义编码器、解码器、拦截器等

2. 声明式风格

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 使用HttpClient等工具手动构建请求
public List<User> getUsers() {
    try {
        // 1. 创建HTTP客户端
        CloseableHttpClient client = HttpClients.createDefault();
        // 2. 构建请求
        HttpGet request = new HttpGet("http://api.example.com/users");
        request.addHeader("Accept", "application/json");
        // 3. 执行请求
        HttpResponse response = client.execute(request);
        // 4. 处理响应
        String json = EntityUtils.toString(response.getEntity());
        // 5. 手动反序列化
        ObjectMapper mapper = new ObjectMapper();
        return mapper.readValue(json, new TypeReference<List<User>>(){});
    } catch (Exception e) {
        throw new RuntimeException("API调用失败", e);
    }
}
// 只需定义接口,Feign自动实现
public interface UserApi {

    @RequestLine("GET /users")
    @Headers("Accept: application/json")
    List<User> getUsers();
}

// 使用方式
public class Client {

    public static void main(String[] args) {
        UserApi api = Feign.builder()
                          .decoder(new JacksonDecoder())
                          .target(UserApi.class, "http://api.example.com");
                          
        List<User> users = api.getUsers(); // 一行调用
    }
}

基于 interface + annotation 实现了 DSL,只需要描述接口信息,其他的样板代码如创建httpclient、payload编解码、

只需要描述 调用的接口,具体的调用细节全部屏蔽。

2. 核心API

1. Feign

img

img

1,【构造Feign】feign.Feign.Builder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 // 创建完整配置的Feign实例
    public static Feign createFeignInstance() {
        ObjectMapper objectMapper = new ObjectMapper()
            .registerModule(new JavaTimeModule())
            .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

        return Feign.builder()
            .client(new ApacheHttpClient())
            .encoder(new JacksonEncoder(objectMapper))
            .decoder(new JacksonDecoder(objectMapper))
            .errorDecoder(new CustomErrorDecoder())
            .options(new Request.Options(1000, 5000))
            .retryer(new Retryer.Default())
            .logger(new Slf4jLogger())
            .logLevel(Logger.Level.BASIC)
            .requestInterceptor(new AuthInterceptor())
            .queryMapEncoder(new BeanQueryMapEncoder())
            .contract(new SpringMvcContract())
            .build();
    }

feign.Feign.Builder是可复用的工厂,收集了大量定制化配置 和 自定义拓展,用于后续指定Interface动态代理生成目标对象。

  1. .client(new ApacheHttpClient())
    1. 作用:指定底层HTTP客户端实现
    2. 说明:使用Apache HttpClient作为HTTP传输层,替代默认的JDK HttpURLConnection
  2. .encoder(new JacksonEncoder(objectMapper))
    1. 作用:请求体编码器
    2. 说明:使用Jackson将Java对象序列化为JSON格式的请求体
    3. 定制:传入自定义配置的ObjectMapper(支持Java8时间类型,忽略未知属性)
  3. .decoder(new JacksonDecoder(objectMapper))
    1. 作用:响应体解码器
    2. 说明:使用Jackson将JSON响应体反序列化为Java对象
    3. 定制:使用与编码器相同的ObjectMapper配置保证一致性
  4. .errorDecoder(new CustomErrorDecoder())
    1. 作用:错误响应处理器
    2. 说明:自定义非2xx响应的处理逻辑,可转换为特定异常类型
  5. .options(new Request.Options(1000, 5000))
    1. 作用:请求超时配置
    2. 参数:连接超时1秒,读取超时5秒
  6. .retryer(new Retryer.Default())
    1. 作用:重试策略
    2. 说明:使用默认的重试机制(间隔时间指数增长,最多重试5次)
  7. .logger(new Slf4jLogger())
    1. 作用:日志记录器
    2. 说明:使用SLF4J记录请求日志
  8. .logLevel(Logger.Level.BASIC)
    1. 作用:日志级别
    2. 说明:记录请求方法、URL和响应状态等基本信息
  9. .requestInterceptor(new AuthInterceptor())
    1. 作用:请求拦截器
    2. 说明:可添加认证头等全局请求参数(如JWT token)
  10. .queryMapEncoder(new BeanQueryMapEncoder())
    1. 作用:查询参数编码器
    2. 说明:将Java对象转换为URL查询参数
  11. .contract(new SpringMvcContract())
    1. 作用:接口方法解析契约
    2. 说明:支持Spring MVC注解(如@RequestMapping),使Feign接口与Controller定义方式一致

2.【构造Stub】feign.Feign

1
2
3
4
5
6
    // 创建目标客户端
    public static <T> T createClient(Class<T> apiType, String baseUrl) {
        return createFeignInstance().newInstance(
            new Target.HardCodedTarget<>(apiType, baseUrl)
        );
    }

3. 应用层使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public interface MyApi {
    // 1. 基本GET请求
    @RequestLine("GET /users/{id}")
    User getUserById(@Param("id") Long userId);
}

// 用户实体类
class User {
    private Long id;
    private String name;
    private String email;
    // getters/setters 省略
}
// 使用示例
public class Main {
    public static void main(String[] args) {
        MyApi api = FeignClientFactory.createClient(
            MyApi.class, 
            "http://api.example.com"
        );
        
        api.getData(); // 调用接口方法
    }
}

2. Target

img

Feign动态代理时,需要传入一个target。

img

是动态代理构造Stub的核心上下文。

apply方法是一个hook点,Stub发起调用时,流程会经过该hook点,允许拓展。

img

3. Client

img

Client是负责 发出请求、接受响应的 可拔插 拓展点。具体是远程调用还是本地调用、具体是HTTP协议还是其他协议对于上层透明。

只要能够处理Request,返回Response即可。

imgimg

常见实现类有:

img

4. RequestTemplate

imgimg

feign.RequestTemplate是 Request的工厂,基于【初始化时静态的语法元素】+【调用时动态的方法参数】来动态构造最终的Request。

3. 动态代理

1. newInstance(动态代理)

img

2. targetToHandlersByName.apply(target)

img

img

3. parseAndValidatateMetadata

DSL契约拓展,识别各个预定义的注解。

img

输出是各个方法的Metadata:

img

基于 方法、注解等静态的元信息,构造出来一个静态的 ResquestTemplate。

这个ResquestTemplate 只是一个模板,还有很多占位符需要再 方法调用时 填充。

默认的契约解析只识别Feign定义的HTTP要素注解:

img

可自行拓展适配其他注解,比如SpringMVC的一整套HTTP要素注解(RequestMapping等)。

img

img

img

4. 调用流程

image-20251108191953468

1. InvocationHandler

img

2. MethodHandler

1. 调用入口

img

2. 克隆、填充请求模板

img

3. 调用流程

img

img

5. Spring Cloud封装

1. FeignClientFactoryBean

运行时扫描所有携带FeignClient的接口,构造FeignClientFactoryBean。

img

2. 独立的、隔离的IOC容器

img

img

3. 组件关系

image-20251108191839068

  1. 每个@FeignClient注解的接口都会获得一个独立的子上下文
  2. 子上下文默认继承父上下文的所有bean定义
  3. 子上下文可以通过@Configuration类覆盖父上下文的bean定义
  4. 这种设计实现了配置隔离,允许不同的Feign客户端有不同的行为
  5. 子上下文的生命周期由FeignContext管理,随应用启动而创建,随应用关闭而销毁

这种设计模式使得Spring Cloud OpenFeign能够灵活地支持微服务架构中不同服务客户端的不同配置需求。

6. 总结

Feign 通过声明式接口 + 注解 将 HTTP 客户端调用简化为本地方法调用,屏蔽了底层 HTTP 请求的复杂性(如序列化、连接管理、重试等),显著提升开发效率。

1. 设计思想

  • 动态代理 :运行时生成接口的实现类,将方法调用转换为 HTTP 请求。
  • 契约模式 :通过注解(如 @RequestLine)定义 HTTP 语义,支持扩展(如适配 Spring MVC 注解)。
  • 组件化 :所有关键步骤(编码、解码、HTTP 客户端等)均可插拔替换,高度可定制。

2. 最佳实践

  • 统一配置 :通过 Feign.Builder 集中管理超时、编解码等。
  • 错误处理 :自定义 ErrorDecoder 将 HTTP 错误转换为业务异常。
  • 性能优化 :选择高性能 HTTP 客户端(如 OkHttp),合理设置连接池。
本文由作者按照 CC BY 4.0 进行授权