Feign核心API:Contract

前言

最近碰到一个比较有意思的问题: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方法:

	@Override
    public 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的key
        result.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或POST
      checkState(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),如果是需要重试会走continue
  while (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中三个抽象方法的实现,自己对解析过程进行封装即可。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

唔知取咩名

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值