小BUG大原理:重写WebMvcConfigurationSupport后SpringBoot自动配置失效 (2)

核心方法 invokeAndHandle 囊括了从请求到响应几乎整个 SpringMVC 生命周期

 public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
    // 调用请求
    Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);
    ...
    try {
        // 处理返回值
        this.returnValueHandlers.handleReturnValue(returnValue, this.getReturnValueType(returnValue), mavContainer, webRequest);
    }
    ...
}

本篇的 BUG 也就在于处理请求阶段的问题,所以我们来看下 ServletInvocableHandlerMethod#invokeForRequest 方法。

public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
    Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
    ...
    return this.doInvoke(args);
}

方法内部通过调用父类 InvocableHandlerMethod#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) {
                ...
                try {
                    // 调用HandlerMethodArgumentResolverComposite的resolveArgument解析参数获取返回值
                    args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
                }
                ...
            }
        }

        return args;
    }
}

this.resolvers 是 HandlerMethodArgumentResolverComposite(相当于组合模式的变种),同时实现了 HandlerMethodArgumentResolver 接口,内部又包含所有参数解析器的列表。

public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {
    // SpringMVC参数解析器的集合列表
    private final List<HandlerMethodArgumentResolver> argumentResolvers = new LinkedList();
    ...
    public boolean supportsParameter(MethodParameter parameter) {
        return this.getArgumentResolver(parameter) != null;
    }

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

    @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;
                }
            }
        }

        return result;
    }

}

MethodParameter 是处理器方法(HandlerMethod)的一个 HandlerMethodParameter 处理器方法参数信息,这里面其中就包含了描述方法参数的注解信息(eg:@RequestParam)。然后需要根据参数信息从参数解析器列表查找适配的参数解析器。

小BUG大原理:重写WebMvcConfigurationSupport后SpringBoot自动配置失效

终于,在 27 个参数解析器中找到了 RequestParamMapMethodArgumentResolver 解析器,那我们去看下这个解析器做的适配方法 supportsParameter。

public boolean supportsParameter(MethodParameter parameter) {
    if (parameter.hasParameterAnnotation(RequestParam.class)) {
        if (!Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
            return true;
        } else {
            RequestParam requestParam = (RequestParam)parameter.getParameterAnnotation(RequestParam.class);
            return requestParam != null && StringUtils.hasText(requestParam.name());
        }
    } else if (parameter.hasParameterAnnotation(RequestPart.class)) {
        return false;
    } else {
        parameter = parameter.nestedIfOptional();
        if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
            return true;
        } else {
            return this.useDefaultResolution ? BeanUtils.isSimpleProperty(parameter.getNestedParameterType()) : false;
        }
    }
}

可以看到 RequestParamMapMethodArgumentResolver 支持被注解@RequestParam、@RequestPart 修饰的方法参数。

在确定了参数解析器后,使用解析器的 resolveArgument 方法解析参数。RequestParamMapMethodArgumentResolver 自身没有 resolveArgument 方法,而是使用父类 AbstractNamedValueMethodArgumentResolver 的 resolveArgument 的方法。

public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    // 提取注解的属性键值(eg: 注解RequestParam的name、required、defaultValue)
    AbstractNamedValueMethodArgumentResolver.NamedValueInfo namedValueInfo = this.getNamedValueInfo(parameter);
    MethodParameter nestedParameter = parameter.nestedIfOptional();
    // 获取处理器方法参数名
    Object resolvedName = this.resolveStringValue(namedValueInfo.name);
    if (resolvedName == null) {
        throw new IllegalArgumentException("Specified name must not resolve to null: [" + namedValueInfo.name + "]");
    } else {
        //  根据参数名从request请求对象获取值
        Object arg = this.resolveName(resolvedName.toString(), nestedParameter, webRequest);
        if (arg == null) {
            if (namedValueInfo.defaultValue != null) {
                arg = this.resolveStringValue(namedValueInfo.defaultValue);
            } else if (namedValueInfo.required && !nestedParameter.isOptional()) {
                this.handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
            }

            arg = this.handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
        } else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
            arg = this.resolveStringValue(namedValueInfo.defaultValue);
        }

        if (binderFactory != null) {
            WebDataBinder binder = binderFactory.createBinder(webRequest, (Object)null, namedValueInfo.name);

            try {
                arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
            } catch (ConversionNotSupportedException var11) {
                throw new MethodArgumentConversionNotSupportedException(arg, var11.getRequiredType(), namedValueInfo.name, parameter, var11.getCause());
            } catch (TypeMismatchException var12) {
                throw new MethodArgumentTypeMismatchException(arg, var12.getRequiredType(), namedValueInfo.name, parameter, var12.getCause());
            }
        }

        this.handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
        return arg;
    }
}

这样一个一个的解析处理器方法参数,直到把方法所有的参数都从 request 拿到对应的值之后,返回 args。对应逻辑在 ServletInvocableHandlerMethod#invokeForRequest,最后返回参数数组 args。这样整个参数解析完成之后执行后面的逻辑 this.doInvoke(args)。

至此,SpringMVC 请求处理流程就结束了。

总结下整个 SpringMVC 请求处理的流程:

请求由 DispatcherServlet 拦截处理。

根据 request 信息从 HandlerMapping 映射关系获取对应的 HandlerExecutionChain 执行链(包含了处理器 handler 和 interceptor)。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wsxxjw.html