SpringBoot MVC(4)请求参数的绑定

680 阅读5分钟

返回处理方法后,得到执行链,根据处理方法获取对应的处理适配器,执行一段拦截器的过程,调用处理程序(拦截器相关内容见此)。

准备工作

DispatcherServlet->doDispatch():
    // 前面是一段获取执行链,获取适配器,执行拦截器方法的过程
    // 适配器适用于方法、Controller子类或者HttpRequestHandler的子类,适配器不匹配则会抛出异常
    // ha就是前面返回的适配器,RequestMappingHandlerMapping对应RequestMappingHandlerAdapter
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
AbstractHandlerMethodAdapter->handle():
    // 该方法返回的是一个ModelAndView对象
    return handleInternal(request, response, (HandlerMethod) handler);
RequestMappingHandlerAdapter->handleInternal():
    mav = invokeHandlerMethod(request, response, handlerMethod);
RequestMappingHandlerAdapter->invokeHandlerMethod():
    ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
    // 这里的argumentResolvers与参数绑定策略有关,是HandlerMethodArgumentResolverComposite类
    if (this.argumentResolvers != null) {
        invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
    }
    // 这里的returnValueHandlers与返回策略有关,是HandlerMethodReturnValueHandlerComposite类
    if (this.returnValueHandlers != null) {
        invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
    }
    ......
    // 上面一大段都是准备工作,这一步才算真正开始
    invocableMethod.invokeAndHandle(webRequest, mavContainer);

在实例化beanName为requestMappingHandlerAdapter的时候,执行afterPropertiesSet()方法时,会赋argumentResolversreturnValueHandlers默认值

HandlerAdapter在内部对于每个请求,都会实例化一个ServletInvocableHandlerMethod进行处理,ServletInvocableHandlerMethod在进行处理的时候,会分两部分别对请求跟响应进行处理。

参数的绑定与解析

// 参数webRequest封装了request和resonse
ServletInvocableHandlerMethod->invokeAndHandle():
    // 处理请求
    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
InvocableHandlerMethod->invokeForRequest():
    // 获取绑定的参数
    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);	
InvocableHandlerMethod->getMethodArgumentValues():
    // 如果方法的参数为空的话,直接返回
    if (ObjectUtils.isEmpty(getMethodParameters())) {
        return EMPTY_ARGS;
    }
    // 获取参数个数
    MethodParameter[] parameters = getMethodParameters();
    Object[] args = new Object[parameters.length];
    ......
    // 从argumentResolvers遍历,看看参数是否符合,一般都是符合的
    // 将参数与其对应的methodArgumentResolver绑定,放到argumentResolverCache,下次就不用再遍历了
    if (!this.resolvers.supportsParameter(parameter)) {
        throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
    }
    // 获取绑定的参数类型,同时绑定参数
    args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
HandlerMethodArgumentResolverComposite->resolveArgument():
    HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
    return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);

不同的参数+特定的注解会得到不同的解析器,后面绑定参数也不尽相同,下面会对常用的参数类型作举例,至于其他的可以自行研究

ServletModelAttributeMethodProcessor

对应参数是普通类,如下面的tree

  • 请求id=1&token=123&age=3
  • 参数public String test(Tree tree, Integer age)
ModelAttributeMethodProcessor->resolveArgument():
    // 选择构造器实例化对象
    attribute = createAttribute(name, parameter, binderFactory, webRequest);
    ......
    // 绑定参数
    bindRequestParameters(binder, webRequest);
ServletModelAttributeMethodProcessor->bindRequestParameters():
    servletBinder.bind(servletRequest);
ServletRequestDataBinder->bind():
    // 获取所有请求的参数名称和值,默认都是String类型,如果有相同的名称的话,会存成String数组的
    MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
    doBind(mpvs);
WebDataBinder->doBind():
    super.doBind(mpvs);
DataBinder->doBind():
    applyPropertyValues(mpvs);
DataBinder->applyPropertyValues():
    getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
    
// setPropertyValues的逻辑如下
// 此时id=1,token=123,age=3,遍历(以id为例)
// 1.如果tree有id这个字段就继续,没有会抛出字段不存在异常 
// 2.如果1符合tree的id定义的字段类型,就利用反射完成赋值,否则抛出类型不匹配的异常
// 3.三个字段遍历完成之后,看有没有类型不匹配的异常,有的话抛出异常,没有的话完成参数的绑定
// 4.如果成功的话,最终结果tree(id=1)
AbstractPropertyAccessor->setPropertyValues():
	

RequestParamMethodArgumentResolver

对应的是简单类,BeanUtils->isSimpleValueType为true会被认为是简单类,如下面的age

  • 请求id=1&token=123&age=3
  • 参数public String test(Tree tree, Integer age)
  1. 利用request.getParameterValues("age")获取请求中age的值,如果有相同的名称的话,会返回String数组的
  2. 得到age="3"之后,尝试将3转成Integer类型,如果不成功会抛出类型不匹配异常,最终绑定age=3
  3. 假设请求中age有多个,会返回第一个

RequestResponseBodyMethodProcessor

对应的是参数存在@RequestBody注解的情况,如下面中的tree

  • 请求{"id":1,"token":"123"}
  • 参数public String test1(@RequestBody Tree tree))
  1. 利用反射添加tree有public set字段的字段
  2. 利用构造器实例化对象
  3. 使用消息转换器以流的形式读取http body中的内容,如果tree有id这个字段就继续,没有则跳过
  4. 得到请求体中id的值为1,尝试将其转为Integer型,如果转换成功,就利用反射完成赋值,转换不成功的话会抛出异常
  5. 最终tree(id=1)

需要注意的是,读取完一次之后,流的下标置为结束,再读就没有数据了,这就是为什么方法中同时设置两个@RequestBody参数会报Stream closed的原因了

ServletRequestMethodArgumentResolver、ServletResponseMethodArgumentResolver

一般来说,ServletRequestMethodArgumentResolver对应ServletRequest以及HttpSession的子类,ServletResponseMethodArgumentResolver对应ServletResponse的子类

附录:三种获得httpServletRequest的方式

httpServletRequest用来获取客户端的相关信息,以下三种是获取该对象的常用方式

写在方法中

    @RequestMapping("/test")
    public String test(HttpServletRequest httpServletRequests) {
        return "OK";
    }

前面中说过,作为参数时,使用ServletRequestMethodArgumentResolver解析器进行参数绑定

ServletRequestMethodArgumentResolver->resolveArgument():
    if (ServletRequest.class.isAssignableFrom(paramType) || MultipartRequest.class.isAssignableFrom(paramType)) {
        return resolveNativeRequest(webRequest, paramType);
    }
    // 绑定非ServletRequest实现类的参数
    return resolveArgument(paramType, resolveNativeRequest(webRequest, HttpServletRequest.class));
ServletRequestMethodArgumentResolver->resolveNativeRequest():
    T nativeRequest = webRequest.getNativeRequest(requiredType);
    return nativeRequest;
ServletWebRequest->getNativeRequest():
    // getRequest()方法返回的是ServletRequestAttributes类下的request变量
    // 将变量强转成HttpServletRequest类型
    return WebUtils.getNativeRequest(getRequest(), requiredType);

RequestMappingHandlerAdapter->invokeHandlerMethod()方法中,可以看到ServletWebRequest webRequest = new ServletWebRequest(request, response)这行代码,之后,webRequest作为参数一直传递,最终利用它的getRequest()获得了request对象

写在Ioc容器中

    @Autowired
    private HttpServletRequest httpServletRequest;

IoC容器的request变量是一个代理类,执行它的方法时,执行的是接口处理器类的invoke方法

AutowireUtils->ObjectFactoryDelegatingInvocationHandler->invoke():
    return method.invoke(this.objectFactory.getObject(), args);
WebApplicationContextUtils->RequestObjectFactory->getObject():
    return currentRequestAttributes().getRequest();
WebApplicationContextUtils->currentRequestAttributes():
    RequestAttributes requestAttr = RequestContextHolder.currentRequestAttributes();
    if (!(requestAttr instanceof ServletRequestAttributes)) {
        throw new IllegalStateException("Current request is not a servlet request");
    }
    return (ServletRequestAttributes) requestAttr;

可以看到,IoC容器的request方法实际利用动态代理获得当前RequestContextHolder的RequestAttributes的request对象,然后执行的是它的方法,这里最终返回的request即为requestFacade

利用RequestContextHolder

    ServletRequestAttributes servletRequestAttributes = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes());
    if (null == servletRequestAttributes) {
        return null;
    }
    return servletRequestAttributes.getRequest();

映射的匹配中说过,DispatcherServlet处理请求之前有一段绑定线程变量的过程

FrameworkServlet->processRequest():
    // 这里的request即为requestFacade
    ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
    initContextHolders(request, localeContext, requestAttributes);
FrameworkServlet->initContextHolders():
    RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);

requestAttributes封装了requestFacade,之后requestAttributes绑定当前线程,这就是为什么在http主线程中可以通过RequestContextHolder获取到requestFacade的原因了

小结

尽管三种方式获取的原理以及对象引用各不相同,但最终都是requestFacade执行方法