今天在项目中遇见了Feign.Builder并通过内部的拦截器对feign接口做了一些配置,比较感兴趣,所以去特地了解了一下Feign.Builder的简要原理,在此记录一下,后面会慢慢去尝试读取源码并分享:
feign.Feign它是最上层的API,是使用者直接使用的对象,它能完成对接口生成动态代理对象,从而很方面的让你面向接口编程,而不用太过例会Feign内部的实现细节。
Feign的目的是简化针对rest的Http Api的开发。在实现中,Feign是一个用于生成目标实例Feign.newInstance()的工厂,这个生成的实例便是接口的代理对象。
该类是个抽象类:
public abstract class Feign {
// 唯一的public的抽象方法,用于为目标target创建一个代理对象实例
public abstract <T> T newInstance(Target<T> target);
// -----------------静态方法-----------------
// 工具方法,生成configKey
// MethodMetadata#configKey属性的值就来自于此方法
public static String configKey(Class targetType, Method method) { ... }
// Feign的实例统一,有且只能通过builder构建,文下有详解,它是重点
public static Builder builder() {
return new Builder();
}
}
feign.Feign它有且仅有一个唯一实现类ReflectiveFeign,但这个实现类并不面向使用者,使用者只需用Builder构建,面向接口/抽象类编程足矣。
在接触ReflectiveFeign之前,我认为很有必要了解下它。
该类有且仅提供一个方法:将feign.Target转为Map
首先展示一下类和大概的理解,然后我们细细的理解:
static final class ParseHandlersByName {
// 提取器
private final Contract contract;
private final Options options;
private final Encoder encoder;
private final Decoder decoder;
private final ErrorDecoder errorDecoder;
private final QueryMapEncoder queryMapEncoder;
// 这个工厂只产生SynchronousMethodHandler实例
// 从这也很好理解:所有的Method最终的处理器都是SynchronousMethodHandler
private final SynchronousMethodHandler.Factory factory;
... // 省略构造器
public Map<String, MethodHandler> apply(Target key) {
// 通过Contract提取出该类所有方法的元数据信息:MethodMetadata
// 它会解析注解,不同的实现支持的注解是不一样的
List<MethodMetadata> metadata = contract.parseAndValidatateMetadata(key.type());
// 一个方法一个方法的处理,生成器对应的MethodHandler处理器
// 上篇文章有讲过,元数据都是交给RequestTemplate.Factory去构建成为一个请求模版的
Map<String, MethodHandler> result = new LinkedHashMap<>();
for (MethodMetadata md : metadata) {
// 这里其实我觉得使用接口RequestTemplate.Factory buildTemplate更加的合适
BuildTemplateByResolvingArgs buildTemplate;
// 针对不同元数据参数,调用不同的RequestTemplate.Factory实现类完成处理
if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
// 若存在表单参数formParams,并且没有body模版,说明请求的参数就是简单的Query参数。那就执行表单形式的构建
buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
} else if (md.bodyIndex() != null) {
// 若存在body,那就是body喽
buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
} else {
// 否则就是普通形式:查询参数构建方式
buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder);
}
// 通过factory.create创建出MethodHandler实例,缓存结果
result.put(md.configKey(), factory.create(...));
return result;
}
}
在build方法中我们创建的第二个重要组件。
观察它的apply方法可以猜到ParseHandlersByName的作用就是我们传入Target(封装了我们的模拟接口,要访问的域名),返回这个接口下的各个方法,对应的执行HTTP请求需要的一系列信息。
结果Map
private final Contract contract;
private final Options options;
private final Encoder encoder;
private final Decoder decoder;
private final ErrorDecoder errorDecoder;
private final SynchronousMethodHandler.Factory factory;
ParseHandlersByName的创建除了我们在build方法里面创建好的SynchronousMethodHandler.Factory(https://www.jianshu.com/p/b11af0b51a72)以外。还有其他几个组件:
所以前面五种组件的作用加起来是在干嘛呢?准备一个http请求执行前的各种数据,定义http执行后对于结果的各种处理逻辑。
所以这些组件的作用是 “处理每个HTTP执行前后的事情”
接着加上我们传入SynchronousMethodHandler.Factory的各种组件,还记得他们的作用么:“执行HTTP请求”。
所以当ParseHandlersByName这个组件创建好的时候,我们就已经准备好了执行这个HTTP请求所需要的一切了。
所以build方法的前2个组件包含了 “执行HTTP请求”和“处理每个HTTP执行前后的事情”的能力。最后合并到了ParseHandlersByName中。
ParseHandlersByName最后把以上的组件都封装为了一个结果集Map
一句话总结ParseHandlersByName的作用:准备好执行这个HTTP请求所需要的一切,为指定接口类型的每个方法生成其对应的MethodHandler处理器(可能是默认方法直接执行处理、也可能是发送http请求去处理)。
该步骤中,涉及到元数据提取、编码、模版数据填充等动作,均交给不同的组件去完成,组件化的设计有助于模块化、可插拔等特性。
Reflective中文翻译过来是反射的。
它是feign.Feign的唯一实现,但是它并不提供public的构造器,因此外部并不能直接构建它。
public class ReflectiveFeign extends Feign {
// 文上有解释次类的作用:提供方法,给接口每个方法生成一个处理器
private final ParseHandlersByName targetToHandlersByName;
// 它是调度中心
private final InvocationHandlerFactory factory;
private final QueryMapEncoder queryMapEncoder;
... // 省略构造器
}
准备好几大“工具”后,下面开始干活了:实现父类newInstance()抽象方法,给Target生成一个实例(实为代理对象)返回。
ReflectiveFeign:
@Override
public <T> T newInstance(Target<T> target) {
// 拿到该接口所有方法对应的处理器的Map
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
// 真要处理调用的Method对应的处理器Map
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<>();
// 简单的说:对接口默认方法作为处理方法提供支持,不用发http请求喽,一般可忽略
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<>();
// 查阅该接口所有的Method。
// .getMethods()会获取接口的所有的public方法,包括default方法哦(因为defualt方法也是public的)
for (Method method : target.type().getMethods()) {
... //如果是Object的方法,直接continue。method.getDeclaringClass() == Object.class
... // 如果是Defualt默认方法,那该方法就用DefaultMethodHandler去处理
... // 否则,就nameToHandler.get(Feign.configKey(target.type(), method))用它去处理
}
// 为该目标接口类型创建一个InvocationHandler
// 它持有methodToHandler这个Map,负责全局调度所有的Method方法
// 并且为此接口创建一个代理对象
InvocationHandler handler = factory.create(target, methodToHandler);
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[] {target.type()}, handler);
// 根据前文知道:要想调用DefaultMethodHandler#invoke之前,必须先bind
// 因此这里千万别忘了来一次bind绑定动作
for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
就这样,通过ReflectiveFeign的newInstance方法给该接口创建出来了一个代理实例,它使用的处理器是FeignInvocationHandler。
至于ReflectiveFeign为接口生成动态代理实例的步骤在之后细读源码时再做分享。
那么最终就到了我们项目中自动配置常用的Feign.Builder了:
了解了ReflectiveFeign的作用,它作为feign.Feign的唯一实现,但其实我们在使用过程中几乎不会使用它和接触它,因为构建实例均通过工厂来进行,这便是feign.Feign.Builder:
public static class Builder {
// 请求模版的拦截器,默认的空的,木有哦,你可以自定义,在builder的时候加进来
private final List<RequestInterceptor> requestInterceptors = new ArrayList<>();
// 这是Feign自己的日志级别,默认不输出日志
// 因此若你要打印请求日志,这里级别需要调高
private Logger.Level logLevel = Logger.Level.NONE;
// 默认使用的日志记录器,也是不作为
private Logger logger = new NoOpLogger();
// 默认使用的提取器,就是支持@RequestLine源生注解的这种
private Contract contract = new Contract.Default();
// 默认使用JDK的`HttpURLConnection`发送请求,并且是非SSL加密的
private Client client = new Client.Default(null, null);
// 请务必注意:默认情况下Feign是开启了重试的
// 100ms重试一次,一共重试5次。最长持续1s钟
// 在生产环境下,重试请务必慎用
private Retryer retryer = new Retryer.Default();
// 默认10s链接超时,60s读取超时
private Options options = new Options();
// 默认编码器:只支持String类型的编码
// 请注意:编码器的生效时机哦~~~(没有标注@Param注解会交给编码器处理)
private Encoder encoder = new Encoder.Default();
// 默认只能解码String类型和字节数组类型
private Decoder decoder = new Decoder.Default();
// 支持把@QueryMap标注在Map or Bean前面
// Bean需要有public的get方法才算一个属性
private QueryMapEncoder queryMapEncoder = new QueryMapEncoder.Default();
// 把异常、错误码包装为FeignException异常向上抛出
private ErrorDecoder errorDecoder = new ErrorDecoder.Default();
// FeignInvocationHandler是它唯一的实现
private InvocationHandlerFactory invocationHandlerFactory = new InvocationHandlerFactory.Default();
// 默认不会解码404
private boolean decode404;
private boolean closeAfterDecode = true;
// 异常传播策略:NONE代表不包装 不处理,直接抛
private ExceptionPropagationPolicy propagationPolicy = NONE;
}
这个Builder构建器的内容非常丰富,是对前面讲解几乎所有组件的一个总结。
Builder的方法效果同set方法,没什么太多可说的,但这里我摘出几个,相对重视一下:
Feign.Builder:
// 这两个方法调用其一,方法二是一的加强版
public Builder decoder(Decoder decoder) {
this.decoder = decoder;
return this;
}
public Builder mapAndDecode(ResponseMapper mapper, Decoder decoder) {
this.decoder = new ResponseMappingDecoder(mapper, decoder);
return this;
}
// 拦截器一个个添加,可以多次调用添加多个
public Builder requestInterceptor(RequestInterceptor requestInterceptor) {
this.requestInterceptors.add(requestInterceptor);
return this;
}
// 请注意:如果一次性添加多个,那么此方法相当于set方法哦,并不是add
public Builder requestInterceptors(Iterable<RequestInterceptor> requestInterceptors) {
this.requestInterceptors.clear();
for (RequestInterceptor requestInterceptor : requestInterceptors) {
this.requestInterceptors.add(requestInterceptor);
}
return this;
}
// 构建:返回一个ReflectiveFeign实例
public Feign build() {
...
return new ReflectiveFeign( ... );
}
创建接口代理实例(代码示例)
有了Builder构建器,这样得到接口的代理实例,就可这么做啦:
@Test
public void fun1() {
Feign feign = Feign.builder().build();
DemoClient client = feign.newInstance(new Target.HardCodedTarget<>(DemoClient.class, "http://localhost:8080"));
System.out.println(client);
}
执行程序,控制台输出:
HardCodedTarget(type=DemoClient, url=http://localhost:8080)
但是,这样使用其实是有一定弊端的,且有一定使用门槛:
使用Builder构建时,使用者还需知道Feign.newInstance这个API
使用者必须知道Target的实现类HardCodedTarget才能完成构建
那么,作为一个合格的Builder,能否能屏蔽一些底层API,做到一步到位呢?
答案是肯定的,builder还额外提供了如下两个方法:
Feign.Builder
// 很显然:方法一对调用者更加友好,因为Class和URL这些是使用者没有门槛的
// 方法一依赖于方法二,它内部帮你构建Target的实例
public <T> T target(Class<T> apiType, String url) {
return target(new HardCodedTarget<T>(apiType, url));
}
public <T> T target(Target<T> target) {
return build().newInstance(target);
}
那么对于上例,其实完全可以非常简单的一步到位,简单都如下代码所示:
@Test
public void fun2(){
DemoClient client = Feign.builder().target(DemoClient.class, "http://localhost:8080");
System.out.println(client);
}
运行程序,结果同上。
因此这么做才是在生产上的推荐做法
那么至此我们也就从源码的角度上大致了解了一下Feign.Builder的设计来由以及各个组件和方法的大致含义。
六、RequestInterceptor
我们项目中在feign.builder()后还使用了requestInterceptor(RequestInterceptor requestInterceptor),那么很多人不知道RequestInterceptor 的用处,下面来介绍下:
我们可以从英文角度翻一下:RequestInterceptor其实就是Feign的拦截器
在使用feign做服务间调用的时候,如何修改请求的头部或编码信息呢,可以通过实现RequestInterceptor接口的apply方法,feign在发送请求之前都会调用该接口的apply方法,所以我们也可以通过实现该接口来记录请求发出去的时间点。