SpringBoot Controller 中使用多个@RequestBody的正确姿势
Posted 明明如月学长
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 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;
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<HandlerMethodArgumentResolver> 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参考配置方式)
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<constructor-arg value="UTF-8"/>
</bean>
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>application/json</value>
<value>text/html</value>
<value>text/plain</value>
</list>
</property>
<property name="fastJsonConfig" ref="fastJsonConfig"/>
</bean>
</mvc:message-converters>
<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")
@ResponseBody
public 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
如果觉得本文对你有帮助,欢迎点赞评论,欢迎关注我,我将努力创作更多更好的文章。
-----------------------------------------------------
以上是关于SpringBoot Controller 中使用多个@RequestBody的正确姿势的主要内容,如果未能解决你的问题,请参考以下文章
SpringBoot篇 - Controller中常用的注解简介
SpringBoot中常用注解@Controller/@RestController/@RequestMapping的区别