@Author:zxw
@Email:502513206@qq.com
目录
-
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的子类ReflectiveFeignreturn 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个抽象方法让子类实现 ```java /**- 解析类上的注解 */ 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个方法解析类、方法、参数上的注解。通过这套解析流程,我们也能定义自己的注解进行解析了。<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/12949707/1643477916856-ec3378a4-8141-4452-9908-9e4ad287c6fb.png#clientId=ub9635a25-4893-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=735&id=u5468ef92&margin=%5Bobject%20Object%5D&name=image.png&originHeight=735&originWidth=639&originalType=binary&ratio=1&rotation=0&showTitle=false&size=25924&status=done&style=none&taskId=u4ebd8921-5e1c-4370-b4c1-07875be72d1&title=&width=639)<br />具体的方法调用如下,可以看到最终生成了一个List<MethodMetadata> ```java 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为我们提供不同的模板工厂类。
前面都是封装了方法的基本参数,我们在发起调用时还需要Client客户端等参数,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); }
MethodHandler
作为方法请求的执行器。 ```java MethodHandler -> SynchronousMethodHandler
public 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
```java
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的流程已经分析完了,接下来在回顾一下构建的过程
其中使用到的类有如下
- Feign -> ReflectiveFeign:builder类
- ParseHandlersByName:解析方法上的注解并生成Map映射关系
- MethodMetadata:方法解析后对象
- BuildTemplateByResolvingArgs:包装了MethodMetadata以及编码器等
- SynchronousMethodHandler:包装了MethodMetadata、Template以及解码器等调用参数
- InvocationHandlerFactory:Feign的代理工厂类
- 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;
}