Feign的核心API:Contract

  • 前言
  • Feign
  • Contract
    • parseAndValidateMetadata
    • BaseContract
    • Default
  • SynchronousMethodHandler
  • 总结

前言

最近碰到一个比较有意思的问题:Open Feign如何增加自定义注解的支持。比如增加的注解类似RequestBody,但是可以
使用在非复杂对象上,并且这个注解可能在一个方法,中会有多个。简单说就是不想增加实体类,又想实现类似RequestBody
的作用,在MvC中已经简单实现,但是Feign中咋玩呢?

Feign

Feign是Netflix开发的声明式、模板化的HTTP客户端,其灵感来自Retrofit、JAXRS-2.0以及WebSocket。Feign可帮助我们更加便捷、优雅地调用HTTP API。在Spring Cloud中,使用Feign非常简单——创建一个接口,并在接口上添加一些注解,代码就完成了。Feign支持多种注解,例如Feign自带的注解或者JAX-RS注解等。
Spring Cloud对Feign进行了增强,使Feign支持了Spring MVC注解,并整合了Ribbon和Eureka,从而让Feign的使用更加方便。

Contract

上面我们说到Feign是基于接口和注解开发,当真正发送http请求时,就需要对接口上的注解进行转化,提取http请求需要用到的参数,包括但不限于返回类型,参数,url,请求体等;
Contract 正式用来提取注解上有用信息,封装成MethodMetadata元数据的。
该接口继承结构如下图:

parseAndValidateMetadata

Contract接口里唯一 的方法,需要被实现类实现,主要用于组装HTTP请求的MethodMetadata元信息

BaseContract

内部抽象基类,实现Contract接口,我们看看BaseContract实现的parseAndValidateMetadata方法:

 @Overridepublic List<MethodMetadata> parseAndValidateMetadata(Class<?> targetType) {//首先会进行类检验://1. 类上不能存在泛型变量checkState(targetType.getTypeParameters().length == 0, "Parameterized types unsupported: %s",targetType.getSimpleName());//2. 父接口最多只能存在一个checkState(targetType.getInterfaces().length <= 1, "Only single inheritance supported: %s",targetType.getSimpleName());if (targetType.getInterfaces().length == 1) {checkState(targetType.getInterfaces()[0].getInterfaces().length == 0,"Only single-level inheritance supported: %s",targetType.getSimpleName());}final Map<String, MethodMetadata> result = new LinkedHashMap<String, MethodMetadata>();//3. getMethods()获取本类和父类所有方法for (final Method method : targetType.getMethods()) {//排除掉object、static、default方法if (method.getDeclaringClass() == Object.class ||(method.getModifiers() & Modifier.STATIC) != 0 ||Util.isDefault(method)) {continue;}//调用本类的parseAndValidateMetadata方法,对每个method转换final MethodMetadata metadata = parseAndValidateMetadata(targetType, method);checkState(!result.containsKey(metadata.configKey()), "Overrides unsupported: %s",metadata.configKey());//用metadata的configKey作为result的keyresult.put(metadata.configKey(), metadata);}return new ArrayList<>(result.values());}

通过代码我们能看到,Feign对接口是有要求的:

  1. 接口不能包含泛型
  2. 接口只能有一个或没有父类接口
  3. 会排除掉接口里的静态,默认,以及Object的方法

校验通过后,把每个方法解析元信息就落在了parseAndValidateMetadata()方法上:
其中不同级别的注解解析类都是抽象方法,需要子类去实现的:

  1. processAnnotationOnClass
  2. processAnnotationOnMethod
  3. processAnnotationsOnParameter
    这三个抽象方法支持扩展,其中,Feign实现类是DeclarativeContract,它也是一个抽象类,最终的实现是在Contract.Default,这也是Feign的默认实现。

这三个方法的另一个实现类是SpringMvcContractspringmvc利用它实现了RequestMapping等注解。

 protected MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {final MethodMetadata data = new MethodMetadata();data.targetType(targetType);data.method(method);//方法返回支持泛型data.returnType(Types.resolve(targetType, targetType, method.getGenericReturnType()));//生成唯一的configKey,上面result会用到data.configKey(Feign.configKey(targetType, method));//处理class级别的注解,把父类也一起处理了if (targetType.getInterfaces().length == 1) {processAnnotationOnClass(data, targetType.getInterfaces()[0]);}processAnnotationOnClass(data, targetType);//处理method级别的注解for (final Annotation methodAnnotation : method.getAnnotations()) {processAnnotationOnMethod(data, methodAnnotation, method);}if (data.isIgnored()) {return data;}//校验请求方式:GET或POSTcheckState(data.template().method() != null,"Method %s not annotated with HTTP method type (ex. GET, POST)%s",data.configKey(), data.warnings());//参数类型,支持泛型final Class<?>[] parameterTypes = method.getParameterTypes();final Type[] genericParameterTypes = method.getGenericParameterTypes();//参数级别注解final Annotation[][] parameterAnnotations = method.getParameterAnnotations();final int count = parameterAnnotations.length;for (int i = 0; i < count; i++) {boolean isHttpAnnotation = false;if (parameterAnnotations[i] != null) {isHttpAnnotation = processAnnotationsOnParameter(data, parameterAnnotations[i], i);}if (isHttpAnnotation) {data.ignoreParamater(i);}//参数类型若是URI,url就以此为准if (parameterTypes[i] == URI.class) {data.urlIndex(i);} else if (!isHttpAnnotation && parameterTypes[i] != Request.Options.class) {if (data.isAlreadyProcessed(i)) {checkState(data.formParams().isEmpty() || data.bodyIndex() == null,"Body parameters cannot be used with form parameters.%s", data.warnings());} else {checkState(data.formParams().isEmpty(),"Body parameters cannot be used with form parameters.%s", data.warnings());checkState(data.bodyIndex() == null,"Method has too many Body parameters: %s%s", method, data.warnings());data.bodyIndex(i);data.bodyType(Types.resolve(targetType, targetType, genericParameterTypes[i]));}}}if (data.headerMapIndex() != null) {checkMapString("HeaderMap", parameterTypes[data.headerMapIndex()],genericParameterTypes[data.headerMapIndex()]);}if (data.queryMapIndex() != null) {if (Map.class.isAssignableFrom(parameterTypes[data.queryMapIndex()])) {checkMapKeys("QueryMap", genericParameterTypes[data.queryMapIndex()]);}}return data;}

Default

DefaultFeign的默认实现,类中主要是通过构造器去判断注解类型,再分别调用父类DeclarativeContract对应的processAnnotationOnClass()、processAnnotationOnMethod()、processAnnotationsOnParameter(),而DeclarativeContractBaseContract的子类,通过Default构造器我们能看到Feign具体支持哪些注解:

static final Pattern REQUEST_LINE_PATTERN = Pattern.compile("^([A-Z]+)[ ]*(.*)$");public Default() {super.registerClassAnnotation(Headers.class, (header, data) -> {...});super.registerMethodAnnotation(RequestLine.class, (ann, data) -> {...});super.registerMethodAnnotation(Body.class, (ann, data) -> {...});super.registerMethodAnnotation(Headers.class, (header, data) -> {...});super.registerParameterAnnotation(Param.class, (paramAnnotation, data, paramIndex) -> {...});super.registerParameterAnnotation(QueryMap.class, (queryMap, data, paramIndex) -> {...});super.registerParameterAnnotation(HeaderMap.class, (queryMap, data, paramIndex) -> {...});}

SynchronousMethodHandler

如果学习过Feign的源码,并且了解Feign的实现机制,那么对这个类不会感到陌生,Feign是基于动态代理实现的,并且是层层代理的invoke()实现,最终会执行SynchronousMethodHandler类里的invoke()方法,发送HTTP请求。

对于被@FeignClients注解的接口,我们会根据其属性在IOC容器里注入一个FeignClientFactoryBean,而FeignClientFactoryBean实现了FactoryBean接口,因此实际上我们对该bean进行初始化后得到的是其getObject()的返回值。这也是我们能够通过类似于调用服务的方法实现http请求发送的关键所在。

通过getObject()方法最后会来到这样一段代码:

@Override
public <T> T newInstance(Target<T> target) {...InvocationHandler handler = factory.create(target, methodToHandler);T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),new Class<?>[] {target.type()}, handler);...
}

这里的proxy就是代理对象,而handlerFeign的默认实现是FeignInvocationHandler,在它的invoke()方法中,会获取到每个方法对应的SynchronousMethodHandler,执行其invoke()方法:

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {...//dispatch是一个Map<Method, MethodHandler>,SynchronousMethodHandler是MethodHandler子类return dispatch.get(method).invoke(args);
}

再来看看SynchronousMethodHandlerinvoke()方法:

@Override
public Object invoke(Object[] argv) throws Throwable {RequestTemplate template = buildTemplateFromArgs.create(argv);Options options = findOptions(argv);Retryer retryer = this.retryer.clone();//注意这里是while(true),如果是需要重试会走continuewhile (true) {try {return executeAndDecode(template, options);} catch (RetryableException e) {try {retryer.continueOrPropagate(e);} catch (RetryableException th) {Throwable cause = th.getCause();if (propagationPolicy == UNWRAP && cause != null) {throw cause;} else {throw th;}}if (logLevel != Logger.Level.NONE) {logger.logRetry(metadata.configKey(), logLevel);}continue;}}
}

executeAndDecode(template, options)会发送HTTP请求,以及解析Response,如果出现异常,会抛出RetryableException,然后进行重试。

总结

以上是关于Feign的核心API,最后的SynchronousMethodHandler类值得细致研究,很枯燥但也很硬核。
回到最开始的问题,如果要在Feign中自定义一个用于解析注解与接口之间的元数据的类,可以参考Contrac.Default中三个抽象方法的实现,自己对解析过程进行封装即可。

Feign核心API:Contract相关推荐

  1. SpringCloud 中 Feign 核心原理,简单易懂!

    目录 SpringCloud 中 Feign 核心原理 Feign远程调用的基本流程 Feign 远程调用的重要组件 Feigh 远程调用的执行流程 SpringCloud 中 Feign 核心原理 ...

  2. Spring Security系列教程11--Spring Security核心API讲解

    前言 经过前面几个章节的学习,一一哥 带大家实现了基于内存和数据库模型的认证与授权,尤其是基于自定义的数据库模型更是可以帮助我们进行灵活开发.但是前面章节的内容,属于让我们达到了 "会用&q ...

  3. Spring Security系列教程-Spring Security核心API讲解

    前言 经过前面几个章节的学习,一一哥 带大家实现了基于内存和数据库模型的认证与授权,尤其是基于自定义的数据库模型更是可以帮助我们进行灵活开发.但是前面章节的内容,属于让我们达到了 "会用&q ...

  4. hibernate教程--常用配置和核心API详解

    一.Hibernate的常用的配置及核心API. 1.1 Hibernate的常见配置: 1.1.1.核心配置: 核心配置有两种方式进行配置:  1)属性文件的配置: * hibernate.prop ...

  5. hibernate教程--常用配置和核心API

    一.Hibernate的常用的配置及核心API. 1.1Hibernate的常见配置: 1.1.1.核心配置: 核心配置有两种方式进行配置: 1)属性文件的配置: * hibernate.proper ...

  6. hibernate框架学习第二天:核心API、工具类、事务、查询、方言、主键生成策略等...

    核心API Configuration 描述的是一个封装所有配置信息的对象 1.加载hibernate.properties(非主流,早期) Configuration conf = new Conf ...

  7. EXT核心API详解(二)-Array/Date/Function/Number/String

    EXT核心API详解(二)-Array/Date/Function/Number/String Array类 indexOf( Object o )  Number object是否在数组中,找不到返 ...

  8. 核心API最佳实践——JDK日志分级

    核心API最佳实践--JDK日志分级 时间:2005-10-29 08:00 来源:网管之家bitsCN.com 字体:[大 中 小] 日志(Log)是什么?字典对其的解释是"对某种机器工作 ...

  9. Java核心API需要掌握的程度

    Java的核心API是非常庞大的,这给开发者来说带来了很大的方便,经常人有评论,java让程序员变傻. 但是一些内容我认为是必须掌握的,否则不可以熟练运用java,也不会使用就很难办了. 1.java ...

最新文章

  1. linux互信封装脚本,使用shell脚本实现自动SSH互信功能
  2. stand up meeting 12/25/2015 weekend 12/26/2015~12/27/2015
  3. win11怎么改任务栏大小
  4. 苹果x可以双卡吗_苹果12支持双卡吗
  5. 对xml操作的主要方法[轉]
  6. 使用sed在文件中定位文本的方式
  7. Excel 技巧大全之 01 如何将公式应用于 Excel 中的整列(5 种简单方法)
  8. 前装车载导航搭载率突破50%,谁在领跑背后的导航引擎
  9. maya怎么导出abc格式_maya导入abc文件到UE4
  10. 软件测试工程师必备的27个基础技能
  11. gateway自定义负载均衡策略
  12. EDK环境搭建UEFI工程模块文件介绍
  13. 一个前端程序员的日常
  14. 产品经理知识框架+求职面经——快手,字节跳动,
  15. 内存拷贝函数memcpy相关解析(C语言)
  16. 什么是Redis?为什么要用Redis?
  17. 人工智能还是人工智障?我tm快崩溃了
  18. java基础 马士兵_马士兵java零基础
  19. maven-项目中引入依赖包
  20. python3爬取巨潮资讯网站年报数据

热门文章

  1. Python基础-48-文本处理(逗号分隔值CSV)
  2. linux thunderbird,在 Linux 中安装 Thunderbird
  3. FFMPEG之 Ubuntu系统上配置MP3和AMR编解码工具
  4. worker服务器推送消息,浏览器中serviceWorker用法
  5. 武汉科技大学计算机专业研究生在哪个校区,武汉科技大学有几个校区啊,哪个校区最好...
  6. 功率半导体器件中的米勒效应(Miller Effect)
  7. 智能社 Javascript之Node.Js-经典全套教程(价值300元)
  8. Java使用UCanAccess连接mdb数据库
  9. 二级倒立摆的matlab模拟,输出倒立摆运动过程角度变化
  10. 更换手机号,需要换绑哪些业务