SpringBoot各种类型参数解析原理

Posted 二木成林

tags:

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

视频地址:https://www.bilibili.com/video/BV19K4y1L7MT?p=32

我们在控制器中添加一个方法用于测试:

    // http://localhost:8888/user/33/name/zhangsan?age=12&inters=reading,playing,listening
    @GetMapping("/user/{id}/name/{username}")
    public Map<String, Object> getUser(@PathVariable("id") Integer id,
                                       @PathVariable("username") String username,
                                       @PathVariable Map<String, String> pv,
                                       @RequestHeader("User-Agent") String userAgent,
                                       @RequestHeader Map<String, String> headers,
                                       @RequestParam("age") Integer age,
                                       @RequestParam("inters") List<String> inters,
                                       @CookieValue("cookie") String cookie) {
        Map<String, Object> map = new HashMap<>();
        map.put("id", id);
        map.put("username", username);
        map.put("pv", pv);
        map.put("userAgent", userAgent);
        map.put("headers", headers);
        map.put("age", age);
        map.put("inters", inters);
        map.put("cookie", cookie);
        return map;
    }

浏览器访问,各参数的值都已经获得了:

所有的请求处理都是在DispatcherServlet,并且在doDispatcher()方法中,对于请求参数的处理核心代码就是:

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

进入到handle发现是一个接口:

public interface HandlerAdapter {
    boolean supports(Object var1);

    @Nullable
    ModelAndView handle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;

    long getLastModified(HttpServletRequest var1, Object var2);
}

我们需要查看具体的实现类AbstractHandlerMethodAdapter类,这是一个抽象类,因为我们使用了@RequestMapping,匹配的适配器就是这个。该类中有一个handle()方法,该方法内又调用了抽象方法handleInternal,执行后会返回一个ModelAndView。

查看handleInternal()方法的实现方法在RequestMappingHandlerAdapter类中,该方法的代码如下:

    protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
        this.checkRequest(request);
        ModelAndView mav;
        if (this.synchronizeOnSession) {
            HttpSession session = request.getSession(false);
            if (session != null) {
                Object mutex = WebUtils.getSessionMutex(session);
                synchronized(mutex) {
                    mav = this.invokeHandlerMethod(request, response, handlerMethod);
                }
            } else {
                mav = this.invokeHandlerMethod(request, response, handlerMethod);
            }
        } else {
            mav = this.invokeHandlerMethod(request, response, handlerMethod);
        }

        if (!response.containsHeader("Cache-Control")) {
            if (this.getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
                this.applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
            } else {
                this.prepareResponse(response);
            }
        }

        return mav;
    }

这个方法中最核心的代码是mav = this.invokeHandlerMethod(request, response, handlerMethod);,查看invokeHandlerMethod()方法的源码:

    @Nullable
    protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
        ServletWebRequest webRequest = new ServletWebRequest(request, response);

        Object result;
        try {
            WebDataBinderFactory binderFactory = this.getDataBinderFactory(handlerMethod);
            ModelFactory modelFactory = this.getModelFactory(handlerMethod, binderFactory);
            ServletInvocableHandlerMethod invocableMethod = this.createInvocableHandlerMethod(handlerMethod);
            if (this.argumentResolvers != null) {
                invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
            }

            if (this.returnValueHandlers != null) {
                invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
            }

            invocableMethod.setDataBinderFactory(binderFactory);
            invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
            ModelAndViewContainer mavContainer = new ModelAndViewContainer();
            mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
            modelFactory.initModel(webRequest, mavContainer, invocableMethod);
            mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
            AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
            asyncWebRequest.setTimeout(this.asyncRequestTimeout);
            WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
            asyncManager.setTaskExecutor(this.taskExecutor);
            asyncManager.setAsyncWebRequest(asyncWebRequest);
            asyncManager.registerCallableInterceptors(this.callableInterceptors);
            asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
            if (asyncManager.hasConcurrentResult()) {
                result = asyncManager.getConcurrentResult();
                mavContainer = (ModelAndViewContainer)asyncManager.getConcurrentResultContext()[0];
                asyncManager.clearConcurrentResult();
                LogFormatUtils.traceDebug(this.logger, (traceOn) -> {
                    String formatted = LogFormatUtils.formatValue(result, !traceOn);
                    return "Resume with async result [" + formatted + "]";
                });
                invocableMethod = invocableMethod.wrapConcurrentResult(result);
            }

            invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);
            if (!asyncManager.isConcurrentHandlingStarted()) {
                ModelAndView var15 = this.getModelAndView(mavContainer, modelFactory, webRequest);
                return var15;
            }

            result = null;
        } finally {
            webRequest.requestCompleted();
        }

        return (ModelAndView)result;
    }

这个方法内有两个变量需要注意,也是核心:

argumentResolvers参数解析器,有26种,底层是一个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;
}

我们来查看参数解析器,如果我们使用了@PathVariable注解获得参数就会通过下面的PathVariableMethodArgumentResolver方法参数解析器,其他类型的参数也会如此:

returnValueHandlers是返回值处理器,决定了目标方法到底能够写多少种类型的返回值,默认有15种

看完参数解析器和返回值处理器后,下面就开始是invokeAndHandle()方法,打断点跟踪该方法:

该方法在ServletInvocableHandlerMethod类种,源码如下,重要的是里面的invokeForRequest()方法

跟踪invokeForRequest()方法,来到InvocableHandlerMethod类,查看方法源码如下:

    @Nullable
    public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
        Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
        if (this.logger.isTraceEnabled()) {
            this.logger.trace("Arguments: " + Arrays.toString(args));
        }

        return this.doInvoke(args);
    }

    // 核心方法,获取参数值
    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)) {
                        throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
                    }

                    try {
                        // 核心,解析参数
                        args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
                    } catch (Exception var10) {
                        if (this.logger.isDebugEnabled()) {
                            String exMsg = var10.getMessage();
                            if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
                                this.logger.debug(formatArgumentError(parameter, exMsg));
                            }
                        }

                        throw var10;
                    }
                }
            }

            return args;
        }
    }

获取到的参数声明:

拿到参数解析器后,我们就可以获取到参数的值了

我们进入resolveArgument()方法中

进入到HandlerMethodArgumentResolver接口中,关注实现该接口的实现类AbstractNamedValueMethodArgumentResolver,该类又是一个抽象类,有resolveArgument()方法能够解析参数的值。

AbstractNamedValueMethodArgumentResolver抽象类又有很多具体实现类,解析各种参数

以上是关于SpringBoot各种类型参数解析原理的主要内容,如果未能解决你的问题,请参考以下文章

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

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

第269天学习打卡(知识点回顾 springboot视图解析原理流程)

SpringBoot 一篇搞定(Cookie Session 跳转 内容协商 converter解析器 thymeleaf)

SpringBoot启动及自动装配原理

SpringBoot Starter运行原理代码解析