Springboot自定义参数解析器解决多requestbody问题

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<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;
  }
}
最后,需要将自定义的解析器交给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<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());
    }
}
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();
    }

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值