最近遇到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
如果觉得本文对你有帮助,欢迎点赞评论,欢迎关注我,我将努力创作更多更好的文章。
原文地址:https://blog.csdn.net/w605283073/article/details/82119284 </div>