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. oracle数据加载控制文件格式,oracle数据加载的几种常用方法
  2. SEO优化中影响网站关键词排名的因素有哪些?
  3. ML之Validation:机器学习中模型验证方法的简介、代码实现、案例应用之详细攻略
  4. 【python数据挖掘课程】二十四.KMeans文本聚类分析互动百科语料
  5. netflix 模式创新_创新设计模式:工厂模式
  6. html5的canvas元素使用,HTML5canvas元素概念及使用方法介绍
  7. Fliptile——搜索+二进制优化
  8. TextRank、BM25算法提取关键字、文章自动摘要优秀文章保存
  9. git 应用 创建分支 并切换分支
  10. 梦断代码阅读笔记之二
  11. ati hd4570 linux amd 64 安装命令,ATI 显卡的驱动在Linux下的安装方法
  12. extern 的使用方法介绍
  13. 离散数学及其应用第六版中文电子书和答案
  14. MCU_segger-JLINK批处理脚本烧录工具-JFlash
  15. java 数据内地地址_我国大陆居民×××Java验证
  16. 第三方app实现微信登录功能
  17. html js鼠标滚轮事件,JS事件-鼠标滚轮事件
  18. 海康威视监控推流自建服务器实现网页端无插件1-2秒低延迟实时监控
  19. ES6 isFinite()
  20. linux外网服务器跳转内网服务器实现内网访问(iptables)

热门文章

  1. js 列表对象数据 转换成 excel格式数据
  2. ANSYS Autodyn仿真基础到高级视频教程
  3. cp1025 linux驱动下载,HP LaserJet 专业 CP1025 彩色打印机驱动下载
  4. Windows 事件查看器(收集)
  5. 包工协议书样本_小工程承包合同协议书
  6. python下载版本区别
  7. 普通人应该如何找到,一个适合自己的创业项目?开一家赚钱的小店?
  8. mysql distribute by_Hive order by、distribute by、sort by和cluster by
  9. java钢琴_java电子钢琴 源码
  10. JAVA计算机毕业设计道路桥梁工程知识文库系统Mybatis+系统+数据库+调试部署