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) @Documented public @interface JsonFmt { /** * 值 */ String value() default ""; /** * 是否必须 */ boolean require() default true; }
这里的值,不是给参数的默认值(defaultValue),而是类似于@RequestParam注解中的value、name,是用来指定入参的key
1.2.自定义注解的实现类
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.core.MethodParameter; import org.springframework.util.StringUtils; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; import javax.servlet.http.HttpServletRequest; import java.io.BufferedReader; import java.util.HashMap; import java.util.Map; @Slf4j public class JsonFmtHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver { //自定义key private static final String KEY = "TEST_JSON_BODY_KEY"; private static ObjectMapper objectMapper = new ObjectMapper(); @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(JsonFmt.class); } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { 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); } return paramValue; } private String getParamName(MethodParameter parameter, JsonFmt jsonFmt) { String value = jsonFmt.value(); if (StringUtils.isEmpty(value)) { value = parameter.getParameterName(); } return value; } private Object getParamValue(JSONObject jsonObject,String value) { for (String key: jsonObject.keySet()) { if(key.equalsIgnoreCase(value)){ return jsonObject.get(key); } } return null; } private JSONObject getJsonObject(NativeWebRequest webRequest) throws Exception { 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 = new StringBuilder(); char[] buf = new char[1024]; int rd; while ((rd = reader.read(buf)) != -1) { sb.append(buf, 0, rd); } jsonBody = sb.toString(); if(StringUtils.isEmpty(jsonBody)){ Map<String,String[]> params = request.getParameterMap(); Map tmp = new HashMap(); for (Map.Entry<String,String[]> param: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); } return JSONObject.parseObject(jsonBody); } }
方法说明:
supportsParameter:说明支持的注解,只要方法参数有@JsonFmt就启用该实现类
resolveArgument:解决方法,注解的具体实现
getJsonObject:获取请求体,这里的实现逻辑就是从请求中获取Json体,如果没有获取到,则从请求参数中获取(兼容From模式),将请求体封装为JsonObject
getParamName:获取注解参数的key,先获取注解的value,如果为空,则使用方法参数的名称
getParamValue:这个可以不加,我这里是为了让key不区分大小写,如果需要区分,直接使用jsonObject.get(key)即可
1.3.加入自定义注解
import com.example.demo.jsonfmt.JsonFmtHandlerMethodArgumentResolver; import org.springframework.context.annotation.Configuration; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.util.List; @Configuration public class AppConfig implements WebMvcConfigurer { @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { resolvers.add(new JsonFmtHandlerMethodArgumentResolver()); } }
2.使用
到这里我们就能愉快的使用我们的自定义注解@JsonFmt来进行参数接收了
目前在Json传参中,能完美的接收实体类、List、Map以及其他基础类型
在Form传参中,能够支持List、Map以及其他基础类型,对于实体类暂时还不能兼容
因为后台接收到的是Map,不容易区分哪些是实体类的字段,无法进行填充,这种建议使用@RequestBody