0.需求
在实际的开发过程中,服务间调用一般使用Json传参的模式,SpringBoot项目无法使用@RequestParam接收Json传参
只有@RequestBody支持Json,但是每次为了一个接口就封装一次实体类比较麻烦
如果使用Map来进行参数接收,则会导致参数不可控,会在接口中新增较多判断进行入参控制
其次,在实际的开发过程中,我们偶尔会传入两个实体类,如果使用@RequestBody也会出错
因为传入的参数只能够读取一次,一般这里也会封装一次实体类,不够方便
也有重写HttpServletRequestWrapper的处理办法,但不能解决上一个问题
1.思路
因为一个注解只能读取一次,按照重写HttpServletRequestWrapper的思路,将请求中的Json参数进行缓存
另外自定义一个注解,来把参数进行注入。
1.1.自定义@JsonFmt注解
import java.lang.annotation.*;
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documentedpublic @interfaceJsonFmt {/*** 值*/String value()default "";/*** 是否必须*/
boolean require() default true;
}
这里的值,不是给参数的默认值(defaultValue),而是类似于@RequestParam注解中的value、name,是用来指定入参的key
1.2.自定义注解的实现类
importcom.alibaba.fastjson.JSON;importcom.alibaba.fastjson.JSONObject;importcom.fasterxml.jackson.databind.ObjectMapper;importlombok.extern.slf4j.Slf4j;importorg.springframework.core.MethodParameter;importorg.springframework.util.StringUtils;importorg.springframework.web.bind.support.WebDataBinderFactory;importorg.springframework.web.context.request.NativeWebRequest;importorg.springframework.web.method.support.HandlerMethodArgumentResolver;importorg.springframework.web.method.support.ModelAndViewContainer;importjavax.servlet.http.HttpServletRequest;importjava.io.BufferedReader;importjava.util.HashMap;importjava.util.Map;
@Slf4jpublic class JsonFmtHandlerMethodArgumentResolver implementsHandlerMethodArgumentResolver {//自定义key
private static final String KEY = "TEST_JSON_BODY_KEY";private static ObjectMapper objectMapper = newObjectMapper();
@Overridepublic booleansupportsParameter(MethodParameter parameter) {return parameter.hasParameterAnnotation(JsonFmt.class);
}
@Overridepublic Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throwsException {
JsonFmt jsonFmt= parameter.getParameterAnnotation(JsonFmt.class);
JSONObject jsonObject=getJsonObject(webRequest);
String value=getParamName(parameter,jsonFmt);boolean require =jsonFmt.require();
Object paramValue=getParamValue(jsonObject,value);if (paramValue == null &&require) {throw new Exception("parameter[" + value + "]不能为空。");
}if (paramValue == null) {return null;
}
Class> classType =parameter.getParameterType();if (paramValue.getClass().equals(JSONObject.class)){
paramValue=objectMapper.readValue(paramValue.toString(),classType);
}returnparamValue;
}privateString getParamName(MethodParameter parameter, JsonFmt jsonFmt) {
String value=jsonFmt.value();if(StringUtils.isEmpty(value)) {
value=parameter.getParameterName();
}returnvalue;
}privateObject getParamValue(JSONObject jsonObject,String value) {for(String key: jsonObject.keySet()) {if(key.equalsIgnoreCase(value)){returnjsonObject.get(key);
}
}return null;
}private JSONObject getJsonObject(NativeWebRequest webRequest) throwsException {
String jsonBody=(String) webRequest.getAttribute(KEY, NativeWebRequest.SCOPE_REQUEST);if(StringUtils.isEmpty(jsonBody)){
HttpServletRequest request= webRequest.getNativeRequest(HttpServletRequest.class);
BufferedReader reader=request.getReader();
StringBuilder sb= newStringBuilder();char[] buf = new char[1024];intrd;while ((rd = reader.read(buf)) != -1) {
sb.append(buf,0, rd);
}
jsonBody=sb.toString();if(StringUtils.isEmpty(jsonBody)){
Map params =request.getParameterMap();
Map tmp= newHashMap();for (Map.Entryparam:params.entrySet()) {if(param.getValue().length == 1){
tmp.put(param.getKey(),param.getValue()[0]);
}else{
tmp.put(param.getKey(),param.getValue());
}
}
jsonBody=JSON.toJSONString(tmp);
}
webRequest.setAttribute(KEY, jsonBody, NativeWebRequest.SCOPE_REQUEST);
}returnJSONObject.parseObject(jsonBody);
}
}
方法说明:
supportsParameter:说明支持的注解,只要方法参数有@JsonFmt就启用该实现类
resolveArgument:解决方法,注解的具体实现
getJsonObject:获取请求体,这里的实现逻辑就是从请求中获取Json体,如果没有获取到,则从请求参数中获取(兼容From模式),将请求体封装为JsonObject
getParamName:获取注解参数的key,先获取注解的value,如果为空,则使用方法参数的名称
getParamValue:这个可以不加,我这里是为了让key不区分大小写,如果需要区分,直接使用jsonObject.get(key)即可
1.3.加入自定义注解
importcom.example.demo.jsonfmt.JsonFmtHandlerMethodArgumentResolver;importorg.springframework.context.annotation.Configuration;importorg.springframework.web.method.support.HandlerMethodArgumentResolver;importorg.springframework.web.servlet.config.annotation.WebMvcConfigurer;importjava.util.List;
@Configurationpublic class AppConfig implementsWebMvcConfigurer {
@Overridepublic void addArgumentResolvers(Listresolvers) {
resolvers.add(newJsonFmtHandlerMethodArgumentResolver());
}
}
2.使用
到这里我们就能愉快的使用我们的自定义注解@JsonFmt来进行参数接收了
目前在Json传参中,能完美的接收实体类、List、Map以及其他基础类型
在Form传参中,能够支持List、Map以及其他基础类型,对于实体类暂时还不能兼容
因为后台接收到的是Map,不容易区分哪些是实体类的字段,无法进行填充,这种建议使用@RequestBody