一、问题描述
1、服务端feign接口。
/**
* 上链接口
*/
@PostMapping(value = STORE_TO_CHAIN)
R<Void> storeDataToBlockChain(StoreDataToChainParamDTO param, @RequestPart(value = "files",required = false) List<MultipartFile> files);
2、客户端调用接口,服务端并未收到 StoreDataToChainParamDTO 对象的值。
================ Request Start ================
===> POST: /client/chain/store-to-chain Parameters: {"param":{"enterpriseSocialCode":null,"enterpriseId":null,"tenantId":null,"enterpriseName":null,"name":null,"certCardNumber":null,"industryType":null,"industryName":null,"region":null,"pluginType":null,"timestamp":null,"refSerialNumber":null,"type":null,"dataList":null},"files":null}
===Headers=== Accept: */*
===Headers=== Connection: Keep-Alive
===Headers=== User-Agent: okhttp/4.9.3
===Headers=== Host: 2.0.0.1:9500
===Headers=== Accept-Encoding: gzip
===Headers=== Content-Length: 2
===Headers=== Content-Type: application/json
================ Request End ================
二、原因分析
1、类 FeignInvocationHandler:调用feign接口的方法,实际调用的就是这个动态代理对象的 invoke() 方法。
FeignInvocationHandler.java
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("equals".equals(method.getName())) {
try {
Object otherHandler =
args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
return equals(otherHandler);
} catch (IllegalArgumentException e) {
return false;
}
} else if ("hashCode".equals(method.getName())) {
return hashCode();
} else if ("toString".equals(method.getName())) {
return toString();
}
// 基于dispatch 找到对应的 MethodHandler,调用invoke方法
return dispatch.get(method).invoke(args);
}
2、dispatch.get(method).invoke(args) 调用类 SynchronousMethodHandler 的 invoke() 方法。
SynchronousMethodHandler.java
@Override
public Object invoke(Object[] argv) throws Throwable {
// 基于入参,构建出 http request template
RequestTemplate template = buildTemplateFromArgs.create(argv);
Options options = findOptions(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
// 请求和解码
return executeAndDecode(template, options);
} catch (RetryableException e) {
try {
retryer.continueOrPropagate(e);
} catch (RetryableException th) {
Throwable cause = th.getCause();
if (propagationPolicy == UNWRAP && cause != null) {
throw cause;
} else {
throw th;
}
}
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
3、buildTemplateFromArgs.create(argv) 调用类 BuildTemplateByResolvingArgs 的 create() 方法。
BuildTemplateByResolvingArgs.java
@Override
public RequestTemplate create(Object[] argv) {
RequestTemplate mutable = RequestTemplate.from(metadata.template());
mutable.feignTarget(target);
if (metadata.urlIndex() != null) {
int urlIndex = metadata.urlIndex();
checkArgument(argv[urlIndex] != null, "URI parameter %s was null", urlIndex);
mutable.target(String.valueOf(argv[urlIndex]));
}
Map<String, Object> varBuilder = new LinkedHashMap<String, Object>();
for (Entry<Integer, Collection<String>> entry : metadata.indexToName().entrySet()) {
int i = entry.getKey();
Object value = argv[entry.getKey()];
if (value != null) { // Null values are skipped.
if (indexToExpander.containsKey(i)) {
value = expandElements(indexToExpander.get(i), value);
}
for (String name : entry.getValue()) {
varBuilder.put(name, value);
}
}
}
// 核心:组装template
RequestTemplate template = resolve(argv, mutable, varBuilder);
if (metadata.queryMapIndex() != null) {
// add query map parameters after initial resolve so that they take
// precedence over any predefined values
Object value = argv[metadata.queryMapIndex()];
Map<String, Object> queryMap = toQueryMap(value);
template = addQueryMapQueryParameters(queryMap, template);
}
if (metadata.headerMapIndex() != null) {
template =
addHeaderMapHeaders((Map<String, Object>) argv[metadata.headerMapIndex()], template);
}
return template;
}
4、1)类BuildTemplateByResolvingArgs、BuildFormEncodedTemplateFromArgs、BuildEncodedTemplateFromArgs 都实现了 resolve(argv, mutable, varBuilder) 方法;
2)走哪个实现类,是在生成代理对象的时候判断(类 ParseHandlersByName 的 apply() 方法);
3)根据 apply() 方法,resolve(argv, mutable, varBuilder) 会调用 BuildFormEncodedTemplateFromArgs 的 resolve() 方法,入参 argv 不会填充到 formVariables ,导致客户端的参数没有发送到服务端。
ParseHandlersByName.java
public Map<String, MethodHandler> apply(Target target) {
List<MethodMetadata> metadata = contract.parseAndValidateMetadata(target.type());
Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
for (MethodMetadata md : metadata) {
BuildTemplateByResolvingArgs buildTemplate;
if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
buildTemplate =
new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);
} else if (md.bodyIndex() != null || md.alwaysEncodeBody()) {
buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);
} else {
buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder, target);
}
if (md.isIgnored()) {
result.put(md.configKey(), args -> {
throw new IllegalStateException(md.configKey() + " is not a method handled by feign");
});
} else {
result.put(md.configKey(),
factory.create(target, md, buildTemplate, options, decoder, errorDecoder));
}
}
return result;
}
BuildFormEncodedTemplateFromArgs.java
@Override
protected RequestTemplate resolve(Object[] argv,
RequestTemplate mutable,
Map<String, Object> variables) {
Map<String, Object> formVariables = new LinkedHashMap<String, Object>();
for (Entry<String, Object> entry : variables.entrySet()) {
if (metadata.formParams().contains(entry.getKey())) {
formVariables.put(entry.getKey(), entry.getValue());
}
}
try {
encoder.encode(formVariables, Encoder.MAP_STRING_WILDCARD, mutable);
} catch (EncodeException e) {
throw e;
} catch (RuntimeException e) {
throw new EncodeException(e.getMessage(), e);
}
return super.resolve(argv, mutable, variables);
}
}
BuildEncodedTemplateFromArgs.java
@Override
protected RequestTemplate resolve(Object[] argv,
RequestTemplate mutable,
Map<String, Object> variables) {
boolean alwaysEncodeBody = mutable.methodMetadata().alwaysEncodeBody();
Object body = null;
if (!alwaysEncodeBody) {
body = argv[metadata.bodyIndex()];
checkArgument(body != null, "Body parameter %s was null", metadata.bodyIndex());
}
try {
if (alwaysEncodeBody) {
body = argv == null ? new Object[0] : argv;
encoder.encode(body, Object[].class, mutable);
} else {
encoder.encode(body, metadata.bodyType(), mutable);
}
} catch (EncodeException e) {
throw e;
} catch (RuntimeException e) {
throw new EncodeException(e.getMessage(), e);
}
return super.resolve(argv, mutable, variables);
}
三、解决方案
1、把文件对象封装起来,resolve(argv, mutable, varBuilder) 会调用 BuildEncodedTemplateFromArgs 的 resolve() 方法。
@PostMapping(value = STORE_TO_CHAIN, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
R<Void> storeDataToBlockChain(StoreDataToChainParamDTO param);
2、序列化时list转换失败问题解决(客户端)
@Configuration
public class MultipartSupportConfig {
@Autowired
private ObjectFactory<HttpMessageConverters> messageConverters;
@Bean
public Encoder feignFormEncoder() {
return new SpringFormEncoder(new SpringEncoder(messageConverters));
}
}
3、解码(服务端)
public class StringToListMapConverter implements Converter<String, List<LinkedHashMap<String,Object>>> {
@Override
public List<LinkedHashMap<String, Object>> convert(String source) {
return JsonUtil.parse(source, new TypeReference<List<LinkedHashMap<String, Object>>>() {});
}
}
@Configuration
public class ConvertConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringToListMapConverter());
}
}