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

2023-04-12


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格式,如下所示:




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


思考


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


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

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



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

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



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



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


结论


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

正常响应。


心得


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




本文仅代表作者观点,版权归原创者所有,如需转载请在文中注明来源及作者名字。

免责声明:本文系转载编辑文章,仅作分享之用。如分享内容、图片侵犯到您的版权或非授权发布,请及时与我们联系进行审核处理或删除,您可以发送材料至邮箱:service@tojoy.com