是什么
Feign是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求。Spring Cloud引入 Feign并且集成了Ribbon实现客户端负载均衡调用。
封装了Http调用流程,更适合面向接口化的变成习惯
实现流程
流程图1
流程图2
核心就是通过一系列的封装和处理,将以 Java 注解的方式定义的远程调用 API 接口,最终转换成 HTTP 的请求形式,然后将 HTTP 的请的响应结果,解码成 Java Bean,返回给调用者
Feign 源码实现
基于面向接口的动态代理方式生成实现类
开启扫描所有@FeignClient注解的接口,将所有的访问路径以及接口信息封装成一个FeignClientFactoryBean注册到Spring容器中。
在使用feign 时,会定义对应的接口类,在接口类上使用Http相关的注解,标识HTTP请求参数信息
在 Feign 底层,通过基于面向接口的动态代理方式生成实现类,将请求调用委托到动态代理实现类,基本原理如下所示:
为了创建Feign的远程接口的代理实现类,Feign提供了自己的一个默认的调用处理器,叫做 FeignInvocationHandler 类,该类处于 feign-core 核心jar包中。当然,调用处理器可以进行替换,如果Feign与Hystrix结合使用,则会替换成HystrixInvocationHandler调用处理器类,类处于 feign-hystrix 的jar包中
根据 Contract 协议规则,解析接口类的注解信息,解析成内部表现

/*** Defines what annotations and values are valid on interfaces.*/public interface Contract {/*** Called to parse the methods in the class that are linked to HTTP requests.* 传入接口定义,解析成相应的方法内部元数据表示* @param targetType {@link feign.Target#type() type} of the Feign interface.*/// TODO: break this and correct spelling at some pointList<MethodMetadata> parseAndValidatateMetadata(Class<?> targetType);}
基于 RequestBean,动态生成 Request
根据传入的Bean对象和注解信息,从中提取出相应的值,来构造Http Request 对象
使用Encoder 将Bean转换成 Http报文正文(消息解析和转码逻辑)
Feign 最终会将请求转换成Http 消息发送出去,传入的请求对象最终会解析成消息体
拦截器负责对请求和返回进行装饰处理
在请求转换的过程中,Feign 抽象出来了拦截器接口,用于用户自定义对请求的操作,比如,如果希望Http消息传递过程中被压缩,可以定义一个请求拦截器。
public interface RequestInterceptor {/*** 可以在构造RequestTemplate 请求时,增加或者修改Header, Method, Body 等信息* Called for every request. Add data using methods on the supplied {@link RequestTemplate}.*/void apply(RequestTemplate template);}
比如,如果希望Http消息传递过程中被压缩,可以定义一个请求拦截器:
public class FeignAcceptGzipEncodingInterceptor extends BaseRequestInterceptor {/*** Creates new instance of {@link FeignAcceptGzipEncodingInterceptor}.** @param properties the encoding properties*/protected FeignAcceptGzipEncodingInterceptor(FeignClientEncodingProperties properties) {super(properties);}/*** {@inheritDoc}*/@Overridepublic void apply(RequestTemplate template) {// 在Header 头部添加相应的数据信息addHeader(template, HttpEncoding.ACCEPT_ENCODING_HEADER, HttpEncoding.GZIP_ENCODING,HttpEncoding.DEFLATE_ENCODING);}}
日志记录
在发送和接收请求的时候,Feign定义了统一的日志门面来输出日志信息 , 并且将日志的输出定义了四个等级:NONE、BASIC、HEADERS、FULL
具体实现位于:feign.Logger
基于重试器发送HTTP请求
Feign 内置了一个重试器,当HTTP请求出现IO异常时,Feign会有一个最大尝试次数发送请求
final class SynchronousMethodHandler implements MethodHandler {// 省略部分代码@Overridepublic Object invoke(Object[] argv) throws Throwable {//根据输入参数,构造Http 请求。RequestTemplate template = buildTemplateFromArgs.create(argv);// 克隆出一份重试器Retryer retryer = this.retryer.clone();// 尝试最大次数,如果中间有结果,直接返回while (true) {try {return executeAndDecode(template);} catch (RetryableException e) {retryer.continueOrPropagate(e);if (logLevel != Logger.Level.NONE) {logger.logRetry(metadata.configKey(), logLevel);}continue;}}}}
发送 HTTP 请求
Feign 真正发送HTTP请求是委托给 feign.Client 来做的。
public interface Client {/*** Executes a request against its {@link Request#url() url} and returns a response.* 执行Http请求,并返回Response* @param request safe to replay.* @param options options to apply to this request.* @return connected response, {@link Response.Body} is absent or unread.* @throws IOException on a network error connecting to {@link Request#url()}.*/Response execute(Request request, Options options) throws IOException;}
Feign 默认底层通过JDK 的 java.net.HttpURLConnection 实现了feign.Client接口类,在每次发送请求的时候,都会创建新的HttpURLConnection 链接,这也就是为什么默认情况下Feign的性能很差的原因。可以通过拓展该接口,使用Apache HttpClient 或者OkHttp3等基于连接池的高性能Http客户端。
而这个client会委托给org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient进行负载均衡地调用,采用了观察者模式。
public class LoadBalancerFeignClient implements Client {//省略.....@Overridepublic Response execute(Request request, Request.Options options) throws IOException {try {URI asUri = URI.create(request.url());String clientName = asUri.getHost();URI uriWithoutHost = cleanUrl(request.url(), clientName);FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(this.delegate, request, uriWithoutHost);IClientConfig requestConfig = getClientConfig(options, clientName);return lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();}catch (ClientException e) {IOException io = findIOException(e);if (io != null) {throw io;}throw new RuntimeException(e);}}//省略.....}
