Open effiu opened 3 months ago
public class PostBodyParamParameterProcessor implements AnnotatedParameterProcessor {
private static final String JSON_TOKEN_START = "{";
private static final String JSON_TOKEN_END = "}";
private static final String JSON_TOKEN_START_ENCODED = "%7B";
private static final String JSON_TOKEN_END_ENCODED = "%7D";
private static final String QUOTA = "\"";
private static final Class<PostBodyParam> ANNOTATION = PostBodyParam.class;
@Override
public Class<? extends Annotation> getAnnotationType() {
return ANNOTATION;
}
@Override
public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) {
int parameterIndex = context.getParameterIndex();
PostBodyParam bodyParam = ANNOTATION.cast(annotation);
String name = bodyParam.value();
checkState(emptyToNull(name) != null, "PostBodyParam.value() was empty on parameter %s", parameterIndex);
MethodMetadata metadata = context.getMethodMetadata();
context.setParameterName(name);
Class<?> parameterType = method.getParameterTypes()[parameterIndex];
Map<Integer, Param.Expander> expander = metadata.indexToExpander();
// 这里根据不同的参数类型,拼接不同的字符串。例如数字和对象(json序列化后)不带引号,字符串带引号。
String value = JSON_TOKEN_START + name + JSON_TOKEN_END;
if (isNumber(parameterType)) {
expander.put(parameterIndex, new Param.ToStringExpander());
} else if (isString(parameterType)) {
expander.put(parameterIndex, new Param.ToStringExpander());
value = QUOTA + value + QUOTA;
} else {
// BigDecimal、集合类、数组会被认为是普通对象,json序列化.
expander.put(parameterIndex, JsonUtil::toJson);
}
String appendBody = appendBody(metadata.template().bodyTemplate(), name, value);
metadata.template().bodyTemplate(appendBody);
metadata.template().header("content-type", MediaType.APPLICATION_JSON_VALUE);
return true;
}
/**
* 这个判断,暂不支持复杂的Java类型,例如{@code BigDecimal}等. 如何后续需要支持,加上即可。<br/>
* 需要注意的是 {@code JsonUtil.toJson(BigDecimal)} 与 {@code BigDecimal.toString()}的区别。
*
* @see java.math.BigDecimal
* @param parameterType 参数类型
* @return 是否是数字
*/
private boolean isNumber(Class<?> parameterType) {
return NumberUtils.STANDARD_NUMBER_TYPES.contains(parameterType) || parameterType.isAssignableFrom(int.class)
|| parameterType.isAssignableFrom(long.class) || parameterType.isAssignableFrom(byte.class)
|| parameterType.isAssignableFrom(double.class) || parameterType.isAssignableFrom(float.class);
}
/**
* 是否是字符串,暂不考虑String的包装类。
*
* @see StringBuilder#toString()
* @see feign.Param.Expander
*
* @param parameterType 参数类型
* @return 是否是字符串
*/
private boolean isString(Class<?> parameterType) {
return parameterType.isAssignableFrom(String.class);
}
/**
* 这里 只能手动拼接字符串。因为如果用map的话,在多参数时,序列号和反序列化过程中,body中的{key}占位符,只能用"{}"表示。 <br/>
* 多了引号后,子对象就不再是json格式,而是字符串了。所以这里使用append拼接的方式。
*
* @param body
* @param key
* @param value
*/
private String appendBody(String body, String key, String value) {
StringBuilder builder = new StringBuilder();
if (StringUtils.hasText(body)) {
// 这里删除最后一个字符串,即:}/%7D
builder.append(body).delete(body.length() - 3, body.length()).append(",");
} else {
builder.append(JSON_TOKEN_START_ENCODED);
}
builder.append(QUOTA).append(key).append(QUOTA).append(":");
builder.append(value);
builder.append(JSON_TOKEN_END_ENCODED);
return builder.toString();
}
}`
BodyTemplate
is meant for at most, the simplest of use cases. Anything else should be done using an Encoder
instance. What you are trying is technically possible with BodyTemplate
, but as you've discovered is extremely difficult.
TLDR; use an Encoder
and not @Body
I customized an AnnotatedParameterProcessor implementation class. But I encountered a problem: when I use the following code to put the parameter into the request body.
Such as the bodyTemplate‘s value is: %7B"ab":{ab}%7D, and the variable(ab) is a Array or a List.
feign.template.Template#resolveExpression feign.template.Expressions.SimpleExpression#expand
When the variable is of type Iterable, it will be handle and encode. But I don't want to handle my variable. How should i control it.
I customized an AnnotatedParameterProcessor to encapsulate the parameters on the feignclient method(POST) into a format like {"a":{a},"b":{b},"c":{c}}, and then put it into the bodyTemplate. Then, in feign.RequestTemplate#resolve(java.util.Map<java.lang.String,?>) replace {a} in the body with the real value, and put this string into the request body (JSON format). However, if {c} is a List, it will be encoded. {"a":1,"b":test,"c":[],"d":{}},but variable c will be processed into other formats and encoded (like in URLs).