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

9 篇文章 0 订阅

描述

每天多思考一点,不断丰富自己的知识体系。有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 <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"
}]

正常响应。

心得

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jinwen5290

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值