SpringBoot使用@RequestBody接收多个对象的解决办法

15 篇文章 1 订阅

最近在写一个项目,使用了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,亲测,推荐使用

附上该注解的原作者文章:传送门,深度使用请查阅原作者文章及github项目源码!感谢作者!

 

### 回答1: Spring Boot框架提供了多种方式来接收多个对象。 1. 使用集合:在Controller的方法参数中声明一个集合类型(如List、Set、数组等),并在请求体中传递多个对象的JSON数组格式。Spring Boot会自动将JSON数组中的每个元素转换成相应的对象,并添加到集合中。例如: ```java @PostMapping("/objects") public void receiveMultipleObjects(@RequestBody List<MyObject> objects) { // 处理接收到的多个对象 } ``` 2. 使用@RequestParam注解:通过@RequestParam注解来指定请求参数的名称,并通过多次调用该注解接收多个对象。这种方式常用于GET请求,需要手动构建多个请求参数,每个参数对应一个对象。例如: ```java @GetMapping("/objects") public void receiveMultipleObjects(@RequestParam("object1") MyObject object1, @RequestParam("object2") MyObject object2) { // 处理接收到的多个对象 } ``` 3. 使用数组:在Controller的方法参数中声明一个数组类型,可以通过请求参数的多次传递来接收多个对象。类似于第2种方式,但是不需要指定@RequestParam注解。例如: ```java @GetMapping("/objects") public void receiveMultipleObjects(MyObject[] objects) { // 处理接收到的多个对象 } ``` 无论使用哪种方式,Spring Boot都会根据请求内容将数据转换成相应的对象,并将其传递给Controller中对应的方法进行处理。这样可以方便地接收处理多个对象。 ### 回答2: Spring Boot 可以接收多个对象的方式有许多种,以下是两种常见的方法: 1. 通过 @RequestParam 接收多个对象: 在控制器方法的参数列表中,使用 @RequestParam 注解并指定参数名,可以接收多个对象。例如,假设有两个对象 User 和 Product,可以使用以下方式接收: ```java @PostMapping("/save") public String save(@RequestParam(value = "user") User user, @RequestParam(value = "product") Product product) { // 处理接收到的对象 return "success"; } ``` 这样,客户端在发送请求时,需要将 User 对象的属性以 `?user.xxx=yyy` 的形式添加到请求 URL 中,将 Product 对象的属性以 `?product.xxx=yyy` 的形式添加到请求 URL 中。 2. 通过 @RequestBody 接收多个对象: 在控制器方法的参数列表中,使用 @RequestBody 注解,并传入一个包含多个对象的实体类对象,可以接收多个对象。例如,假设有一个实体类包含 User 和 Product 对象: ```java public class Request { private User user; private Product product; // 省略 getter 和 setter } @PostMapping("/save") public String save(@RequestBody Request request) { User user = request.getUser(); Product product = request.getProduct(); // 处理接收到的对象 return "success"; } ``` 客户端在发送请求时,可以使用 POST 方法,并将 User 对象和 Product 对象封装到一个 JSON 对象中,然后通过请求的 body 发送给服务器。服务器端使用 @RequestBody 注解接收这个包含多个对象的 JSON 对象,然后通过调用 getter 方法获取对应的对象。 以上是两种常见的接收多个对象的方法,具体的方式根据实际项目的需求和业务逻辑来选择使用。 ### 回答3: Spring Boot可以通过以下几种方式来接收多个对象: 1. 使用集合作为Controller方法的参数:Controller方法可以直接接收集合类型参数,例如List、Set或Map。在请求中传递多个对象时,将这些对象封装在集合中,并作为请求参数发送到Controller方法。Spring Boot会自动将请求中的数据映射到集合对象中。 2. 使用@RequestParam注解接收多个对象:在Controller方法中,可以使用@RequestParam注解来映射请求参数,通过设置@RequestParam("paramName")即可获取请求中的对应参数值。当需要接收多个对象时,可以在方法的参数使用@RequestParam注解多次,分别映射不同对象参数Spring Boot会自动将请求中的数据映射到这些对象中。 3. 使用@RequestParam注解接收多个对象的数组或列表:类似上述方式,通过在Controller方法的参数使用@RequestParam注解,但将参数类型设置为数组或列表,即可接收多个对象参数。在请求中,可以使用相同的参数名,并追加[]符号或索引号,例如paramName[0]、paramName[1]等,这样就能够将多个对象参数传递给对应的Controller方法。 4. 使用@RequestBody注解接收多个对象:在需要接收多个对象的地方,可以使用@RequestBody注解接收请求体中的多个对象参数。需要注意的是,请求体中的数据需要以JSON或XML格式传递,并且在请求头中设置Content-Type为application/json或application/xml,以告知Spring Boot使用哪种数据格式进行参数解析和映射。 总结而言,Spring Boot接收多个对象可以通过集合、@RequestParam注解、数组或列表、@RequestBody注解等方式实现。开发者可以根据实际需求和数据传递方式选择合适的方式来接收多个对象
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值