SpringBoot使用@RequestBody接收多个对象的解决办法 ゞ 浴缸里的玫瑰 2021-09-24 23:30 1522阅读 0赞 最近在写一个项目,使用了springboot+vue+axios。 其中axiox请求Content-Type 为默认的 application/json,结果在接收参数时遇到麻烦,后台使用@RequestBody接收参数时无法接收多个对象! 而且一个方法只能写一个@RequestBody注解,难道说,传过来的许多的参数只能用一个对象接收?这就很难受啊。 面向百度编程——才知道:SpringMVC中@RequestBody是读取的流的方式, 在取 body参数时第一个参数取到后把request.getInputStream()关闭,导致后面的@requestBody的对象拿取不到,就会报错。 又接着了解到,其实请求参数永远都是一个,因为一个request中只包含一个request body. 理解了这个,就会明白Spring MVC不支持多个@RequestBody。 知道了为什么,就来看看解决方法,大体有三种解决方法: 1.将前台传过来的json数据单独封装一个类来接收 //前台传输的json对象 data:{name:'admin', pwd:'123', dept: {name:'dept'}} //后台接收参数,UserVo为封装的对象 // user = {name = 'admin' , pwd = '123',dept = {name = 'dept'}} public void test(@RequestBody UserVO user) 但是这种方法会很繁琐,每次传输可能都需要去封装一个对象,需求一变动,可能都需要重新封装对象接收,想想都可怕 2.使用Map<String,Object>来接收所有的参数,然后在通过data.get("name")自己反序列化为需要使用的实体对象 //前台传输的json对象 data:{name:'admin', pwd:'123', dept: {name:'dept'}} //后台接收参数,使用Map<String, Object>就可以一次性接收所有参数,接下来通过data.get("name")获取对应的数据 public void test(@RequestBody Map<String, Object> data) 这种方法本来是可以使用的,但是可读性不好 3.使用一个String统一接收参数,在使用fastjson解析 // json传递多个对象解决办法 public void test(@RequestBody String json){ // fastjson转成json对象 JSONObject jsonObject = JSON.parseObject(json); // 在转成不同的实体类 User user = jsonObject.getObject("user", User.class); UserAccount userAccount = jsonObject.getObject("userAccount", UserAccount.class); } 比上一种方法稍稍友好一点,但是可读性仍然不好 4.自定义一个注解,自己解析参数,听起来就很优雅,是不是,使用起来更优雅! 共需要添加三个文件 1注解文件 package cn.wwkj.ay_infra.basic.config; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Controller中方法接收多个JSON对象 * @date 2018/08/27 */ @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface MultiRequestBody { /** * 是否必须出现的参数 */ boolean required() default true; /** * 当value的值或者参数名不匹配时,是否允许解析最外层属性到该对象 */ boolean parseAllFields() default true; /** * 解析时用到的JSON的key */ String value() default ""; } 2注解实现 package cn.wwkj.ay_infra.basic.config; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONException; import com.alibaba.fastjson.JSONObject; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.core.MethodParameter; 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.IOException; import java.lang.reflect.Field; import java.util.HashSet; import java.util.Set; /** * MultiRequestBody解析器 * 解决的问题: * 1、单个字符串等包装类型都要写一个对象才可以用@RequestBody接收; * 2、多个对象需要封装到一个对象里才可以用@RequestBody接收。 * 主要优势: * 1、支持通过注解的value指定JSON的key来解析对象。 * 2、支持通过注解无value,直接根据参数名来解析对象 * 3、支持基本类型的注入 * 4、支持GET和其他请求方式注入 * 5、支持通过注解无value且参数名不匹配JSON串key时,根据属性解析对象。 * 6、支持多余属性(不解析、不报错)、支持参数“共用”(不指定value时,参数名不为JSON串的key) * 7、支持当value和属性名找不到匹配的key时,对象是否匹配所有属性。 * * @author Wangyang Liu QQ: 605283073 * @date 2018/08/27 */ public class MultiRequestBodyArgumentResolver implements HandlerMethodArgumentResolver { private static final String JSONBODY_ATTRIBUTE = "JSON_REQUEST_BODY"; /** * 设置支持的方法参数类型 * * @param parameter 方法参数 * @return 支持的类型 */ @Override public boolean supportsParameter(MethodParameter parameter) { // 支持带@MultiRequestBody注解的参数 return parameter.hasParameterAnnotation(MultiRequestBody.class); } /** * 参数解析,利用fastjson * 注意:非基本类型返回null会报空指针异常,要通过反射或者JSON工具类创建一个空对象 */ @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { String jsonBody = getRequestBody(webRequest); JSONObject jsonObject = JSON.parseObject(jsonBody); // 根据@MultiRequestBody注解value作为json解析的key MultiRequestBody parameterAnnotation = parameter.getParameterAnnotation(MultiRequestBody.class); //注解的value是JSON的key String key = parameterAnnotation.value(); Object value; // 如果@MultiRequestBody注解没有设置value,则取参数名FrameworkServlet作为json解析的key if (StringUtils.isNotEmpty(key)) { value = jsonObject.get(key); // 如果设置了value但是解析不到,报错 if (value == null && parameterAnnotation.required()) { throw new IllegalArgumentException(String.format("required param %s is not present", key)); } } else { // 注解为设置value则用参数名当做json的key key = parameter.getParameterName(); value = jsonObject.get(key); } // 获取的注解后的类型 Long Class<?> parameterType = parameter.getParameterType(); // 通过注解的value或者参数名解析,能拿到value进行解析 if (value != null) { //基本类型 if (parameterType.isPrimitive()) { return parsePrimitive(parameterType.getName(), value); } // 基本类型包装类 if (isBasicDataTypes(parameterType)) { return parseBasicTypeWrapper(parameterType, value); // 字符串类型 } else if (parameterType == String.class) { return value.toString(); } // 其他复杂对象 return JSON.parseObject(value.toString(), parameterType); } // 解析不到则将整个json串解析为当前参数类型 if (isBasicDataTypes(parameterType)) { if (parameterAnnotation.required()) { throw new IllegalArgumentException(String.format("required param %s is not present", key)); } else { return null; } } // 非基本类型,不允许解析所有字段,必备参数则报错,非必备参数则返回null if (!parameterAnnotation.parseAllFields()) { // 如果是必传参数抛异常 if (parameterAnnotation.required()) { throw new IllegalArgumentException(String.format("required param %s is not present", key)); } // 否则返回null return null; } // 非基本类型,允许解析,将外层属性解析 Object result; try { result = JSON.parseObject(jsonObject.toString(), parameterType); } catch (JSONException jsonException) { // TODO:: 异常处理返回null是否合理? result = null; } // 如果非必要参数直接返回,否则如果没有一个属性有值则报错 if (!parameterAnnotation.required()) { return result; } else { boolean haveValue = false; Field[] declaredFields = parameterType.getDeclaredFields(); for (Field field : declaredFields) { field.setAccessible(true); if (field.get(result) != null) { haveValue = true; break; } } if (!haveValue) { throw new IllegalArgumentException(String.format("required param %s is not present", key)); } return result; } } /** * 基本类型解析 */ private Object parsePrimitive(String parameterTypeName, Object value) { final String booleanTypeName = "boolean"; if (booleanTypeName.equals(parameterTypeName)) { return Boolean.valueOf(value.toString()); } final String intTypeName = "int"; if (intTypeName.equals(parameterTypeName)) { return Integer.valueOf(value.toString()); } final String charTypeName = "char"; if (charTypeName.equals(parameterTypeName)) { return value.toString().charAt(0); } final String shortTypeName = "short"; if (shortTypeName.equals(parameterTypeName)) { return Short.valueOf(value.toString()); } final String longTypeName = "long"; if (longTypeName.equals(parameterTypeName)) { return Long.valueOf(value.toString()); } final String floatTypeName = "float"; if (floatTypeName.equals(parameterTypeName)) { return Float.valueOf(value.toString()); } final String doubleTypeName = "double"; if (doubleTypeName.equals(parameterTypeName)) { return Double.valueOf(value.toString()); } final String byteTypeName = "byte"; if (byteTypeName.equals(parameterTypeName)) { return Byte.valueOf(value.toString()); } return null; } /** * 基本类型包装类解析 */ private Object parseBasicTypeWrapper(Class<?> parameterType, Object value) { if (Number.class.isAssignableFrom(parameterType)) { Number number = (Number) value; if (parameterType == Integer.class) { return number.intValue(); } else if (parameterType == Short.class) { return number.shortValue(); } else if (parameterType == Long.class) { return number.longValue(); } else if (parameterType == Float.class) { return number.floatValue(); } else if (parameterType == Double.class) { return number.doubleValue(); } else if (parameterType == Byte.class) { return number.byteValue(); } } else if (parameterType == Boolean.class) { return value.toString(); } else if (parameterType == Character.class) { return value.toString().charAt(0); } return null; } /** * 判断是否为基本数据类型包装类 */ private boolean isBasicDataTypes(Class clazz) { Set<Class> classSet = new HashSet<>(); classSet.add(Integer.class); classSet.add(Long.class); classSet.add(Short.class); classSet.add(Float.class); classSet.add(Double.class); classSet.add(Boolean.class); classSet.add(Byte.class); classSet.add(Character.class); return classSet.contains(clazz); } /** * 获取请求体JSON字符串 */ private String getRequestBody(NativeWebRequest webRequest) { HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class); // 有就直接获取 String jsonBody = (String) webRequest.getAttribute(JSONBODY_ATTRIBUTE, NativeWebRequest.SCOPE_REQUEST); // 没有就从请求中读取 if (jsonBody == null) { try { jsonBody = IOUtils.toString(servletRequest.getReader()); webRequest.setAttribute(JSONBODY_ATTRIBUTE, jsonBody, NativeWebRequest.SCOPE_REQUEST); } catch (IOException e) { throw new RuntimeException(e); } } return jsonBody; } } 3WebConfig文件 package cn.wwkj.ay_infra.basic.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.nio.charset.Charset; import java.util.List; /** * Web Config Demo * * @author Wangyang Liu liuwangyangedu@163.com * @date 2018/08/27 */ @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { // 添加MultiRequestBody参数解析器 argumentResolvers.add(new MultiRequestBodyArgumentResolver()); } @Bean public HttpMessageConverter<String> responseBodyConverter() { // 解决中文乱码问题 return new StringHttpMessageConverter(Charset.forName("UTF-8")); } @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { converters.add(responseBodyConverter()); } } 最后就是使用了! // 简单的使用————像@RequestBody一样使用就可以了 public void save(@MultiRequestBody ProjBill projBill, @MultiRequestBody List<String> fileIds) 最后,还有一种方法是在实体类中使用@Transient注解,忽略实体类不需要的属性,不推荐使用,所以具体使用的方法,自行百度该注解即可! 第四种方法贼6,亲测,推荐使用 附上该注解的原作者文章:[传送门][Link 1],深度使用请查阅原作者文章及github项目源码!感谢作者! [Link 1]: https://blog.csdn.net/w605283073/article/details/82119284
还没有评论,来说两句吧...