SpringBoot2各类型参数解析原理(源码分析)

Posted AC_Jobim

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot2各类型参数解析原理(源码分析)相关的知识,希望对你有一定的参考价值。


环境:SpringBoot 2.5.2

一、使用注解来获取请求参数

测试链接:http://localhost:8080/car/2/owner/zhangsan?age=15&inters=football
测试方法:

@GetMapping("/car/{id}/owner/{username}")
public Map<String,Object> getCar(@PathVariable("id") Integer id,
                                 @PathVariable("username") String name,
                                 @PathVariable Map<String,String> pv,
                                 @RequestHeader("User-Agent") String userAgent,
                                 @RequestHeader Map<String,String> header,
                                 @RequestParam("age") Integer age,
                                 @RequestParam("inters") List<String> inters,
                                 @RequestParam Map<String,String> params){
    Map<String,Object> map = new HashMap<>();

    map.put("id",id);
    map.put("name",name);
    map.put("pv",pv);
    map.put("userAgent",userAgent);
    map.put("headers",header);
    map.put("age",age);
    map.put("inters",inters);
    map.put("params",params);
    return map;
}

1.1 获取HandlerAdapter

doDispatch()方法进行debug,mappedHandler = getHandler(processedRequest);这一行代码是确定了请求的handler方法,也就是确定Controller的处理方法。那么,HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());这一行是为找到的handler再确定一个HandlerAdapter适配器,来解决参数处理问题。

getHandlerAdapter()方法,为当前Handler找一个适配器HandlerAdapter

寻找Handler方法类似,也就是在定义好的4种HanderAdapter中遍历,这四种适配器分别为:

  • RequestMappingHandlerAdapter:支持方法上标注@RequestMapping注解的
  • HandlerFunctionAdapter:支持函数式编程的
  • HttpRequestHandlerAdapter:业务自行处理请求,不需要通过modelAndView转到试图
  • SimpleControllerHandlerAdapter:标准控制器,返回ModelAndView

getHandlerAdapter里调用了adapter.supports(handler)方法来确定adapter。

public final boolean supports(Object handler) {
    return handler instanceof HandlerMethod && this.supportsInternal((HandlerMethod)handler);
}

最终getHandlerAdapter()方法方法返回了RequestMappingHandlerAdapter

1.2 执行目标方法

找到了RequestMappingHandlerAdapter这个适配器,接着进行了一系列验证之后,真正执行目标方法的代码为doDispatch方法中的这一行

我们查看这一方法的执行,进入到RequestMappingHandlerAdapter类中,发现真正执行的是这个类的handleInternal()方法,而在这个方法中,关键是这一行代码

进入invokeHandlerMethod()方法,有两行关键代码

这是为invocableMethod也就是调用方法设置参数解析器和返回值处理器

1.2.1 参数解析器(HandlerMethodArgumentResolver)

HandlerMethodArgumentResolver是一个接口

public interface HandlerMethodArgumentResolver {
	//解析器是否支持当前参数
    boolean supportsParameter(MethodParameter var1);
	 将request中的请求参数解析到当前Controller参数上,在这里进行类型转换
    @Nullable
    Object resolveArgument(MethodParameter var1, @Nullable ModelAndViewContainer var2, NativeWebRequest var3, @Nullable WebDataBinderFactory var4) throws Exception;
}

参数解析器的作用判断是否支持解析这种参数,支持就调用 resolveArgument解析最终确定将要执行的目标方法的每一个参数的值是什么,而在SpringMVC中,目标方法能写多少种类型的参数,就取决于参数解析器的个数。

HandlerMethodArgymentResolver接口

1.2.2 返回值处理器(HandlerMethodReturnValueHandler)

返回值处理器也确定了Controller方法能返回的值的种类

1.2.3 反射调用方法

再对invocableMethod进行一系列的封装之后,最后执行invocableMethod.invokeAndHandle(webRequest, mavContainer);这一行代码,我们进入invokeAndHandle方法,这是在ServletInvocableHandlerMethod类中

进入invokeForRequest()方法中

1.2.4 确定目标方法的参数值详细

进入getMethodArgumentValues方法,这个方法便是确定目标方法参数值的详细过程

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
    MethodParameter[] parameters = this.getMethodParameters(); // 获取目标方法的参数列表
    if (ObjectUtils.isEmpty(parameters)) {
        return EMPTY_ARGS;
    } else {
        Object[] args = new Object[parameters.length];  // 方法返回值:确定好的方法参数

        for(int i = 0; i < parameters.length; ++i) {
            MethodParameter parameter = parameters[i]; // 获得参数列表的参数
            parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
            args[i] = findProvidedArgument(parameter, providedArgs);
            if (args[i] == null) {
                if (!this.resolvers.supportsParameter(parameter)) { // 判断当前的参数解析器中是否有支持此方法参数的参数解析器,详见1.2.4.1
                    throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
                }

                try {
                    args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory); // 判断当前的参数解析器中是否有支持此方法参数的参数解析器,详见1.2.4.2
                } catch (Exception var10) {
                    if (logger.isDebugEnabled()) {
                        String exMsg = var10.getMessage();
                        if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
                            logger.debug(formatArgumentError(parameter, exMsg));
                        }
                    }

                    throw var10;
                }
            }
        }

        return args;
    }
}

1.2.4.1 resolvers.supportsParameter(parameter)

判断在27个参数解析器中是否存在支持当前方法参数的参数解析器,我们可以看到方法内部挨个遍历确认支持的参数解析器。另外,其中还用到了argumentResolverCache缓存,如果以后有同一类型的参数进来,就可以直接从缓存中获取

public boolean supportsParameter(MethodParameter parameter) {
    return this.getArgumentResolver(parameter) != null; //检查支持该参数的参数解析器是否为null
}
//挨个判断所有参数解析器哪个支持解析这个参数,并返回该参数解析器
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    HandlerMethodArgumentResolver result = (HandlerMethodArgumentResolver)this.argumentResolverCache.get(parameter); //先从缓存中找
    if (result == null) {
        Iterator var3 = this.argumentResolvers.iterator();

        while(var3.hasNext()) {
            HandlerMethodArgumentResolver resolver = (HandlerMethodArgumentResolver)var3.next();
            if (resolver.supportsParameter(parameter)) {
                result = resolver;
                this.argumentResolverCache.put(parameter, resolver); //如果找到则放入缓存中
                break;
            }
        }
    }
}

1.2.4.2 resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory)

@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    HandlerMethodArgumentResolver resolver = this.getArgumentResolver(parameter);  // 获取支持方法参数的参数解析器
    if (resolver == null) {
        throw new IllegalArgumentException("Unsupported parameter type [" + parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
    } else {
        return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory); // 解析参数
    }
}

resolveArgument内部会再配合UrlPathHelper(会将url中的变量解析出来,放在request的请求域中),最终得到变量值。

二、对于传入的是Servlet API的参数的处理

测试代码:

@GetMapping("/goto")
public String goToPage(HttpServletRequest request) {
    request.setAttribute("msg","成功了...");
    request.setAttribute("code",200);
    return "forward:/success";  //转发到  /success请求
}

当传入参数类型为HttpServletRequest时,请求最终还是会进入到resolvers.supportsParameter(parameter)方法

public boolean supportsParameter(MethodParameter parameter) {
    return this.getArgumentResolver(parameter) != null; //检查支持该参数的参数解析器是否为null
}
//挨个判断所有参数解析器哪个支持解析这个参数,并返回该参数解析器
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    HandlerMethodArgumentResolver result = (HandlerMethodArgumentResolver)this.argumentResolverCache.get(parameter); //先从缓存中找
    if (result == null) {
        Iterator var3 = this.argumentResolvers.iterator();

        while(var3.hasNext()) {
            HandlerMethodArgumentResolver resolver = (HandlerMethodArgumentResolver)var3.next();
            if (resolver.supportsParameter(parameter)) {
                result = resolver;
                this.argumentResolverCache.put(parameter, resolver); //如果找到则放入缓存中
                break;
            }
        }
    }
}

遍历那27个参数解析器,当遍历到ServletRequestMethodArgumentResolver时,进入supportsParameter方法

@Override
public boolean supportsParameter(MethodParameter parameter) {
    Class<?> paramType = parameter.getParameterType();
    return (WebRequest.class.isAssignableFrom(paramType) ||
            ServletRequest.class.isAssignableFrom(paramType) ||
            MultipartRequest.class.isAssignableFrom(paramType) ||
            HttpSession.class.isAssignableFrom(paramType) ||
            (pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
            Principal.class.isAssignableFrom(paramType) ||
            InputStream.class.isAssignableFrom(paramType) ||
            Reader.class.isAssignableFrom(paramType) ||
            HttpMethod.class == paramType ||
            Locale.class == paramType ||
            TimeZone.class == paramType ||
            ZoneId.class == paramType);
}

可以看出WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId这些都能通过ServletRequestMethodArgumentResolver进行解析。

三、请求方法参数为复杂参数

Map、Model(map、model里面的数据会被放在request的请求域 request.setAttribute)、Errors/BindingResult、RedirectAttributes( 重定向携带数据)、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilde也可以作为请求参数


我们在编写接口时会传入复杂参数,如Map、Model等,这种类似的参数会有相应的参数解析器进行解析,并且最后会将解析出的值放到request域中,下面我们一起来探析一下其中的原理。

测试链接:http://localhost:8080/params
测试代码:

@GetMapping("params")
public String testParam(Map<String,String> map,
                        Model model,
                        HttpServletRequest request,
                        HttpServletResponse response) {
    map.put("hello","hello666");
    model.addAttribute("world","world666");
    request.setAttribute("message","HelloWorld");

    Cookie cookie = new Cookie("c1","v1");
    response.addCookie(cookie);
    return "forward:/success";
}

3.1 获得目标方法的参数对象

获得目标方法的参数对象,对于Map、Model类型的参数,其值就是mavContainer的model属性

方法调用栈:

  1. 首先,解析参数都是跟上面一样的步骤,就是进行到resolvers.supportsParameter(parameter)这个方法后,遍历那26个参数解析器,拿到对应的解析器去解析,而对于map这类型的数据来说,就是对应的MapMerhodProcessor解析器
  2. 之后进行试图解析:
  3. 拿到解析器后,就得找对应的参数进行影射了,对于上面的使用@requestmapping注解的方法,它会去url缓存中获取参数值。而对于map类型的参数,它来到了mavContainer.getModel()这个方法,准备获取模型数据。

    getModel()这个方法他会返回一个ModeMap类型的数据

    最终,他是返回一个ModelMap的子类BindingAwareModelMap

    BindingAwareModelMap的继承树
  4. 然后开始解析下一个Model类型的参数

    视图解析的时候也调用getModel()方法返回一个ModeMap类型的数据

    debug后发现,居然跟解析Map类型调用的是一样的方法,而且两者返回的是同一个BindingAwareModelMap

3.2 执行目标请求方法,设置mavContainer的model属性

执行目标请求方法,并将目标方法的返回值进行返回

进入到请求方法内部

执行完目标方法后mavContainer中已经放入了数据

3.3 设置mavContainer的view属性

之后调用HandlerMethodReturnValueHandlerComposite的handleReturnValue()方法处理返回值

其内部又调用handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest)方法

此时的mavContainer(view已经为“”forward:/success)

3.4 获取ModelAndView对象(将mavContainer对象封装)

之后执行RequestMappingHandlerAdapter的getModelAndView()方法,返回ModelAndView
将所有的数据都放在 ModelAndView中;包含要去的页面地址View。还包含Model数据。

3.5 将数据放到request域中

此时doDispatch()方法中已经获得了带有数据的ModelAndView对象

方法的调用栈:

开始执行DispatchServlet的另一个方法processDispatchResult()方法,用来处理分发结果
然后又调用了render()方法,最终调用了InternalResourceView类中的renderMergedOutputModel()方法

protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
    this.exposeModelAsRequestAttributes(model, request); //暴露模型作为请求域属性
    this.exposeHelpers(request);
    String dispatcherPath = this.prepareForRendering(request, response);
    RequestDispatcher rd = this.getRequestDispatcher(request, dispatcherPath);
    if (rd == null) {
        throw new ServletException("Could not get RequestDispatcher for [" + this.getUrl() + "]: Check that the corresponding file exists within your web application archive!");
    } else {
        if (this.useInclude(request, response)) {
            response.setContentType(this.getContentType());
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Including [" + this.getUrl() + "]");
            }

            rd.include(request, response);
        } else {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Forwarding to [" + this.getUrl() + "]");
            }

            rd.forward(request, response);
        }

    }
}

exposeModelAsRequestAttributes()方法就是将model数据重新放入请求域中

protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception {
	//model中的所有数据遍历挨个放在请求域中
    model.forEach((name,以上是关于SpringBoot2各类型参数解析原理(源码分析)的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot 2从入门到入坟 | 请求参数处理篇:源码分析之各种类型参数解析原理

Springboot2-10-Thymeleaf

SpringBoot2之web开发(上)——之静态资源和请求参数处理

Dubbo配置参数源码解析-group

kafka源码ReassignPartitionsCommand源码原理分析(附配套教学视频)

SpringBoot2----定制化原理