SpringBoot Controller 中使用多个@RequestBody的正确姿势

最近遇到Controller中需要多个@RequestBody的情况,但是发现并不支持这种写法,

这样导致

1、单个字符串等包装类型都要写一个对象才可以用@RequestBody接收;

2、多个对象需要封装到一个对象里才可以用@RequestBody接收。

查阅StackOverFlow,受到一个解决方案的启发,本人改进为以下版本,并给出了详尽的注释,希望对大家有帮助。

改进后的方案支持:

1、支持通过注解的value指定JSON的key来解析对象。

2、支持通过注解无value,直接根据参数名来解析对象

3、支持GET方式和其他请求方式

4、支持基本类型属性注入

5、支持通过注解无value且参数名不匹配JSON串key时,根据属性解析对象。

6、支持多余属性(不解析、不报错)、支持参数“共用”(不指定value时,参数名不为JSON串的key)

7、支持当value和属性名找不到匹配的key时,对象是否匹配所有属性。

重要更新记录:

2019年02月25日 新增xml方式参考配置

2019年02月07日 fix 当list参数为空时,parameterType.newInstance会导致异常。

2018年12月28日 新增测试用例,完善解析部分代码

2018年10月23日 完善项目格式

2018年08月28日 创建第一版

项目仅供参考,如因使用不当造成任何问题,请自行负责,有问题欢迎探讨改进。

项目地址(建议去拉最新代码):

https://github.com/chujianyun/Spring-MultiRequestBody

另外代码应该会尽量持续更新完善,欢迎大家贡献代码。

 

步骤如下:

0、除spring的Jar包外涉及的主要Maven依赖

    <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.4</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.35</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>
其中fastjson用来解析json对象,commons-lang用来字符串判空(也可以自己手写),commons-io用来读取请求封装为字符串类型(也可以自己封装)。

 

1、重写方法参数解析器

package com.chujianyun.web.bean;

import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONObject;import io.github.chujianyun.annotation.MultiRequestBody;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;

/**

  • 多RequestBody解析器
    *
  • @author 明明如月
  • @date 2018/08/27
    */
    public class MultiRequestBodyArgumentResolver implements HandlerMethodArgumentResolver {

private static final String JSONBODYATTRIBUTE = "JSONREQUEST_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 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;
  }
}
 

2、编写解析的方法注解:

package com.chujianyun.web.annotation;

import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;

/**

  • Controller中方法接收多个JSON对象
    *
  • @author 明明如月
  • @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 "";
}
3、在配置Bean中注入

特别注意: 如果加入本配置导致页面访问404 可以去掉 @EnableWebMvc注解

package com.chujianyun.web.config;

import com.chujianyun.web.bean.MultiRequestBodyArgumentResolver;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.EnableWebMvc;import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import java.nio.charset.Charset;import java.util.List;

/**

  • 添加多RequestBody解析器
  • @author 明明如月
  • @date 2018/08/27
    */
    @Configuration
    @EnableWebMvc
    public class WebConfig extends WebMvcConfigurerAdapter {
    @Override
    public void addArgumentResolvers(List argumentResolvers) {
    argumentResolvers.add(new MultiRequestBodyArgumentResolver());
    }
    @Bean
    public HttpMessageConverter<String> responseBodyConverter() {
        return new StringHttpMessageConverter(Charset.forName("UTF-8"));
    }
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        super.configureMessageConverters(converters);
        converters.add(responseBodyConverter());
    }
}
 

xml配置方式(感谢网友 "熔 岩"提供了的xml参考配置方式)

application/json text/html text/plain

        <mvc:argument-resolvers>
             <bean class="io.github.chujianyun.bean.MultiRequestBodyArgumentResolver"/>
        </mvc:argument-resolvers>
    </mvc:annotation-driven>
使用方法:

package com.chujianyun.web.controller;

import com.chujianyun.web.annotation.MultiRequestBody;import com.chujianyun.web.domain.Dog;import com.chujianyun.web.domain.User;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;

/**

  • 演示控制器
  • @author 明明如月
  • @date 2018/08/27
    */
    @Controller
    @RequestMapping("/xhr/test")
    public class DemoController {
    @RequestMapping("/demo")
    @ResponseBody
     public String multiRequestBodyDemo1(@MultiRequestBody Dog dog, @MultiRequestBody User user) {
        System.out.println(dog.toString()+user.toString());
        return dog.toString()+";"+user.toString();
    }
 
    @RequestMapping("/demo2")
    @ResponseBody
    public String multiRequestBodyDemo2(@MultiRequestBody("dog") Dog dog, @MultiRequestBody User user) {
        System.out.println(dog.toString()+user.toString());
        return dog.toString()+";"+user.toString();
    }
    @RequestMapping("/demo3")
    @ResponseBody
    public String multiRequestBodyDemo3(@MultiRequestBody("dog") Dog dog, @MultiRequestBody("user") User user) {
        System.out.println(dog.toString()+user.toString());
        return dog.toString()+";"+user.toString();
    }
 
 
    @RequestMapping("/demo4")
    @ResponseBody
    public String multiRequestBodyDemo4(@MultiRequestBody("dog") Dog dog, @MultiRequestBody Integer age) {
        System.out.println(dog.toString() + age.toString());
        return dog.toString() + ";age属性为:"+age.toString();
    }
 
    @RequestMapping("/demo5")
    @ResponseBody
    public String multiRequestBodyDemo5(@MultiRequestBody("color") String color, @MultiRequestBody("age") Integer age) {
        return "color="+color + "; age=" + age;
    }
    @RequestMapping("/demo6")
    @ResponseBody
    public String multiRequestBodyDemo6(@MultiRequestBody("dog") Dog dog, @MultiRequestBody Integer age) {
        System.out.println(dog.toString() + age.toString());
        return dog.toString() + ";age属性为:"+age.toString();
    }
 
    @RequestMapping("/demo7")
    @ResponseBody
    public String multiRequestBodyDemo7(@MultiRequestBody Dog color2, @MultiRequestBody("age") Integer age) {
        return "color="+color2 + "; age=" + age;
    }
 
    @RequestMapping("/demo9")
    @ResponseBody
    public String multiRequestBodyDemo9( @MultiRequestBody Dog dog) {
        return dog.toString();
    }
   @RequestMapping("/demo10")
    @ResponseBody
    public String multiRequestBodyDemo10( @MultiRequestBody(parseAllFields = false,required = false) Dog dog) {
        return dog.toString();
    }

@RequestMapping("/testList")@ResponseBodypublic String multiRequestBodyDemo1(@MultiRequestBody List test, @MultiRequestBody String str) {

    return test.toString() + str;
  }

}两个实体:

package com.chujianyun.web.domain;

/**

  • @author 明明如月
  • @date 2018/08/27
    */
    public class Dog {
    private String name;
    private String color;
 
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getColor() {
        return color;
    }
    public void setColor(String color) {
        this.color = color;
    }
    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", color='" + color + '\'' +
                '}';
    }
}
package com.chujianyun.web.domain;

/**

  • @author 明明如月
  • @date 2018/08/27
    */
    public class User {
    private String name;
    private Integer age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
效果:

demo 

demo2

demo3

 

参考文章:https://stackoverflow.com/questions/12893566/passing-multiple-variables-in-requestbody-to-a-spring-mvc-controller-using-ajax

版权声明:本文为CSDN博主「明明如月小角落」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/w605283073/article/details/82119284

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值