基于@RequestBody注解只能注入对象和map的解决

时间:2022-06-06 01:16:07

@RequestBody注解只能注入对象和map的问题

前后端分离开发模式下,前后端数据交互全部采用json,所以在后端在采用spring框架的时候都会使用@ResponseBody(后端返回参数封装为json格式)和@RequestBody(前端请求携带json参数解析)注解。

但是在实际开发中,往往@RequestBody的使用会比较令人难受(超级难受),因为它spring官方只支持到将json解析为一个定义好的对象或者是一个通用性的map,而我们实际项目中经常传递的参数仅仅是一个或者是两个参数,这样的参数封装程对象总是有点大材小用的感觉,并且还消耗性能,而使用map又感觉操作比较繁琐,那这样的话可不可以使用简单的类似于@PathVariable(value="")这样的模式取请求数据呢????当然可以!!

1、自定义一个适应于这种情况的注解@RequestJson

package cn.annotation; 
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; 
/**
* Title:      RequestJson
* @date       2018年9月10日
* @version    V1.0
* Description: 自定义请求json数据解析注解(主要解决单参数传递)
*/
@Target(ElementType.PARAMETER)//使用在参数上
@Retention(RetentionPolicy.RUNTIME)//运行时注解
public @interface RequestJson {
  /**
   * 是否必须出现的参数
   */
  boolean required() default true;

  /**
   * 当value的值或者参数名不匹配时,是否允许解析最外层属性到该对象
   */
  boolean parseAllFields() default true;

  /**
   * 解析时用到的JSON的key
   */
  String value() default "";
}

2、自定义RequestJsonHandlerMethodArgumentResolver

实现HandlerMethodArgumentResolver(spring解析数据的接口)

package cn.config; 
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject; 
import cn.annotation.RequestJson; 
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;

/**
* Title:      RequestJsonHandlerMethodArgumentResolver
* @date       2018年9月10日
* @version    V1.0
* Description: 自定义解析json数据
*/
public class RequestJsonHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
private static final String JSONBODY_ATTRIBUTE = "JSON_REQUEST_BODY";

  /**
   * 设置支持的方法参数类型
   * @param parameter 方法参数
   * @return 支持的类型
   */
  @Override
  public boolean supportsParameter(MethodParameter parameter) {
      // 支持带@RequestJson注解的参数
      return parameter.hasParameterAnnotation(RequestJson.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);
      // 根据@RequestJson注解value作为json解析的key
      RequestJson parameterAnnotation = parameter.getParameterAnnotation(RequestJson.class);
      //注解的value是JSON的key
      String key = parameterAnnotation.value();
      Object value = null;
      // 如果@RequestJson注解没有设置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);
      }


      Class<?> parameterType = parameter.getParameterType();
      // 通过注解的value或者参数名解析,能拿到value进行解析
      if (value != null) {
          if (isBasicDataTypes(parameterType)) {
              return value;
          }
          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;
          }
      }

      Object result = parameterType.newInstance();
      // 非基本类型,不允许解析所有字段,返回null
      if (!parameterAnnotation.parseAllFields()) {
          // 如果是必传参数抛异常
          if (parameterAnnotation.required()) {
              throw new IllegalArgumentException(String.format("required param %s is not present", key));
          }
          // 否则返回空对象
          return result;
      }
     // 非基本类型,允许解析,将外层属性解析
      result = JSON.parseObject(jsonObject.toString(), parameterType);
      // 如果非必要参数直接返回,否则如果没有一个属性有值则报错
      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;
      }
  }

  /**
   * 基本数据类型直接返回
   */
  @SuppressWarnings("rawtypes")
  private boolean isBasicDataTypes(Class clazz) {
Set<Class> classSet = new HashSet<>();
      classSet.add(String.class);
      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(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;
}
}

3、将上述配置应用到spring项目中

重写addArgumentResolvers方法

package cn.config; 
import java.nio.charset.Charset;
import java.util.List; 
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.WebMvcConfigurerAdapter;

/**
* Title:      WebConfig
* @date       2018年9月10日
* @version    V1.0
* Description: 将自定义注解配置到spring
*/

@Configuration
@SuppressWarnings("deprecation")
public class WebConfig extends WebMvcConfigurerAdapter{
@Override
  public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
      argumentResolvers.add(new RequestJsonHandlerMethodArgumentResolver());
  }

  @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());
  }
}

4、配置完成了,简单使用

单参数:(不加value默认为参数名)

基于@RequestBody注解只能注入对象和map的解决

或者:

基于@RequestBody注解只能注入对象和map的解决

多参数:

基于@RequestBody注解只能注入对象和map的解决

 

@RequestBody注解的使用问题

今天遇到的问题:@RequestBody的使用问题

先看一下@RequestBody的作用

基于@RequestBody注解只能注入对象和map的解决

我想获取json字符串某个字段值,看截图:

基于@RequestBody注解只能注入对象和map的解决

看一下控制台的输出信息:

基于@RequestBody注解只能注入对象和map的解决

what ?这什么情况,为什么拿到的是整个json字符串,然后我继续测试

基于@RequestBody注解只能注入对象和map的解决

给了一个400

基于@RequestBody注解只能注入对象和map的解决

what ?这又是什么情况 (好像只能有一个@RequestBody)我想参数如果是整形的话能不能获取,我继续进行测试代码:

基于@RequestBody注解只能注入对象和map的解决

传参:

基于@RequestBody注解只能注入对象和map的解决

又给了一个400 (好像只能是String类型) 测试引用类型对象

基于@RequestBody注解只能注入对象和map的解决

代码:

基于@RequestBody注解只能注入对象和map的解决

传参:

基于@RequestBody注解只能注入对象和map的解决

控制台打印:

基于@RequestBody注解只能注入对象和map的解决

测试成功。

个人总结:

1) 一个方法只能有一个@RequestBody

2) 如果接收参数是字符串类型的,获取的是整个json字符串

3) 如果接受的参数是引用对象,@requestBody User user 会将json字符串中的值赋予user中对应的属性上

需要注意的是,json字符串中key必须和User对象的属性名对应

以上为个人经验,希望能给大家一个参考,也希望大家多多支持服务器之家。

原文链接:https://blog.csdn.net/WoddenFish/article/details/82593317