基於@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。
推薦閱讀:
- @RequestBody不能映射到對象的解決
- FeignClient服務器拋出異常客戶端處理方案
- spring mvc中@RequestBody註解的作用說明
- Spring MVC如何使用@RequestParam註解獲取參數
- ConstraintValidator類如何實現自定義註解校驗前端傳參