
前言
我们平时写的OpenFeign Client接口,和SpringMvc Controller接口语法的定义一模一样。使得Spring Mvc用户使用OpenFeign框架非常丝滑的过渡。
比如下方的openFeign接口:
@FeignClient(name = "fox-server", url = "http://127.0.0.1")public interface FeignClientApi {@RequestMapping(path = "/get")String getName(@RequestParam(name = "name") String name);}
一开始接触OpenFeign,我就非常好奇,我们写的OpenFeign Client里的方法语法为什么和SpringMvc Controller接口方法一样。
问题
带着上面的疑问,探究一下OpenFeign是如何做到使用SpringMvc的注解的。本文主要是了解下OpenFeign是如何实现对Spring Mvc注解支持的。我们可以知道下面两个问题。
如果没有SpringMvc,OpenFeign能独立运行吗?
OpenFeign如何做到支持SpringMvc注解的?
没有SpringMvc,OpenFeign能独立运行吗?
Contract。Contract 是OpenFeign定义的一个接口,一个协议,一个约定,按约定解析成OpenFeign可以识别的元信息。parseAndValidatateMetadata方法。参数Class<?> targetType就是我们写的Feign Client接口的Class。
返回是List<MethodMetadata>方法元数据列表。
public interface Contract {/*** 解析类上方法的元数据,转换成http请求数据*/List<MethodMetadata> parseAndValidatateMetadata(Class<?> targetType);}
BaseContract,他定义了核心的主逻辑
abstract class BaseContract implements Contract {@Overridepublic List<MethodMetadata> parseAndValidatateMetadata(Class<?> targetType) {//结果集,每个方法都会存一份元数据Map<String, MethodMetadata> result = new LinkedHashMap<String, MethodMetadata>();//循环每个方法开始解析for (Method method : targetType.getMethods()) {if (method.getDeclaringClass() == Object.class ||(method.getModifiers() & Modifier.STATIC) != 0 ||Util.isDefault(method)) {continue;}MethodMetadata metadata = parseAndValidateMetadata(targetType, method);checkState(!result.containsKey(metadata.configKey()), "Overrides unsupported: %s",metadata.configKey());result.put(metadata.configKey(), metadata);}return new ArrayList<>(result.values());}}
protected MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {//方法返回和方法名作为keyMethodMetadata data = new MethodMetadata();data.returnType(Types.resolve(targetType, targetType, method.getGenericReturnType()));data.configKey(Feign.configKey(targetType, method));//只有一个接口的情况解析父接口上的注解if (targetType.getInterfaces().length == 1) {processAnnotationOnClass(data, targetType.getInterfaces()[0]);}//解析Feign Client接口上的注解processAnnotationOnClass(data, targetType);//解析方法上的注解for (Annotation methodAnnotation : method.getAnnotations()) {processAnnotationOnMethod(data, methodAnnotation, method);}//方法上的注解必须不为空checkState(data.template().method() != null,"Method %s not annotated with HTTP method type (ex. GET, POST)",method.getName());//方法参数类型Class<?>[] parameterTypes = method.getParameterTypes();Type[] genericParameterTypes = method.getGenericParameterTypes();//方法参数的注解Annotation[][] parameterAnnotations = method.getParameterAnnotations();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 (parameterTypes[i] == URI.class) {data.urlIndex(i);} else if (!isHttpAnnotation) {checkState(data.formParams().isEmpty(),"Body parameters cannot be used with form parameters.");checkState(data.bodyIndex() == null, "Method has too many Body parameters: %s", method);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;}
BaseContract定义了解析各个方法的主流程,具体实现留给子类,使用了模版方法设计模式。
abstract class BaseContract implements Contract {/*** 解析类上的注解*/protected abstract void processAnnotationOnClass(MethodMetadata data, Class<?> clz);/*** 解析方法上的注解*/protected abstract void processAnnotationOnMethod(MethodMetadata data,Annotation annotation,Method method);/*** 解析参数上的注解*/protected abstract boolean processAnnotationsOnParameter(MethodMetadata data,Annotation[] annotations,int paramIndex);}
默认的协议feign.Contract.Default实现
在默认协议实现中,实现了基础协议定义的抽象方法processAnnotationOnClass,processAnnotationOnMethod,processAnnotationsOnParameter,
分别用来处理feign.Headers, feign.RequestLine,feign.Body,feign.Param等注解。这些注解都是feign包中的,也就是OpenFeign也提供了类似于SpringMvc开发方式的注解。


如果不使用SpringMvc,我们可以这样定义FeignClient。
public interface FeignClientTestApi {}
支持SpringMvc的协议实现SpringMvcContract
Spring Cloud 为了在SpringMvc项目中比较容易使用OpenFeign,对SpringMvc进行了支持。实现了SpringMvcContract
在方法processAnnotationOnClass实现中,对类上的RequestMapping注解进行了解析。
protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) {if (clz.getInterfaces().length == 0) {//查找类上的RequestMapping注解RequestMapping classAnnotation = findMergedAnnotation(clz, RequestMapping.class);if (classAnnotation != null && classAnnotation.value().length > 0) {String pathValue = Util.emptyToNull(classAnnotation.value()[0]);pathValue = this.resolve(pathValue);if (!pathValue.startsWith("/")) {pathValue = "/" + pathValue;}data.template().uri(pathValue);}}}
对方法上的RequestMapping进行解析
protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) {if (RequestMapping.class.isInstance(methodAnnotation) || methodAnnotation.annotationType().isAnnotationPresent(RequestMapping.class)) {RequestMapping methodMapping = (RequestMapping)AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);RequestMethod[] methods = methodMapping.method();if (methods.length == 0) {methods = new RequestMethod[]{RequestMethod.GET};}this.checkOne(method, methods, "method");data.template().method(HttpMethod.valueOf(methods[0].name()));this.checkAtMostOne(method, methodMapping.value(), "value");if (methodMapping.value().length > 0) {String pathValue = Util.emptyToNull(methodMapping.value()[0]);if (pathValue != null) {pathValue = this.resolve(pathValue);if (!pathValue.startsWith("/") && !data.template().path().endsWith("/")) {pathValue = "/" + pathValue;}data.template().uri(pathValue, true);}}this.parseProduces(data, method, methodMapping);this.parseConsumes(data, method, methodMapping);this.parseHeaders(data, method, methodMapping);data.indexToExpander(new LinkedHashMap());}}
对参数上的注解解析
protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) {boolean isHttpAnnotation = false;AnnotatedParameterProcessor.AnnotatedParameterContext context = new SimpleAnnotatedParameterContext(data, paramIndex);Method method = (Method)this.processedMethods.get(data.configKey());Annotation[] var7 = annotations;int var8 = annotations.length;for(int var9 = 0; var9 < var8; ++var9) {Annotation parameterAnnotation = var7[var9];AnnotatedParameterProcessor processor = (AnnotatedParameterProcessor)this.annotatedArgumentProcessors.get(parameterAnnotation.annotationType());if (processor != null) {Annotation processParameterAnnotation = this.synthesizeWithMethodParameterNameAsFallbackValue(parameterAnnotation, method, paramIndex);isHttpAnnotation |= processor.processArgument(context, processParameterAnnotation, method);}}if (isHttpAnnotation && data.indexToExpander().get(paramIndex) == null) {TypeDescriptor typeDescriptor = createTypeDescriptor(method, paramIndex);if (this.conversionService.canConvert(typeDescriptor, STRING_TYPE_DESCRIPTOR)) {Param.Expander expander = this.convertingExpanderFactory.getExpander(typeDescriptor);if (expander != null) {data.indexToExpander().put(paramIndex, expander);}}}return isHttpAnnotation;}
不管是OpenFeign自己的feign.Contract.Default实现,还是支持SpringMvc的SpringMvcContract实现,它们最终都将FeignClient上的方法解析成feign.MethodMetadata,里面包含了参数和响应的参数,类型等信息。

总结
1、回到文章开头提的疑问,现在可以知道了 如果没有SpringMvc,OpenFeign能独立运行吗?
答案是可以的,默认支持自己的注解,比如feign.RequestLine,feign.Body,
2、OpenFeign如何做到支持SpringMvc注解的?
通过实现Contract接口的SpringMvcContract, 里面会对SpringMvc注解的解析。
3、OpenFeign和SpringMvc注解对比
| openfeign注解 |
Springmvc注解 |
含义 |
RequestLine |
RequestMapping |
定义请求地址 |
Body |
RequestParam |
定义请求体参数 |
Headers |
RequestMapping#headers | 定义请求头参数 |
原文作者:服务端技术栈;如有侵权,请联系管理员删除。