spring mvc中@RequestBody註解的作用說明
@RequestBody的作用
@RequestBody主要用來接收前端傳遞給後端的json字符串中的數據的(請求體中的數據的),所以隻能發送POST請求。
GET方式無請求體,所以使用@RequestBody接收數據時,前端不能使用GET方式提交數據,而是用POST方式進行提交。
在後端的同一個接收方法裡,@RequestBody與@RequestParam()可以同時使用,@RequestBody最多隻能有一個,而@RequestParam()可以有多個。
註:一個請求,隻有一個RequestBody;一個請求,可以有多個RequestParam。
jQuery中,$.ajax()默認發送的參數類型及編碼為:application/x-www-form-urlcoded,而@RequestBody處理的參數類型及編碼為:aplication/json或者是application/xml,通過contentType屬性來指定
在傳遞之前,對JSON對象要使用JSON.stringify(),JSON.stringify() 方法將一個 JavaScript 值(對象或者數組)轉換為一個 JSON 字符串
後端@RequestBody註解對應的類在將HTTP的輸入流(含請求體)裝配到目標類(即:@RequestBody後面的類)時,會根據json字符串中的key來匹配對應實體類的屬性,如果匹配一致且json中的該key對應的值符合(或可轉換為)實體類的對應屬性的類型要求時,會調用實體類的setter方法將值賦給該屬性。
註:當同時使用@RequestParam()和@RequestBody時,@RequestParam()指定的參數可以是普通元素、數組、集合、對象等等
(即:當,@RequestBody 與@RequestParam()可以同時使用時,原SpringMVC接收參數的機制不變,隻不過RequestBody 接收的是請求體裡面的數據;而RequestParam接收的是key-value裡面的參數,所以它會被切面進行處理從而可以用普通元素、數組、集合、對象等接收)
實現案例
1. jsp頁面
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> <script src="${pageContext.request.contextPath}/js/jquery.min.js"></script> <script> var userList=new Array(); userList.push({userName:"beijing",age:34}); userList.push({userName:"shanghai",age:89}); $.ajax({ type:"post", url:"${pageContext.request.contextPath}/bike7", data:JSON.stringify(userList), contentType:"application/json;charset=utf-8", success:function(result){ alert(result); } }) </script> </head> <body> </body> </html>
2. controller
/** * @author liujianfu * @description controller中業務方法的集合參數獲取,要將集合參數封裝到一個pojo中才可以 * 參數屬性名與請求參數名稱一致,參數值會自動映射匹配 * @date 2021/1/10 22:14 * @param * @return */ @RequestMapping("/bike6") public String bike6(){ System.out.println("controller6:"); return "ajax"; } /** * @author liujianfu * @description controller中業務方法的集合參數獲取,要將集合參數封裝到一個pojo中才可以 * 參數屬性名與請求參數名稱一致,參數值會自動映射匹配 * @date 2021/1/10 22:14 * @param * @return */ @RequestMapping("/bike7") @ResponseBody public String bike7(@RequestBody List<User> userList){ for(User u:userList){ System.out.println("user:"+u.getUserName()); } return "ok"; }
3. spring-mvc 配置文件
4. 頁面訪問
@RequestBody原理
通過Http傳遞參數一般有兩種方式,一種是通過url解析參數,一種是通過body來解決,那麼我們本次說的RequestBody就是去解析請求體然後映射到我們的參數,那它該如何解析body呢?這就是本篇博客誕生的目的。
這個其實是SpringMVC中做的一個處理機制,在整個SpringMVC的處理流程中,會通過HandlerMethod來代理每個Map後的controller和method,在通過反射invoke method的過程中,會解析request來獲得arguments,而@RequestBody就是在解析參數的這個過程中起作用的
1. 執行流程
在InvocableHandlerMethod#getMethodArgumentValues中,它會遍歷當前HandlerMethod的參數,對於每一個參數,它都會通過HandlerMethodArgumentResolverComposite#supportsParameter來判斷是否可以被解析器解析,如果可以被解析,則通過HandlerMethodArgumentResolverComposite#resolveArguement進行解析
對於@RequestBody來說,它對應的解析器是RequestResponseBodyMethodProcessor,那麼,我們就深入到它的源碼中來一探究竟:
public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor { // 是否可以解析當前參數 @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(RequestBody.class); } // 是否可以解析當前返回值 @Override public boolean supportsReturnType(MethodParameter returnType) { return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) || returnType.hasMethodAnnotation(ResponseBody.class)); } @Override public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { parameter = parameter.nestedIfOptional(); // 解析handlerMethod中的參數 Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType()); // 獲取變量名 String name = Conventions.getVariableNameForParameter(parameter); if (binderFactory != null) { WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name); if (arg != null) { // 通過binder校驗@Validated註解的字段 validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { throw new MethodArgumentNotValidException(parameter, binder.getBindingResult()); } } if (mavContainer != null) { mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult()); } } // 如果方法是Optional參數,則代理 return adaptArgumentIfNecessary(arg, parameter); } @Override protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter, Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException { HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class); Assert.state(servletRequest != null, "No HttpServletRequest"); ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest); // 解析http請求中的body並映射到對應的parameter上 Object arg = readWithMessageConverters(inputMessage, parameter, paramType); if (arg == null && checkRequired(parameter)) { throw new HttpMessageNotReadableException("Required request body is missing: " + parameter.getExecutable().toGenericString()); } return arg; } protected boolean checkRequired(MethodParameter parameter) { RequestBody requestBody = parameter.getParameterAnnotation(RequestBody.class); return (requestBody != null && requestBody.required() && !parameter.isOptional()); } }
2. 註冊流程
那麼RequestResponseBodyMethodProcessor是如何被註冊到resolver中呢?主要是在RequestMappingHandlerAdapter中:
- RequestMappingHandlerAdapter實現瞭HandlerAdapter這個接口,這個接口是MVC框架的SPI,DispatcherServlet通過此接口訪問所有已安裝的處理程序。
- HandlerAdapter主要是路由之後方法的適配器,DispatcherServlet在路由之後通過HandlerAdapter來執行真實的操作(handlerAdapter是通過HandlerMethod來執行的)
對於RequestResponseBodyMethodProcessor來說,它實現瞭InitializingBean,在bean初始化之後會添加參數處理器和返回值處理器,對於參數處理器來說,內容如下:
從圖中我們可以看到,RequestMappingHandlerAdapter在初始化的時候會把系統給定的,自定義的參數解析器加載到內存中。
加入說,我們要自定義一個參數解析器,系統會在什麼時候加載進入內存呢?
我們發現RequestMappingHandlerAdapter#setCustomArgumentResolvers這個方法就是要去設置自定義參數解析器的,那麼我們隻需要找到它的調用方即可。
我們隻需要實現WebMvcConfigurer即可(這點關系到Spring的自動裝配,暫時沒有看到,先鴿一下)
3. 設計優點
設計模式
采用策略模式+工廠模式 + 組合模式:
對於HandlerMethod的參數和返回值處理來說,對應著不同的處理方式,即對應著不同的策略,所以此處用的策略模式來處理的。至於HandlerMethodArgumentResolverComposite它則對應著策略工廠,同時,因為這個類實現瞭HandlerMethodArgumentResolver,所以它也是組合模式的變形,具體的策略類是HandlerMethodArgumentResolver
類圖如下:
緩存處理
在參數解析工廠中,剛開始的參數解析器是在剛啟動時註冊到list中,但是如果之後被使用的時候就會存放到map中,可以直接獲得(key是MethodParam),提高路由效率
附:常見的MVC參數註解
對於url解析參數來說,有兩個註解,分別是pathVariable(指一種占位符)和requestParam,對於body來說,有requestBody。不加註解,也可以直接把url轉為對應參數或者實體類
1.@PathVariable: www.666.com/web/6
@GetMapping("/web/{node}") public ReturnType listEmployeeInNode(@PathVariable String node) throws BusinessException { }
2.@ReqeustParam: www.666.com/web?user=1
@GetMapping("/web") public ReturnType listEmployeeInNode(@RequestParam("user") String node) throws BusinessException { }
3.@RequestBody: www.666.com/web body中是json
@GetMapping("/web") public ReturnType listEmployeeInNode(@RequestBody UserDTO userDto) throws BusinessException { }
4.無註解:www.666.com/web?userId=1&pwd=2
@PostMapping("/web") public ReturnType listEmployeeInNode(UserDTO userDto) throws BusinessException { }
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。
推薦閱讀:
- Spring MVC數據綁定方式
- 一文搞懂SpringMVC中@InitBinder註解的使用
- SpringMVC 參數綁定相關知識總結
- 詳解如何在SpringBoot中自定義參數解析器
- Spring的@RequestParam對象綁定方式