是一个HTTP请求调用轻量级框架,可以以Java接口注解的方式调用HTTP请求,而不用像Java中通过封装HTTP请求报文的方式直接调用。
Feign解决了什么问题
在服务调用的场景中,我们经常调用基于HTTP协议的服务,而我们经常使用到的框架可能有HttpURLConnection、Apache HttpComponnets、OkHttp3 、Netty等等,这些框架在基于自身的专注点提供了自身特性。而从角色划分上来看,他们的职能是一致的提供HTTP调用服务。具体流程如下:
Fegin设计原理
PHASE 1. 基于面向接口的动态代理方式生成实现类
在使用Fegin时,会定义对应的接口类,在接口类上使用HTTP相关的注解,标识HTTP请求参数信息,如下所示:
@RequestLine("GET /repos/{owner}/{repo}/contributors")
List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);
在Feign底层,通过基于面向接口的动态代理方式生成实现类,将请求调用委托到动态代理实现类,基本原理如下所示:
public class ReflectiveFeign extends Feign{///省略部分代码@Overridepublic <T> T newInstance(Target<T> target) {//根据接口类和Contract协议解析方式,解析接口类上的方法和注解,转换成内部的MethodHandler处理方式Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();for (Method method : target.type().getMethods()) {if (method.getDeclaringClass() == Object.class) {continue;} else if(Util.isDefault(method)) {DefaultMethodHandler handler = new DefaultMethodHandler(method);defaultMethodHandlers.add(handler);methodToHandler.put(method, handler);} else {methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));}}InvocationHandler handler = factory.create(target, methodToHandler);// 基于Proxy.newProxyInstance 为接口类创建动态实现,将所有的请求转换给InvocationHandler 处理。T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {defaultMethodHandler.bindTo(proxy);}return proxy;}//省略部分代码
PHASE 2. 根据Contract协议规则,解析接口类的注解信息,解析成内部表现:
PHASE 3. 基于 RequestBean,动态生成Request
PHASE 4. 使用Encoder 将Bean转换成 Http报文正文(消息解析和转码逻辑)
Feign最终会将请求转换成Http 消息发送出去,传入的请求对象最终会解析成消息体,如下所示:
PHASE 5. 拦截器负责对请求和返回进行装饰处理
在请求转换的过程中,Feign抽象出来了拦截器接口,用于用户自定义对请求的操作:
public interface RequestInterceptor {/*** 可以在构造RequestTemplate 请求时,增加或者修改Header, Method, Body 等信息* Called for every request. Add data using methods on the supplied {@link RequestTemplate}.*/void apply(RequestTemplate template);
}
PHASE 6. 日志记录
在发送和接收请求的时候,Feign定义了统一的日志门面来输出日志信息 , 并且将日志的输出定义了四个等级:
级别 | 说明 |
NONE | 不做任何记录 |
BASIC | 只记录输出Http 方法名称、请求URL、返回状态码和执行时间 |
HEADERS | 记录输出Http 方法名称、请求URL、返回状态码和执行时间 和 Header 信息 |
FULL | 记录Request 和Response的Header,Body和一些请求元数据 |
PHASE 7 . 基于重试器发送HTTP请求
Feign内置了一个重试器,当HTTP请求出现IO异常时,Feign会有一个最大尝试次数发送请求,以下是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;}}}
PHASE 8. 发送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客户端。