SpringBoot Controller 中使用多个@RequestBody

曾几何时,开发了一个小小工具,然后想一次性提交多个表單,如:

{
    "company":{
        "id":"4308D4EDFA8B8D0505CDDD4A9EADA818",
        "name":"11",
        "address":"112"
    },
    "computer":[
        {
            "mainEngine":"a111",
            "displayDevice":"b2222",
            "printer":"c33333",
            "ukey":"d44444",
            "network":"1",
            "id":"201BE45CA745976EF5F5627AFC41DFA0",
            "companyId":"4308D4EDFA8B8D0505CDDD4A9EADA818"
        }
    ],
    "liaison":[
        {
            "name":"1",
            "cellPhoneNumber":"1111111111",
            "id":"FFF27320B5042A0E41686150D7C34DD6",
            "companyId":"4308D4EDFA8B8D0505CDDD4A9EADA818"
        }
    ]
}

結果發現spring boot 的controller的@RequestBody不提供多個對象的映射的,故想方設法搞定啊!

一、新建一個@interface的MultiRequestBody:

package cn.com.css.target;

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

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 "";
}

二、新建MultiRequestBodyArgumentResolver類

package cn.com.css.utils;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

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;

//1、重写方法参数解析器

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;

import cn.com.css.target.MultiRequestBody;

/**
 * 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时,对象是否匹配所有属性。
 *
 * @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();
			} else if (parameterType == List.class) {// 是不是list对象
				return parseListTypeWrapper(parameterType, value, parameter);

			}
			// 其他复杂对象
			System.out.println(parameterType);
			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;
		}
	}

	*/**
	 * 方法名: parseListTypeWrapper<br/>
	 * 描述: List类型时转成对应的實列对象<br/>
	 * 参数: @param parameterType
	 * 参数: @param value
	 * 参数: @param parameter
	 * 参数: @return <br/> 
	 * 返回类型: Object <br/>    
	 * 异常:
	 */
	private Object parseListTypeWrapper(Class<?> parameterType, Object value, MethodParameter parameter) {
		Type type = parameter.getGenericParameterType();
		if (type instanceof ParameterizedType) {
			ParameterizedType pt = (ParameterizedType) type;
			for (Type arg : pt.getActualTypeArguments()) {
				parameterType = (Class<?>) arg;
			}
		}
		return JSONObject.parseArray(value.toString(), parameterType);
	}*

	/**
	 * 基本类型解析
	 */
	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;
	}

	/**
	 * 判断是否为基本数据类型包装类
	 */
	@SuppressWarnings("rawtypes")
	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;
	}
}

三、controller中使用

@PostMapping("/addByMulti")
	public ResponseData addByMulti(@MultiRequestBody("company") Company company, @MultiRequestBody("computer") List<Computer> computer,
			@MultiRequestBody("liaison") List<Liaison> liaison) {
		return companyService.addByMulti(company, computer, liaison);
	}

四、總結

由於時間原因,尚未類的及優化,只對原博主的基礎上加一個List的轉換步驟,有空繼續優化一把。還有如果cotroller中不加value會報錯的。日後再優化改進一把。

參考:https://blog.csdn.net/w605283073/article/details/82119284

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值