hi和hello两个请求引发的@RequestBody思考
- 描述
- 思考
- DispatcherServlet
- AbstractHandlerMethodAdapter
- RequestMappingHandlerAdapter
- ServletInvocableHandlerMethod
- InvocableHandlerMethod
- HandlerMethodArgumentResolverComposite
- RequestResponseBodyMethodProcessor
- 结论
- 验证
- 设置日志打印级别
- 启动时关键日志
- hi请求日志
- hello请求日志
- 心得
描述
每天多思考一点,不断丰富自己的知识体系。有hi和hello两个get请求,他们俩有啥区别呢?
@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发送如下两个请求:
- 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请求参数改为json格式,如下所示:
然后正常返回字符串“hello”。
思考
首先从写法看来,hello请求多一个@RequestBody注解,并且这个注解是写在参数里的。可以朝着数据绑定的方向去思考。@RequestBody源码如下:
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestBody {
boolean required() default true;
}
然后我们梳理一下一个请求的完整的处理过程。根据SpringMvc核心架构图说明,从用户发送一个请求到应答信息返回,主要有以下几个步骤:
- 发送请求到控制器
- 控制器进行分发
- 处理器进行数据校验和业务逻辑调用
- service层业务处理
- 逻辑处理完成
- 封装数据模型并返回ModelAndView
- 控制器调用视图解析
- ViewResolver进行视图解析
- 控制器调用视图渲染
- View进行视图渲染
- 视图渲染完成,并返回应答信息到用户
- 请求完成
通过跟踪断点,发现以下时间轴方法。
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 <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
……
Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
……
}
图中部分找到了系统后台报异常的代码。
结论
使用@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"
}]
正常响应。
心得
虽然结论很简单,但是收获的是思考的过程。
身为一个程序员,但求勤勤勉勉、兢兢业业,每天有所得、每事有所得。