1、问题背景
开发中遇到一个问题,前端一个表单传递的参数,后端用一个bean来接收,使用@RequestBody来接收,可以将前端传递过来的字符串转换为对应的实体类。后续需求变更,前端表单中需要加另外一些新的字段,新增字段与原有字段代表含义不一致,不便写到一个Bean中,这个时候如果使用@RequestBody参数接收,只能解析成一个bean类,不能解析成两个bean类。
2.解决方式
从网上搜索资料后,发现可以使用HandlerMethodArgumentResolver来自定义参数解析方法
package org.springframework.web.method.support;
import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
public interface HandlerMethodArgumentResolver {
//:用于判定是否需要处理该参数分解,返回true为需要,并会去调用下面的方法resolveArgument。
boolean supportsParameter(MethodParameter parameter);
//真正用于处理参数分解的方法,返回的Object就是controller方法上的形参对象。
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
通过自定义注解,标记该类型Json参数的解析方法
package com.online.analyze.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Description: 自定义@MultiRequestBody注解
* @Author: LiWT
* @Date: 2021/6/26
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface MultiRequestBody {
/**
* 是否必须出现的参数
*/
boolean required() default true;
/**
* 当value的值或者参数名不匹配时,是否允许解析最外层属性到该对象
*/
boolean parseAllFields() default true;
/**
* 解析时用到的JSON的key
*/
String value() default "";
}
可以编写类继承该接口,对获取到的json字符串进行自定义解析
package com.online.analyze.annotation;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
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) {
//基本类型
// isPrimitive确定指定的Class对象是基本类型,其返回是个boolean值,true代表你指定的这个Class对象是基本类型,false代表这个Class对象不是基本类型。
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;
}
}
最后,需要将自定义的解析器交给springboot管理
package com.online.analyze.annotation;
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.WebMvcConfigurer;
import java.nio.charset.Charset;
import java.util.List;
/**
* 添加多RequestBody解析器
*
* @author 明明如月
* @date 2018/08/27
*/
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List argumentResolvers) {
argumentResolvers.add(new MultiRequestBodyArgumentResolver());
}
@Bean
public HttpMessageConverter responseBodyConverter() {
return new StringHttpMessageConverter(Charset.forName("UTF-8"));
}
@Override
public void configureMessageConverters(List> converters) {
// super.configureMessageConverters(converters);
converters.add(responseBodyConverter());
}
}
Spring 5.0后,WebMvcConfigurerAdapter被废弃,取代的方法有两种:
①implements WebMvcConfigurer(官方推荐)
②extends WebMvcConfigurationSupport
使用第一种方法是实现了一个接口,可以任意实现里面的方法,不会影响到Spring Boot自身的@EnableAutoConfiguration,
使用第二种方法相当于覆盖了@EnableAutoConfiguration里的所有方法,每个方法都需要重写
————————————————
版权声明:本文为CSDN博主「QueryEasy」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/sdname/article/details/109718768
使用:可以将一个前端传递的json字符串参数解析成两个后多个java bean
@PostMapping("/addOnlinePlan")
public ResultData addOnlinePlan(@MultiRequestBody("plan") OnlinePlan onlinePlan,
@MultiRequestBody("trace") OnlineTrackModel onlineTrackModel) {
onlinePlanService.addOnlinePlan(onlinePlan,onlineTrackModel);
return ResultData.success();
}