1. 当前位置:首页>百科>OpenFeign是如何识别RequestMapping注解的?

OpenFeign是如何识别RequestMapping注解的?

我们平时写的OpenFeign Client接口,和SpringMvc Controller接口语法的定义一模一样。使得Spring Mvc用户使用OpenFeign框架非常丝滑的过渡。

比如下方这种:

java复制代码@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能独立运行吗?

答案是可以的,OpenFeign是一个声明式的rpc通信客户端,默认实现了自己的注解方式。

理解上面这个问题,不得不提OpenFeign的约定接口Contract。

Contract 是OpenFeign定义的一个接口,一个协议,一个约定,按约定解析成OpenFeign可以识别的元信息。

接口只有一个
parseAndValidatateMetadata方法,

参数Class<?> targetType就是我们写的Feign Client接口的Class。

返回是List<MethodMetadata>方法元数据列表。

java复制代码public interface Contract {/**
* 解析类上方法的元数据,转换成http请求数据
*/List<MethodMetadata> parseAndValidatateMetadata(Class<?> targetType);

OpenFeign提供了基础的协议实现BaseContract,他定义了核心的主逻辑


java复制代码abstract class BaseContract implements Contract {    @Override
   public 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());
   }
java复制代码protected MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {      //方法返回和方法名作为key
     MethodMetadata 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定义了解析各个方法的主流程,具体实现留给子类,使用了模版方法设计模式。

java复制代码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。

java复制代码@FeignClient(name = "fox-server", url = "http://127.0.0.1")public interface FeignClientTestApi {        
       @RequestLine(value = "/get/test")
       String get(@Param String name);
}

支持SpringMvc的协议实现SpringMvcContract

Spring Cloud 为了在SpringMvc项目中比较容易使用OpenFeign,对SpringMvc进行了支持。 实现了SpringMvcContract

在processAnnotationOnClass实现中,对类上的RequestMapping注解进行了解析

java复制代码protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) {        if (clz.getInterfaces().length == 0) {
           RequestMapping classAnnotation = (RequestMapping)AnnotatedElementUtils.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进行解析

java复制代码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());
       }
   }

对参数上的注解解析

java复制代码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,里面包含了参数和响应的参数,类型等信息。

总结

回到文章开头提的疑问,现在可以知道了 如果没有SpringMvc,OpenFeign能独立运行吗?

  • 答案是可以的,默认支持自己的注解,比如feign.RequestLine,feign.Body,

OpenFeign如何做到支持SpringMvc注解的?

通过实现Contract接口的SpringMvcContract, 里面会对SpringMvc注解的解析。


本文采摘于网络,不代表本站立场,转载联系作者并注明出处:https://www.5amiao.com/baike/1704.html

联系我们

在线咨询:点击这里给我发消息

QQ号:1045784018

工作日:10:00-17:00,节假日休息