hi和hello兩個請求引發(fā)的@RequestBody思考
2023-04-12
hi和hello兩個請求引發(fā)的@RequestBody思考
- 描述
- 思考
- DispatcherServlet
- AbstractHandlerMethodAdapter
- RequestMappingHandlerAdapter
- ServletInvocableHandlerMethod
- InvocableHandlerMethod
- HandlerMethodArgumentResolverComposite
- RequestResponseBodyMethodProcessor
- 結論
- 驗證
- 設置日志打印級別
- 啟動時關鍵日志
- hi請求日志
- hello請求日志
- 心得
描述
每天多思考一點,不斷豐富自己的知識體系。有hi和hello兩個get請求,他們倆有啥區(qū)別呢?
@GetMapping("/hi")
public String hi(User user) {
System.out.println("hi");
System.out.println(user.toString());
return "hi";
}
@GetMapping("/hello")
public String hello(@RequestBody User user) {
System.out.println("hello");
System.out.println(user.toString());
return "hello";
}
使用Apifox發(fā)送如下兩個請求:
- http://127.0.0.1:8080/hi
- http://127.0.0.1:8080/hello hi請求正常返回“hi”,hello請求則返回如下“400”錯誤。
{
"timestamp": "2022-04-15T02:00:42.580+00:00",
"status": 400,
"error": "Bad Request",
"path": "/hello"
}
hello請求的后臺錯誤信息如下:
Resolved [org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing: public java.lang.String com.example.web.test.TestController.hello(com.example.web.test.User)]
然后將hello請求參數(shù)改為json格式,如下所示:

然后正常返回字符串“hello”。
思考
首先從寫法看來,hello請求多一個@RequestBody注解,并且這個注解是寫在參數(shù)里的。可以朝著數(shù)據(jù)綁定的方向去思考。@RequestBody源碼如下:
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestBody {
boolean required() default true;
}
然后我們梳理一下一個請求的完整的處理過程。根據(jù)SpringMvc核心架構圖說明,從用戶發(fā)送一個請求到應答信息返回,主要有以下幾個步驟:

- 發(fā)送請求到控制器
- 控制器進行分發(fā)
- 處理器進行數(shù)據(jù)校驗和業(yè)務邏輯調用
- service層業(yè)務處理
- 邏輯處理完成
- 封裝數(shù)據(jù)模型并返回ModelAndView
- 控制器調用視圖解析
- ViewResolver進行視圖解析
- 控制器調用視圖渲染
- View進行視圖渲染
- 視圖渲染完成,并返回應答信息到用戶
- 請求完成
通過跟蹤斷點,發(fā)現(xiàn)以下時間軸方法。

DispatcherServlet
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
……
// 確定當前請求的 handler adapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
……
// 實際的handler處理
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
……
}
AbstractHandlerMethodAdapter
@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}
RequestMappingHandlerAdapter
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
……
// No synchronization on session demanded at all...
mav = invokeHandlerMethod(request, response, handlerMethod);
……
}
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
……
invocableMethod.invokeAndHandle(webRequest, mavContainer);
……
}
ServletInvocableHandlerMethod
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
}
InvocableHandlerMethod
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
……
}
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
……
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
……
}
HandlerMethodArgumentResolverComposite
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
HandlerMethodArgumentResolver resolver = this.getArgumentResolver(parameter);
……
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
RequestResponseBodyMethodProcessor
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
……
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
……
}
@Override
protected Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
……
Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
……
}

圖中部分找到了系統(tǒng)后臺報異常的代碼。
結論
使用@RequestBody注解的接口前置條件如下:
- 請求頭Content-Type必須設置為application/json
- 請求體不能為空
驗證
設置日志打印級別
logging.level.root: debug
啟動時關鍵日志
_.s.web.servlet.HandlerMapping.Mappings :
c.e.w.t.TestController:
{GET [/hi]}: hi(User)
{GET [/hello]}: hello(User)
hi請求日志
Received [GET /hi HTTP/1.1
User-Agent: apifox/2.1.7 (https://www.apifox.cn)
Accept: */*
Host: 127.0.0.1:8080
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 17
username=xiaoming]
正常響應。
hello請求日志
Received [GET /hello HTTP/1.1
User-Agent: apifox/2.1.7 (https://www.apifox.cn)
Content-Type: application/json
Accept: */*
Host: 127.0.0.1:8080
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 32
{
"username": "xiaoming"
}]
正常響應。
心得
雖然結論很簡單,但是收獲的是思考的過程。
身為一個程序員,但求勤勤勉勉、兢兢業(yè)業(yè),每天有所得、每事有所得。
本文僅代表作者觀點,版權歸原創(chuàng)者所有,如需轉載請在文中注明來源及作者名字。
免責聲明:本文系轉載編輯文章,僅作分享之用。如分享內容、圖片侵犯到您的版權或非授權發(fā)布,請及時與我們聯(lián)系進行審核處理或刪除,您可以發(fā)送材料至郵箱:service@tojoy.com





