基于 SynchronousMethodHandler
处理器,来看下执行 http 请求的过程和decode逻辑(仅保留关键代码)
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
// 创建请求
Request request = targetRequest(template);
// 发起请求,并构建响应
Response response = client.execute(request, options);
response = response.toBuilder()
.request(request)
.requestTemplate(template)
.build();
// decoder != null,执行 decoder 逻辑
if (decoder != null) {
return responseInterceptor.aroundDecode(new InvocationContext(decoder, metadata.returnType(), response));
}
// 否则执行 asyncResponseHandler
CompletableFuture<Object> resultFuture = new CompletableFuture<>();
asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response,
metadata.returnType(), elapsedTime);
}
关于 decoder 属性来自于构造注入,如下:
private SynchronousMethodHandler(Target<?> target, Client client, Retryer retryer,
List<RequestInterceptor> requestInterceptors, ResponseInterceptor responseInterceptor,
Logger logger, Logger.Level logLevel, MethodMetadata metadata,
RequestTemplate.Factory buildTemplateFromArgs, Options options,
Decoder decoder, ErrorDecoder errorDecoder, boolean dismiss404,
boolean closeAfterDecode, ExceptionPropagationPolicy propagationPolicy,
boolean forceDecoding) {
// 。。。省略N行
if (forceDecoding) {
// internal only: usual handling will be short-circuited, and all responses will be passed to
// decoder directly!
this.decoder = decoder;
this.asyncResponseHandler = null;
} else {
this.decoder = null;
this.asyncResponseHandler = new AsyncResponseHandler(logLevel, logger, decoder, errorDecoder,
dismiss404, closeAfterDecode, responseInterceptor);
}
}
常规情况下,使用 Feign.Builder 初始化 SynchronousMethodHandler 时,forceDecoding 传入为 false,也就是说构造参数中的 decoder 即使有值,也没有直接赋值给 this.decoder,而是传入到了 new AsyncResponseHandler。所以在上面执行 #executeAndDecode 方法时,实际上是调用了 asyncResponseHandler.handleResponse
void handleResponse(CompletableFuture<Object> resultFuture, String configKey, Response response, Type returnType, long elapsedTime) {
// 。。。省略N行
else if (response.status() >= 200 && response.status() < 300) {
if (isVoidType(returnType)) {
resultFuture.complete(null);
} else {
final Object result = decode(response, returnType);
shouldClose = closeAfterDecode;
resultFuture.complete(result);
}
}
}
当http状态码在 [200, 300) 中间时(常规我们都是 200正常 状态码)
则进行 isVoidType(returnType) 判断,如果是 void 返回值,则 resultFuture.complete(null); 就完事了,不会进入 decode !!!
解决方案:
1)返回值不使用 void 【不推荐】
返回值不使用void,而是提供一个void包装类,在decoder中针对包装类直接跳过
不推荐原因:对于新项目而言还可以,老项目已经存在的void方法,还需要改造成本
2)强制启用 forceDecoding【不推荐】
在创建代理对象前,可以执行自定义,如下:
// customizers
applyBuildCustomizers(context, builder);
private void applyBuildCustomizers(FeignContext context, Feign.Builder builder) {
Map<String, FeignBuilderCustomizer> customizerMap =
context.getInstances(contextId, FeignBuilderCustomizer.class);
if (customizerMap != null) {
customizerMap.values().stream()
.sorted(AnnotationAwareOrderComparator.INSTANCE)
.forEach(feignBuilderCustomizer -> feignBuilderCustomizer.customize(builder));
}
additionalCustomizers.forEach(customizer -> customizer.customize(builder));
}
也就是说,实现 FeignBuilderCustomizer
接口,即可在创建代理对象前拿到 builder
/**
* Internal - used to indicate that the decoder should be immediately called
*/
Builder forceDecoding() {
this.forceDecoding = true;
return this;
}
由于 forceDecoding() 非 public,因此需要使用反射来设置值
public class PilotFeignDecodeBuilderCustomizer implements FeignBuilderCustomizer {
@Override
public void customize(Feign.Builder builder) {
ReflectUtil.setFieldValue(builder, "forceDecoding", true);
}
}
不推荐原因:asyncResponseHandler 无法被执行,则其中的日志记录、body.close() 等都需要decoder来做
3、代理 Contract【推荐】
和 1)返回值不使用 void 的思想一致,只不过对于 void 或者 Void 的返回值,做了一层自动包装
原理:在 Feign.Builder 中存在 Contract 属性,创建 FeignContractHolder 对 Contract 做代理,这样在创建 FeignClient Proxy 过程中,对于 target 的解析工作则交给 FeignContractHolder 来做,在FeignContractHolder 中仍旧使用原来的Contract 进行解析,FeignContractHolder 则对解析结果做遍历,如果存在 void 或者 Void,则设定为包装类
第一步:创建一个 FeignContractHolder,用来做 contract 代理,如下:
import feign.Contract;
@AllArgsConstructor
public class FeignContractHolder implements Contract {
// 代理对象
private final Contract delegate;
@Override
public List<MethodMetadata> parseAndValidateMetadata(Class<?> targetType) {
List<MethodMetadata> metadatas = delegate.parseAndValidateMetadata(targetType);
// 循环遍历处理,如果是 void 或者 Void,则包装一下
if (CollUtil.isNotEmpty(metadatas)) {
metadatas.forEach(item -> {
if (item.returnType() == void.class || item.returnType() == Void.class) {
item.returnType(VoidWrapper.class);
}
});
}
return metadatas;
}
}
第二步:在 FeignBuilderCustomizer 中创建PilotFeignContractHolder
import feign.Contract;
import feign.Feign;
import org.springframework.cloud.openfeign.FeignBuilderCustomizer;
/**
* Feign Builder Customizer.
* <p>
* 1、启用 PilotFeignContractHolder,用来代理 Contract
*/
public class FeignDecodeVoidBuilderCustomizer implements FeignBuilderCustomizer {
@Override
public void customize(Feign.Builder builder) {
// 反射拿到 contract
Contract originalContract = (Contract) ReflectUtil.getFieldValue(builder, "contract");
// 创建 PilotFeignContractHolder,用于代理 originalContract
builder.contract(new PilotFeignContractHolder(originalContract));
}
}