A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始



什么是Feign?
Feign 的英文表意为“假装,伪装,变形”, 是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求,而不用像Java中通过封装HTTP请求报文的方式直接调用。Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求,这种请求相对而言比较直观。
Feign被广泛应用在Spring Cloud 的解决方案中,是学习基于Spring Cloud 微服务架构不可或缺的重要组件。
开源项目地址:
https://github.com/OpenFeign/feign
Feign解决了什么问题?
封装了Http调用流程,更适合面向接口化的变成习惯
在服务调用的场景中,我们经常调用基于Http协议的服务,而我们经常使用到的框架可能有HttpURLConnection、Apache HttpComponnets、OkHttp3 、Netty等等,这些框架在基于自身的专注点提供了自身特性。而从角色划分上来看,他们的职能是一致的提供Http调用服务。具体流程如下:
调用方Client框架服务方构造Http请求URL填写Http请求头信息填写消息报文信息发送Http请求处理请求,返回结果返回报文提取报文信息,转换成对应的Java bean根据Bean中的定义,业务处理调用方Client框架服务方
Feign是如何设计的?
PHASE 1. 基于面向接口的动态代理方式生成实现类
在使用feign 时,会定义对应的接口类,在接口类上使用Http相关的注解,标识HTTP请求参数信息,如下所示:
interface GitHub {  @RequestLine("GET /repos/{owner}/{repo}/contributors")  List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);}public static class Contributor {  String login;  int contributions;}public class MyApp {  public static void main(String... args) {    GitHub github = Feign.builder()                         .decoder(new GsonDecoder())                         .target(GitHub.class, "https://api.github.com");      // Fetch and print a list of the contributors to this library.    List<Contributor> contributors = github.contributors("OpenFeign", "feign");    for (Contributor contributor : contributors) {      System.out.println(contributor.login + " (" + contributor.contributions + ")");    }  }}
在Feign 底层,通过基于面向接口的动态代理方式生成实现类,将请求调用委托到动态代理实现类,基本原理如下所示:
public class ReflectiveFeign extends Feign{  ///省略部分代码  @Override  public <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协议规则,解析接口类的注解信息,解析成内部表现:
Feign 定义了转换协议,定义如下:
/** * 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 point  List<MethodMetadata> parseAndValidatateMetadata(Class<?> targetType);}默认Contract 实现
Feign 默认有一套自己的协议规范,规定了一些注解,可以映射成对应的Http请求,如官方的一个例子:
public interface GitHub {    @RequestLine("GET /repos/{owner}/{repo}/contributors")  List<Contributor> getContributors(@Param("owner") String owner, @Param("repo") String repository);    class Contributor {    String login;    int contributions;  }}
上述的例子中,尝试调用GitHub.getContributors(“foo”,“myrepo”)的的时候,会转换成如下的HTTP请求:
GET /repos/foo/myrepo/contributorsHOST XXXX.XXX.XXX
Feign 默认的协议规范
注解接口Target使用说明
@RequestLine方法上定义HttpMethod 和 UriTemplate. UriTemplate 中使用{} 包裹的表达式,可以通过在方法参数上使用@Param 自动注入
@Param方法参数定义模板变量,模板变量的值可以使用名称的方式使用模板注入解析
@Headers类上或者方法上定义头部模板变量,使用@Param 注解提供参数值的注入。如果该注解添加在接口类上,则所有的请求都会携带对应的Header信息;如果在方法上,则只会添加到对应的方法请求上
@QueryMap方法上定义一个键值对或者 pojo,参数值将会被转换成URL上的 query 字符串上
@HeaderMap方法上定义一个HeaderMap, 与 UrlTemplate 和HeaderTemplate 类型,可以使用@Param 注解提供参数值
具体FeignContract 是如何解析的,不在本文的介绍范围内,详情请参考代码:
https://github.com/OpenFeign/feign/blob/master/core/src/main/java/feign/Contract.java
基于Spring MVC的协议规范SpringMvcContract:
当前Spring Cloud 微服务解决方案中,为了降低学习成本,采用了Spring MVC的部分注解来完成 请求协议解析,也就是说 ,写客户端请求接口和像写服务端代码一样:客户端和服务端可以通过SDK的方式进行约定,客户端只需要引入服务端发布的SDK API,就可以使用面向接口的编码方式对接服务:
我们团队内部就是按照这种思路,结合Spring Boot Starter 的特性,定义了服务端starter,
服务消费者在使用的时候,只需要引入Starter,就可以调用服务。这个比较适合平台无关性,接口抽象出来的好处就是可以根据服务调用实现方式自有切换:
  • 可以基于简单的Http服务调用;
  • 可以基于Spring Cloud 微服务架构调用;
  • 可以基于Dubbo SOA服务治理
这种模式比较适合在SaSS混合软件服务的模式下自有切换,根据客户的硬件能力选择合适的方式部署,也可以基于自身的服务集群部署微服务
当然,目前的Spring MVC的注解并不是可以完全使用的,有一些注解并不支持,如@GetMapping,@PutMapping 等,仅支持使用@RequestMapping 等,另外注解继承性方面也有些问题;具体限制细节,每个版本能会有些出入,可以参考上述的代码实现,比较简单。
Spring Cloud 没有基于Spring MVC 全部注解来做Feign 客户端注解协议解析,个人认为这个是一个不小的坑。在刚入手Spring Cloud 的时候,就碰到这个问题。后来是深入代码才解决的… 这个应该有人写了增强类来处理,暂且不表,先MARK一下,是一个开源代码练手的好机会。
PHASE 3. 基于 RequestBean,动态生成Request
根据传入的Bean对象和注解信息,从中提取出相应的值,来构造Http Request 对象:
PHASE 4. 使用Encoder 将Bean转换成 Http报文正文(消息解析和转码逻辑)
Feign 最终会将请求转换成Http 消息发送出去,传入的请求对象最终会解析成消息体,如下所示:
在接口定义上Feign做的比较简单,抽象出了Encoder 和decoder 接口:
public interface Encoder {  /** Type literal for {@code Map<String, ?>}, indicating the object to encode is a form. */  Type MAP_STRING_WILDCARD = Util.MAP_STRING_WILDCARD;  /**   * Converts objects to an appropriate representation in the template.   *  将实体对象转换成Http请求的消息正文中   * @param object   what to encode as the request body.   * @param bodyType the type the object should be encoded as. {@link #MAP_STRING_WILDCARD}   *                 indicates form encoding.   * @param template the request template to populate.   * @throws EncodeException when encoding failed due to a checked exception.   */  void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException;  /**   * Default implementation of {@code Encoder}.   */  class Default implements Encoder {    @Override    public void encode(Object object, Type bodyType, RequestTemplate template) {      if (bodyType == String.class) {        template.body(object.toString());      } else if (bodyType == byte[.class) {        template.body((byte[) object, null);      } else if (object != null) {        throw new EncodeException(            format("%s is not a type supported by this encoder.", object.getClass()));      }    }  }}public interface Decoder {  /**   * Decodes an http response into an object corresponding to its {@link   * java.lang.reflect.Method#getGenericReturnType() generic return type}. If you need to wrap   * exceptions, please do so via {@link DecodeException}.   *  从Response 中提取Http消息正文,通过接口类声明的返回类型,消息自动装配   * @param response the response to decode    * @param type     {@link java.lang.reflect.Method#getGenericReturnType() generic return type} of   *                 the method corresponding to this {@code response}.   * @return instance of {@code type}   * @throws IOException     will be propagated safely to the caller.   * @throws DecodeException when decoding failed due to a checked exception besides IOException.   * @throws FeignException  when decoding succeeds, but conveys the operation failed.   */  Object decode(Response response, Type type) throws IOException, DecodeException, FeignException;  /** Default implementation of {@code Decoder}. */  public class Default extends StringDecoder {    @Override    public Object decode(Response response, Type type) throws IOException {      if (response.status() == 404) return Util.emptyValueOf(type);      if (response.body() == null) return null;      if (byte[.class.equals(type)) {        return Util.toByteArray(response.body().asInputStream());      }      return super.decode(response, type);    }  }}
目前Feign 有以下实现:
Encoder/ Decoder 实现
说明

JacksonEncoder,JacksonDecoder
基于 Jackson 格式的持久化转换协议

GsonEncoder,GsonDecoder
基于Google GSON 格式的持久化转换协议

SaxEncoder,SaxDecoder
基于XML 格式的Sax 库持久化转换协议

JAXBEncoder,JAXBDecoder
基于XML 格式的JAXB 库持久化转换协议

ResponseEntityEncoder,ResponseEntityDecoder
Spring MVC 基于 ResponseEntity< T > 返回格式的转换协议

SpringEncoder,SpringDecoder
基于Spring MVC HttpMessageConverters 一套机制实现的转换协议 ,应用于Spring Cloud 体系中
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);}
比如,如果希望Http消息传递过程中被压缩,可以定义一个请求拦截器:
public class FeignAcceptGzipEncodingInterceptor extends BaseRequestInterceptor {        /**         * Creates new instance of {@link FeignAcceptGzipEncodingInterceptor}.         *         * @param properties the encoding properties         */        protected FeignAcceptGzipEncodingInterceptor(FeignClientEncodingProperties properties) {                super(properties);        }
本文链接:https://blog.csdn.net/u010349169/article/details/82821294


0 个回复

您需要登录后才可以回帖 登录 | 加入黑马