基於@RequestBody註解隻能註入對象和map的解決

@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註解的使用問題

今天遇到的問題:@RequestBody的使用問題

先看一下@RequestBody的作用

在這裡插入圖片描述

我想獲取json字符串某個字段值,看截圖:

在這裡插入圖片描述

看一下控制臺的輸出信息:

在這裡插入圖片描述

what ?這什麼情況,為什麼拿到的是整個json字符串,然後我繼續測試

在這裡插入圖片描述

給瞭一個400

在這裡插入圖片描述

what ?這又是什麼情況 (好像隻能有一個@RequestBody)我想參數如果是整形的話能不能獲取,我繼續進行測試代碼:

在這裡插入圖片描述

傳參:

在這裡插入圖片描述

又給瞭一個400 (好像隻能是String類型) 測試引用類型對象

在這裡插入圖片描述

代碼:

在這裡插入圖片描述

傳參:

在這裡插入圖片描述

控制臺打印:

在這裡插入圖片描述

測試成功。

個人總結:

1) 一個方法隻能有一個@RequestBody

2) 如果接收參數是字符串類型的,獲取的是整個json字符串

3) 如果接受的參數是引用對象,@requestBody User user 會將json字符串中的值賦予user中對應的屬性上

需要註意的是,json字符串中key必須和User對象的屬性名對應

以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。

推薦閱讀: