Feign核心API:Contract
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对接口是有要求的:
- 接口不能包含泛型
- 接口只能有一个或没有父类接口
- 会排除掉接口里的静态,默认,以及Object的方法
校验通过后,把每个方法解析元信息就落在了parseAndValidateMetadata()
方法上:
其中不同级别的注解解析类都是抽象方法,需要子类去实现的:
- processAnnotationOnClass
- processAnnotationOnMethod
- processAnnotationsOnParameter
这三个抽象方法支持扩展,其中,Feign实现类是DeclarativeContract
,它也是一个抽象类,最终的实现是在Contract.Default
,这也是Feign的默认实现。
这三个方法的另一个实现类是
SpringMvcContract
,springmvc利用它实现了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
Default
是Feign的默认实现,类中主要是通过构造器去判断注解类型,再分别调用父类DeclarativeContract
对应的processAnnotationOnClass()、processAnnotationOnMethod()、processAnnotationsOnParameter()
,而DeclarativeContract
是BaseContract
的子类,通过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
就是代理对象,而handler
,Feign的默认实现是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);
}
再来看看SynchronousMethodHandler
的invoke()
方法:
@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相关推荐
- SpringCloud 中 Feign 核心原理,简单易懂!
目录 SpringCloud 中 Feign 核心原理 Feign远程调用的基本流程 Feign 远程调用的重要组件 Feigh 远程调用的执行流程 SpringCloud 中 Feign 核心原理 ...
- Spring Security系列教程11--Spring Security核心API讲解
前言 经过前面几个章节的学习,一一哥 带大家实现了基于内存和数据库模型的认证与授权,尤其是基于自定义的数据库模型更是可以帮助我们进行灵活开发.但是前面章节的内容,属于让我们达到了 "会用&q ...
- Spring Security系列教程-Spring Security核心API讲解
前言 经过前面几个章节的学习,一一哥 带大家实现了基于内存和数据库模型的认证与授权,尤其是基于自定义的数据库模型更是可以帮助我们进行灵活开发.但是前面章节的内容,属于让我们达到了 "会用&q ...
- hibernate教程--常用配置和核心API详解
一.Hibernate的常用的配置及核心API. 1.1 Hibernate的常见配置: 1.1.1.核心配置: 核心配置有两种方式进行配置: 1)属性文件的配置: * hibernate.prop ...
- hibernate教程--常用配置和核心API
一.Hibernate的常用的配置及核心API. 1.1Hibernate的常见配置: 1.1.1.核心配置: 核心配置有两种方式进行配置: 1)属性文件的配置: * hibernate.proper ...
- hibernate框架学习第二天:核心API、工具类、事务、查询、方言、主键生成策略等...
核心API Configuration 描述的是一个封装所有配置信息的对象 1.加载hibernate.properties(非主流,早期) Configuration conf = new Conf ...
- EXT核心API详解(二)-Array/Date/Function/Number/String
EXT核心API详解(二)-Array/Date/Function/Number/String Array类 indexOf( Object o ) Number object是否在数组中,找不到返 ...
- 核心API最佳实践——JDK日志分级
核心API最佳实践--JDK日志分级 时间:2005-10-29 08:00 来源:网管之家bitsCN.com 字体:[大 中 小] 日志(Log)是什么?字典对其的解释是"对某种机器工作 ...
- Java核心API需要掌握的程度
Java的核心API是非常庞大的,这给开发者来说带来了很大的方便,经常人有评论,java让程序员变傻. 但是一些内容我认为是必须掌握的,否则不可以熟练运用java,也不会使用就很难办了. 1.java ...
最新文章
- linux互信封装脚本,使用shell脚本实现自动SSH互信功能
- stand up meeting 12/25/2015 weekend 12/26/2015~12/27/2015
- win11怎么改任务栏大小
- 苹果x可以双卡吗_苹果12支持双卡吗
- 对xml操作的主要方法[轉]
- 使用sed在文件中定位文本的方式
- Excel 技巧大全之 01 如何将公式应用于 Excel 中的整列(5 种简单方法)
- 前装车载导航搭载率突破50%,谁在领跑背后的导航引擎
- maya怎么导出abc格式_maya导入abc文件到UE4
- 软件测试工程师必备的27个基础技能
- gateway自定义负载均衡策略
- EDK环境搭建UEFI工程模块文件介绍
- 一个前端程序员的日常
- 产品经理知识框架+求职面经——快手,字节跳动,
- 内存拷贝函数memcpy相关解析(C语言)
- 什么是Redis?为什么要用Redis?
- 人工智能还是人工智障?我tm快崩溃了
- java基础 马士兵_马士兵java零基础
- maven-项目中引入依赖包
- python3爬取巨潮资讯网站年报数据
热门文章
- Python基础-48-文本处理(逗号分隔值CSV)
- linux thunderbird,在 Linux 中安装 Thunderbird
- FFMPEG之 Ubuntu系统上配置MP3和AMR编解码工具
- worker服务器推送消息,浏览器中serviceWorker用法
- 武汉科技大学计算机专业研究生在哪个校区,武汉科技大学有几个校区啊,哪个校区最好...
- 功率半导体器件中的米勒效应(Miller Effect)
- 智能社 Javascript之Node.Js-经典全套教程(价值300元)
- Java使用UCanAccess连接mdb数据库
- 二级倒立摆的matlab模拟,输出倒立摆运动过程角度变化
- 更换手机号,需要换绑哪些业务