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发送如下两个请求:

  1. http://127.0.0.1:8080/hi
  2. 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格式,如下所示:

hi和hello两个请求引发的@RequestBody思考_User


然后正常返回字符串“hello”。

思考

首先从写法看来,hello请求多一个@RequestBody注解,并且这个注解是写在参数里的。可以朝着数据绑定的方向去思考。@RequestBody源码如下:

@Target({ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface RequestBody {
        boolean required() default true;
    }

然后我们梳理一下一个请求的完整的处理过程。根据SpringMvc核心架构图说明,从用户发送一个请求到应答信息返回,主要有以下几个步骤:

hi和hello两个请求引发的@RequestBody思考_java_02

  1. 发送请求到控制器
  2. 控制器进行分发
  3. 处理器进行数据校验和业务逻辑调用
  4. service层业务处理
  5. 逻辑处理完成
  6. 封装数据模型并返回ModelAndView
  7. 控制器调用视图解析
  8. ViewResolver进行视图解析
  9. 控制器调用视图渲染
  10. View进行视图渲染
  11. 视图渲染完成,并返回应答信息到用户
  12. 请求完成

通过跟踪断点,发现以下时间轴方法。

hi和hello两个请求引发的@RequestBody思考_java_03

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);
		……
	}

hi和hello两个请求引发的@RequestBody思考_ide_04


图中部分找到了系统后台报异常的代码。

结论

使用@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"
}]

正常响应。

心得

虽然结论很简单,但是收获的是思考的过程。
身为一个程序员,但求勤勤勉勉、兢兢业业,每天有所得、每事有所得。