最近遇到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>