Feign源码分析(二) - builder构建

@Author:zxw
@Email:502513206@qq.com


目录

  1. Feign源码分析(一) - 初探Feign

1.前言

通过上篇文章,我们得知了Feign类中主要的元数据,接下来就看下Feign是如何为我们生成代理类的。可以看到调用target方法时,传入了Class参数Gitee.Class,那么Feign是通过接口代理的方式来生成实现类的,

interface Gitee {
        @RequestLine("GET /api/v5/repos/{owner}/{repo}/stargazers?access_token=xxx&page=1&per_page=20")
        List<Stargazers> repo(@Param("owner") String owner, @Param("repo") String repo);

        default List<Stargazers> stargazers(String owner, String repo) {
            return repo(owner, repo);
        }

        static Gitee connect() {
            final Decoder decoder = new GsonDecoder();
            final Encoder encoder = new GsonEncoder();
            return Feign.builder()
                    .encoder(encoder)
                    .decoder(decoder)
                    .logger(new Logger.ErrorLogger())
                    .logLevel(Logger.Level.BASIC)
                    .requestInterceptor(template -> {
//                        template.header(
//                                "Authorization",
//                                "token 383f1c1b474d8f05a21e7964976ab0d403fee071");
                    })
                    .options(new Request.Options(10, TimeUnit.SECONDS, 60, TimeUnit.SECONDS, true))
                    .target(Gitee.class, "https://gitee.com");
        }
    }

2.源码分析

Builder模式中调用build()方法时,会生成实例化对象。上述代码在build时生成的不是Feign类,而是Feign的子类ReflectiveFeign
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
在代码中我们只定义了接口,而接口的实现则是Feign帮我们生成,所以在build结束后,还会调用方法为我们生成代理类,如下

public <T> T target(Target<T> target) {
      return build().newInstance(target);
}

在生成实现类之前,我们还需要对接口上的注解进行解析

 @RequestLine("GET /api/v5/repos/{owner}/{repo}/stargazers?access_token=xxx&page=1&per_page=20")
 List<Stargazers> repo(@Param("owner") String owner, @Param("repo") String repo);

先前讲解Feign元数据时说过,对于的注解的解析是通过Contract类来实现的,可以看到对于每个方法都会生成一个MethodMetadata对象,接下来对MethodMetadata对象进行分析

 List<MethodMetadata> metadata = contract.parseAndValidateMetadata(target.type());

2.1 MethodMetadata

对于这个类名就能得知该类存储的是方法的元数据,那么一定有一个Method字段指向方法,有了方法的引用当然还得有类本身的引用以及返回值类型等等

private transient Method method;
private transient Class<?> targetType;
private transient Type returnType;

还需要一个key用来作为方法的名称,就像mybatis为接口生成映射的key一样,类名#方法名

private String configKey;

以上是method的基本属性,在编写feign的接口时还可以指定参数替换等,比如在参数处使用@param注解标注替换的属性

@RequestLine("GET /api/v5/repos/{owner}/{repo}/stargazers?access_token=xxx&page=1&per_page=20")
List<Stargazers> repo(@Param("owner") String owner, @Param("repo") String repo);

@RequestLine注解中{}内对应的值会被映射为索引位,从0开始。所以应当还有一个用来存储索引位对应的参数名称的映射关系

private final Map<Integer, Collection<String>> indexToName =
      new LinkedHashMap<Integer, Collection<String>>();

对于MethodMetadata目前用到的元数据就介绍这么多。接下来看Feign解析注解的具体流程

2.2 Contract方法解析

在我们生成Feign类时,会自动生成一个Contract的实现类来解析类中方法的Http请求信息

private Contract contract = new Contract.Default();

对于其类的继承图关系如下
在这里插入图片描述

那么我们先来看看BaseContract的具体内容,Contract接口只提供了一个方法,就是根据Class来解析和验证元数据,该方法有公共的BaseContract提供实现

List<MethodMetadata> parseAndValidateMetadata(Class<?> targetType);

此外BaseContract还有3个抽象方法让子类实现

/** * 解析类上的注解 */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);

通过这几个方法,对于Contract接口的职责大概已经了解。首先传入接口Class,调用parseAndValidateMetadata获取基本信息,然后依次调用上面3个方法解析类、方法、参数上的注解。通过这套解析流程,我们也能定义自己的注解进行解析了。
在这里插入图片描述

具体的方法调用如下,可以看到最终生成了一个List

final Map<String, MethodMetadata> result = new LinkedHashMap<String, MethodMetadata>();      for (final Method method : targetType.getMethods()) {        if (method.getDeclaringClass() == Object.class ||            (method.getModifiers() & Modifier.STATIC) != 0 ||            Util.isDefault(method)) {          continue;        }        final 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());

2.3 ParseHandlersByName

上面我们已经获取到了一个MethodMetadata列表,这里仅仅包含了方法的一些基本信息。还未涉及到编码器,所以Feign提供了一个RequestTemplate.Factory工厂类,通过请求参数的类型,Feign为我们提供不同的模板工厂类。

if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {          buildTemplate =              new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);        } else if (md.bodyIndex() != null) {          buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);        } else {          buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder, target);        }

前面都是封装了方法的基本参数,我们在发起调用时还需要Client客户端等参数,Feign提供了MethodHandler作为方法请求的执行器。

MethodHandler -> SynchronousMethodHandlerpublic MethodHandler create(Target<?> target,                                MethodMetadata md,                                RequestTemplate.Factory buildTemplateFromArgs,                                Options options,                                Decoder decoder,                                ErrorDecoder errorDecoder) {      return new SynchronousMethodHandler(target, client, retryer, requestInterceptors, logger,          logLevel, md, buildTemplateFromArgs, options, decoder,          errorDecoder, decode404, closeAfterDecode, propagationPolicy, forceDecoding);    }

那么这个ParseHandlersByName类其实就类似于一个工具类,里面调用了contract接口的解析获得了MethodMetadata列表,然后在将方法包装成MethodHandler,最后根据configKey返回一个Map

Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);

2.4 FeignInvocationHandler

前面已经完成了方法的解析了,那么最后就是为我们的Feign接口生成代理类,在这里Feign提供了一个InvocationHandlerFactory工厂来生成

static final class Default implements InvocationHandlerFactory {    @Override    public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {      return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);    }  }

通过源码可以看到,Feign是使用jdk的代理方式来生成一个代理对象FeignInvocationHandler

static class FeignInvocationHandler implements InvocationHandler {    private final Target target;    private final Map<Method, MethodHandler> dispatch;    FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {      this.target = checkNotNull(target, "target");      this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);    }}

3.总结

对于Feign类build的流程已经分析完了,接下来在回顾一下构建的过程
在这里插入图片描述

其中使用到的类有如下

  1. Feign -> ReflectiveFeign:builder类
  2. ParseHandlersByName:解析方法上的注解并生成Map映射关系
  3. MethodMetadata:方法解析后对象
  4. BuildTemplateByResolvingArgs:包装了MethodMetadata以及编码器等
  5. SynchronousMethodHandler:包装了MethodMetadata、Template以及解码器等调用参数
  6. InvocationHandlerFactory:Feign的代理工厂类
  7. FeignInvocationHandler:Feign为接口生成的代理对象

最后贴上newInstance的源码

public <T> T newInstance(Target<T> target) {
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();

    for (Method method : target.type().getMethods()) {
      if (method.getDeclaringClass() == Object.class) {
        continue;
      } else if (Util.isDefault(method)) {
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        defaultMethodHandlers.add(handler);
        methodToHandler.put(method, handler);
      } else {
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
      }
    }
    InvocationHandler handler = factory.create(target, methodToHandler);
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] {target.type()}, handler);

    for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
      defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
  }
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值