你现在没有决定的权利,但你有决定未来的权利
–> 返回专栏总目录 <–
代码下载地址:https://github.com/f641385712/feign-learning
前言
通过前几篇文章,能够发现有个对象我们会频繁打交道,它就是Feign的请求模版对象RequestTemplate
。
feign.template.Template
以及它的4个子模版都已经了解过了,体会到了模版设计的高扩展性和高弹性。而此处的RequestTemplate
稍有不同,可以理解它是一个聚合,聚合有多种模版、参数、值从而提供转向标准请求对象feign.Request
的能力。
正文
我们知道一个简单的实例方法RequestTemplate#request()
就完成了模版对标准请求对象的转换,那么在它之前、之中、之后都做了什么呢?
RequestTemplate
它是HTTP目标的请求生成器,最终会被转换为feign.Request
,所以也可以理解为它是feign.Request
的模版配置对象。
RequestTemplate
提供了非常多的配置选项,并且提供了组装、解析的能力,为最终转换为feign.Request
提供支持。
可以把该类理解为UriTemplate
的一个增强版,处理URI外,还有Headers、Body等更多的信息来支持一个标准的Http请求。
源码解析
@SuppressWarnings({"WeakerAccess", "UnusedReturnValue"})
public final class RequestTemplate implements Serializable {
// 查询参数:key是name,QueryTemplate表示遵循rfc6570规范的模版
private final Map<String, QueryTemplate> queries = new LinkedHashMap<>();
private final Map<String, HeaderTemplate> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
private UriTemplate uriTemplate;
// 注意:此处target表示的是最终准备请求的URL,并非feign.Target
private String target;
// 拼接在url后面的片段,一般表示锚点。如#bootmark
private String fragment;
// mark the new template resolved
private boolean resolved = false;
private HttpMethod method;
// 请注意:这里使用的更加通用的UTF-8编码
private transient Charset charset = Util.UTF_8;
// 请求Body体 内包含bodyTemplate字符串
private Request.Body body = Request.Body.empty();
// 这两个属性就不解释了
private boolean decodeSlash = true;
private CollectionFormat collectionFormat = CollectionFormat.EXPLODED;
}
通过这些属性能够发现,它代表着一个目标方法的所有元数据信息,是组装一个标准Http请求的必备信息。
RequestTemplate:
// 构造器:空构造是它唯一非@Deprecated的public构造器,但一般不用它来构建实例
// 此构造器唯一调用处:`MethodMetadata`
public RequestTemplate() {
super();
}
// 将自己转换为标准的Request对象
// 转换之前:请确保所有的template模版都已经被解析过了 resolved = true
public Request request() {
if (!this.resolved) {
throw new IllegalStateException("template has not been resolved.");
}
return Request.create(this.method, this.url(), this.headers(), this.requestBody());
}
// 给请求模版指定Http方法
public RequestTemplate method(HttpMethod method) {
checkNotNull(method, "method");
this.method = method;
return this;
}
// 这个方法最特别的地方在于:给uriTemplate进行了重新赋值->把编码后的结果赋值给它
public RequestTemplate decodeSlash(boolean decodeSlash) {
this.decodeSlash = decodeSlash;
this.uriTemplate =
UriTemplate.create(this.uriTemplate.toString(), !this.decodeSlash, this.charset);
return this;
}
// 该方法作用:给request设置一个uri
// 这里会解析UriTemplate,并且拼接好所有的部分(包括查询参数、锚点等,但domain此处不管哦~)
public RequestTemplate uri(String uri) { ... }
public RequestTemplate uri(String uri, boolean append) { ... }
public String url() { ... }
public String path() { ... }
// 它是组装所有的所有的所有的部分,拼接成一个完成的URL
// 形如:http://www.baidu.com/aaa/bbb?name=YourBatman#mark
public RequestTemplate target(String target) { ... }
// 获取所有template的所有参数名称们,也就是key们
public List<String> variables() { ... }
// Body Template to resolve.
public String bodyTemplate() {
return body.bodyTemplate();
}
// 查询参数
public String queryLine() { ... }
这是RequestTemplate
提供的一些基本访问方法,相对简单。接下来讲述的将是它的重难点:根据参数resolve()处理模版。
RequestTemplate:
// 使用提供的变量值替换解析所有表达式。
// 注意:变量值是一份,所有的模版都会解析哦
public RequestTemplate resolve(Map<String, ?> variables) {
StringBuilder uri = new StringBuilder();
// 特别注意:在基础上赋值一份出来,所以操作的是这份新的,return出去的也是新的哦
RequestTemplate resolved = RequestTemplate.from(this);
... //1、解析uriTemplate,·uriTemplate.expand(variables)·
// 2、解析查询参数模版,这块相对复杂。用&连接起来
if (!this.queries.isEmpty()) {
// 先处理模版:一个模版一个模版的处理
Iterator<QueryTemplate> queryTemplates = this.queries.values().iterator();
while (queryTemplates.hasNext()) {
...
String queryExpanded = queryTemplate.expand(variables);
...
query.append("&");
}
...
// 设值查询参数
resolved.uri(uri.toString());
//再处理请求头模版
if (!this.headers.isEmpty()) {
for (HeaderTemplate headerTemplate : this.headers.values()) {
...
String headerValues = header.substring(header.indexOf(" ") + 1);
...
}
}
// 处理body模版
// 说明:body里持有BodyTemplate的引用,所以底层依旧是bodyTemplate.expand(variables)
resolved.body(this.body.expand(variables));
// 标志位:标记为已处理
resolved.resolved = true;
return resolved;
}
}
RequestTemplate#resolve()
是其最重要的一个方法:用于填充四大模版Template
:UriTemplate、QueryTemplate、HeaderTemplate、BodyTemplate
。在转换为feign.Request
之前,肯定会使用resolve()此方法完成数据填充。
RequestTemplate.Factory
Feign的设计上有个特点:几乎所有的实例都不采用直接new的方式,而是使用工厂方法来创建。RequestTemplate.Factory
顾名思义,它就是用来构建/创建一个RequestTemplate
实例的工厂接口。
interface Factory {
RequestTemplate create(Object[] argv);
}
接口示意很简单:根据参数数组argv
构建出一个RequestTemplate
实例。它有如下实现类:
注意:这些实现类均写在ReflectiveFeign
里面,并且均为private static
的,所以均为内部实现,是一种高内聚的表现。
该接口主要作用是根据最原始的数据:方法参数、MethodMetadata元数据等,完成到RequestTemplate
实例的封装,掌握了它关于数据编码的逻辑将拨开云雾见青天。
BuildTemplateByResolvingArgs
它并不是一个抽象类,作用是根据args、MethodMetadata
、QueryMapEncoder
构建出一个请求模版对象。
private static class BuildTemplateByResolvingArgs implements RequestTemplate.Factory {
private final QueryMapEncoder queryMapEncoder;
protected final MethodMetadata metadata;
// 这里的Expander指的是feign.Param.Expander,也就是由注解决定使用哪种Expander,默认是ToStringExpander
// 这里的key 数字类型:表示参数的index...
private final Map<Integer, Expander> indexToExpander = new LinkedHashMap<Integer, Expander>();
// 唯一构造器:给前两个属性赋值。并且并且并且根据这两个属性
// 计算出indexToExpander的值,然后存储着
private BuildTemplateByResolvingArgs(MethodMetadata metadata, QueryMapEncoder queryMapEncoder) {
this.metadata = metadata;
this.queryMapEncoder = queryMapEncoder;
// 计算
...
for (Entry<Integer, Class<? extends Expander>> indexToExpanderClass : metadata.indexToExpanderClass().entrySet()) {
...
indexToExpander.put(indexToExpanderClass.getKey(), indexToExpanderClass.getValue().newInstance());
...
}
}
}
通过构造器收集到了每个方法参数应该使用的Expander
类型,这是在“启动阶段”,也就是在构建阶段完成的。
接下来自然而然就是最重要的接口方法的实现:
BuildTemplateByResolvingArgs:
@Override
public RequestTemplate create(Object[] argv) {
// 同样的,先拷贝一份出来RequestTemplate出来,但这并不是最终return的
RequestTemplate mutable = RequestTemplate.from(metadata.template());
// varBuilder装载所有的k-v
Map<String, Object> varBuilder = new LinkedHashMap<String, Object>();
for (Entry<Integer, Collection<String>> entry : metadata.indexToName().entrySet()) {
int i = entry.getKey();
Object value = argv[i];
if (value != null) {
...
for (String name : entry.getValue()) {
varBuilder.put(name, value);
}
}
}
// 调用RequestTemplate#resolve()方法,得到一个全新的实例
RequestTemplate template = resolve(argv, mutable, varBuilder);
// 支持@QueryMap
if (metadata.queryMapIndex() != null) {
...
}
// 支持@HeaderMap
if (metadata.headerMapIndex() != null) {
...
}
return template;
}
// 本类默认实现,直接嗲用RequestTemplate填充模版
// 两个子类对此方法均由复写...
protected RequestTemplate resolve(Object[] argv,
RequestTemplate mutable,
Map<String, Object> variables) {
return mutable.resolve(variables);
}
可以看到,这里解析了4大Template,共用一份数值对象variables
。
本创建方法的作用描述:解析元数据对象MethodMetadata
,把对应参数填进RequestTemplate
各个属性里,最终依赖于RequestTemplate#resolve()
方法完成解析。
说明:
MethodMetadata
我们知道它的数据均来自于对注解的解析,这个注解可以是源生注解,也可以是你自定义的注解信息(比如Spring MVC的扩展)
BuildEncodedTemplateFromArgs
它在父类的基础上,加入了解码器feign.codec.Encoder
,因此对于方法体Body的参数,它可以先让解码器解码后(放进body里)再执行父类的解析逻辑。
private static class BuildEncodedTemplateFromArgs extends BuildTemplateByResolvingArgs {
private final Encoder encoder;
... // 省略构造器
// 复写父类的resolve()解析方法,让解码器参与进来
@Override
protected RequestTemplate resolve(Object[] argv,
RequestTemplate mutable,
Map<String, Object> variables) {
Object body = argv[metadata.bodyIndex()];
checkArgument(body != null, "Body parameter %s was null", metadata.bodyIndex());
...
encoder.encode(body, metadata.bodyType(), mutable);
...
}
}
它要求bodyIndex != null
才会生效,对应的encoder
什么时候生效的逻辑之前也说过,此处可重复一次:没有标注@Param
注解时候,才会交给编码器编码进body体里。
BuildFormEncodedTemplateFromArgs
作用几乎同上,也是利用Encoder
进行了编码,不过它会把Map<String, Object> variables
参数值们以from表单的形式提交,这便它的区别。
关于元数据什么时候提取为Body参数,什么时候当作Form表单参数,请参考feign.Contract
详解。
feign.Request
这是Feign
的标准的、不可变的Http请求实体。
public final class Request {
// 这些参数很容易理解:它就是Http请求的各个部分
private final HttpMethod httpMethod;
private final String url;
private final Map<String, Collection<String>> headers;
private final Body body;
... // 省略构造器。构造器是私有的,并不能直接构造实例对象,而是提供了静态方法
// 获取编码 -> 来自于body
public Charset charset() {
return body.encoding;
}
// body的二进制表示形式 --> 通用
public byte[] body() {
return body.data;
}
... // 省略其它get方法(并没有set方法哦~~~)
}
从属性中能看出它是一个标准的Http请求对象,那么如何构建它呢?这就需要下面这些静态工厂方法:
Request:
// 特点:method使用字符串,body使用二进制 + 编码的方式
// Body.encoded(body, charset)可以把二进制 + 编码 转换为一个Body对象
// 所以这个构造器是面向用户良好的创建方法,因此使用也较多
public static Request create(String method,
String url,
Map<String, Collection<String>> headers,
byte[] body,
Charset charset) {
checkNotNull(method, "httpMethod of %s", method);
HttpMethod httpMethod = HttpMethod.valueOf(method.toUpperCase());
return create(httpMethod, url, headers, body, charset);
}
...
// 最终调用都是Request唯一的构造器:new Request(httpMethod, url, headers, body);
// Http标准方法的枚举
public enum HttpMethod {
GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH
}
// 发送请求的选项:链接超时(默认10s),读取超时(默认60秒)
public static class Options {
private final int connectTimeoutMillis;
private final int readTimeoutMillis;
private final boolean followRedirects;
...
}
Request
对象通过静态工厂方法创建,并且不可变。
在这其中,最有文章可循的当属Body对象:
Body
顾名思义,它代表Http的请求体,它也是Request的一个内部类,不过是public的,允许外部完成构建。
public static class Body {
// 数据用字节数组表述,可以表示任意类型,最为通用
private final byte[] data;
private final Charset encoding;
// 但是请注意:body内容可能来自于模版,这是需要区分的一点
// 很显然,bodyTemplate是可以为null的
private final BodyTemplate bodyTemplate;
... // 省略私有构造器
// 根据variables,经过BodyTemplate产生一个新的Body对象
// 这是根据模版构建Body的一种方式:使用较多
public Request.Body expand(Map<String, ?> variables) {
if (bodyTemplate == null)
return this;
return encoded(bodyTemplate.expand(variables).getBytes(encoding), encoding);
}
// 直接传值构建:静态方法
public static Request.Body encoded(byte[] bodyData, Charset encoding) {
return new Request.Body(bodyData, encoding, null);
}
// 静态方法:空消息体 data为null 编码类型为null...
public static Body empty() {
return new Request.Body(null, null, null);
}
...
// 获取body的二进制表示形式。注意:方法名不叫getBytes
public byte[] asBytes() {
return data;
}
// 转为String的前提条件:data和encoding都不能为null
public String asString() {
return (encoding != null && data != null) ? new String(data, encoding) : "Binary data";
}
public int length() {
return data != null ? data.length : 0;
}
// 拿到Body的模版(可能为null)
public String bodyTemplate() {
return (bodyTemplate != null) ? bodyTemplate.toString() : null;
}
}
它最大的特点是把BodyTemplate
放在了本处,而并没有放在RequestTemplate
里,RequestTemplate
选择放了Body而非BodyTemplate
,这点需要引起注意。
总结
本文介绍了Feign请求相关的最重要两个对象:RequestTemplate
和Request
。前者负责各种属性值的收集、模版的解析等逻辑,相对复杂;后者可以理解为一个不可更改的POJO,相对简单。
声明
原创不易,码字不易,多谢你的点赞、收藏、关注。把本文分享到你的朋友圈是被允许的,但拒绝抄袭
。你也可【左边扫码/或加wx:fsx641385712】邀请你加入我的 Java高工、架构师 系列群大家庭学习和交流。
- [享学Feign] 一、原生Feign初体验,Netflix Feign还是Open Feign?
- [享学Feign] 二、原生Feign的注解介绍及使用示例
- [享学Feign] 三、原生Feign的核心API详解(一):UriTemplate、HardCodedTarget…
- [享学Feign] 四、原生Feign的核心API详解(二):Contract、SynchronousMethodHandler…
- [享学Feign] 五、原生Feign的编码器Encoder、QueryMapEncoder
- [享学Feign] 六、原生Feign的解码器Decoder、ErrorDecoder
转载:https://blog.csdn.net/f641385712/article/details/104291545