4

前言

承接【深入浅出spring】Spring MVC 流程解析 -- HandlerAdapterRequestMappingHandlerAdapter一节,具体分析此adapter调用handler处理并获取ModelAndView的过程

这里为何文章名是InvocableHandlerMethod,不应该是RequestMappingHandlerAdapter么?

因为RequestMappingHandlerAdapter在内部对于每个请求,都会实例化一个ServletInvocableHandlerMethodInvocableHandlerMethod的子类)进行处理,是整个处理的核心入口

概述

先贴一张处理流程图,纵向表示方法的依次调用(核心逻辑),横向表示同一方法中的先后处理流程。

图片描述

从上面的流程图可以总结出几个核心处理逻辑:

  • 输入参数处理,包括数据的转换
  • 调用 handler method,获取返回参数。(采用反射的方式,调用@RequestMapping注解的方法)
  • 处理返回参数,并包装成ModelAndView

输入参数处理

概述

先看下InvocableHadlerMethod.getMethodArgumentValues方法,即,获取方法的输入参数。

    private Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {

        MethodParameter[] parameters = getMethodParameters();
        Object[] args = new Object[parameters.length];
        for (int i = 0; i < parameters.length; i++) {
            MethodParameter parameter = parameters[i];
            parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
            args[i] = resolveProvidedArgument(parameter, providedArgs);
            if (args[i] != null) {
                continue;
            }
            // 核心逻辑,获取能够处理入参的ArgumentResolver,然后解析参数
            if (this.argumentResolvers.supportsParameter(parameter)) {
                try {
                    args[i] = this.argumentResolvers.resolveArgument(
                            parameter, mavContainer, request, this.dataBinderFactory);
                    continue;
                }
                catch (Exception ex) {
                    if (logger.isDebugEnabled()) {
                        logger.debug(getArgumentResolutionErrorMessage("Failed to resolve", i), ex);
                    }
                    throw ex;
                }
            }
            if (args[i] == null) {
                throw new IllegalStateException("Could not resolve method parameter at index " +
                        parameter.getParameterIndex() + " in " + parameter.getExecutable().toGenericString() +
                        ": " + getArgumentResolutionErrorMessage("No suitable resolver for", i));
            }
        }
        return args;
    }

从源码很容易得出,和之前的handlerMappingshandlerAdapters类似,都是从一个bean列表中遍历,找到一个能够处理的bean,然后调用bean的核心方法处理。这里的这个列表就是HandlerMethodArgumentResolver,此接口的定义:

public interface HandlerMethodArgumentResolver {

    boolean supportsParameter(MethodParameter parameter);

    @Nullable
    Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;

}

很清晰,通过supportsParameter筛选符合条件的resolver,然后调用resolverresolveArgument解析前端参数

默认HandlerMethodArgumentResolver

入口是RequestMappingHandlerAdapter.afterPropertiesSet

    public void afterPropertiesSet() {
        // Do this first, it may add ResponseBody advice beans
        initControllerAdviceCache();

        if (this.argumentResolvers == null) {
            List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
            this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
        }
        if (this.initBinderArgumentResolvers == null) {
            List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
            this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
        }
        if (this.returnValueHandlers == null) {
            List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
            this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
        }
    }

    private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
        List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();

        // Annotation-based argument resolution
        resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
        resolvers.add(new RequestParamMapMethodArgumentResolver());
        resolvers.add(new PathVariableMethodArgumentResolver());
        resolvers.add(new PathVariableMapMethodArgumentResolver());
        resolvers.add(new MatrixVariableMethodArgumentResolver());
        resolvers.add(new MatrixVariableMapMethodArgumentResolver());
        resolvers.add(new ServletModelAttributeMethodProcessor(false));
        resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
        resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
        resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
        resolvers.add(new RequestHeaderMapMethodArgumentResolver());
        resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
        resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
        resolvers.add(new SessionAttributeMethodArgumentResolver());
        resolvers.add(new RequestAttributeMethodArgumentResolver());

        // Type-based argument resolution
        resolvers.add(new ServletRequestMethodArgumentResolver());
        resolvers.add(new ServletResponseMethodArgumentResolver());
        resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
        resolvers.add(new RedirectAttributesMethodArgumentResolver());
        resolvers.add(new ModelMethodProcessor());
        resolvers.add(new MapMethodProcessor());
        resolvers.add(new ErrorsMethodArgumentResolver());
        resolvers.add(new SessionStatusMethodArgumentResolver());
        resolvers.add(new UriComponentsBuilderMethodArgumentResolver());

        // Custom arguments
        if (getCustomArgumentResolvers() != null) {
            resolvers.addAll(getCustomArgumentResolvers());
        }

        // Catch-all
        resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
        resolvers.add(new ServletModelAttributeMethodProcessor(true));

        return resolvers;
    }

getDefaultArgumentResolvers方法的逻辑主要分3块:

  • 可以看到spring提供了非常多的HandlerMethodArgumentResolver,如果想要知道每个resolver处理的入参场景可以查看resolversupportsParameter方法,查看了几个,发现很多都是通过注解来区分的,比如@PathVariable, @RequestParam, @ModelAttribute等。
  • 用户可以自定义resolver,通过注解来指定处理的参数类型。然后通过getCustomArgumentResolvers方法会注册到revolver列表总
  • RequestParamMethodArgumentResolverServletModelAttributeMethodProcessor做为默认resolver,试图处理入参没有注解的情况,具体支持哪类入参类型,还需查看supportsParameter方法

这里我们着重介绍下RequestParamMethodArgumentResolver

RequestParamMethodArgumentResolver

支持哪些参数处理

RequestParamMethodArgumentResolver.supportsParameter方法源码

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

简单总结,就是处理标注了@RequestParam的参数;在没有显示标注注解时,处理简单类型的参数(包括String,int,array,float,Date 等,不包括 Map, Collection, 自定义类等)。

解析参数

下面来看下核心方法resolveArgumentRequestParamMethodArgumentResolver没有重写此方法,故真正执行的是其父类AbstractNamedValueMethodArgumentResolver的方法

    public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

        NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
        MethodParameter nestedParameter = parameter.nestedIfOptional();

        Object resolvedName = resolveStringValue(namedValueInfo.name);
        if (resolvedName == null) {
            throw new IllegalArgumentException(
                    "Specified name must not resolve to null: [" + namedValueInfo.name + "]");
        }

        Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
        if (arg == null) {
            if (namedValueInfo.defaultValue != null) {
                arg = resolveStringValue(namedValueInfo.defaultValue);
            }
            else if (namedValueInfo.required && !nestedParameter.isOptional()) {
                handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
            }
            arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
        }
        else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
            arg = resolveStringValue(namedValueInfo.defaultValue);
        }

        if (binderFactory != null) {
            WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
            try {
                arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
            }
            catch (ConversionNotSupportedException ex) {
                throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
                        namedValueInfo.name, parameter, ex.getCause());
            }
            catch (TypeMismatchException ex) {
                throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
                        namedValueInfo.name, parameter, ex.getCause());

            }
        }

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

        return arg;
    }

其中核心逻辑是arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);,继续跟踪源码,会发现最后会调用的是TypeConverterDelegate.convertIfNecessary方法,这个方法比较长,不贴了,总结下来核心逻辑如下:

  • PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName),试图根据后端变量的类型和名词来查找是否有自定义的PropertyEditor用于后续的字段转换
  • 如果没有自定义的PropertyEditor,会试图用ConversionService来转换参数,转换成功直接返回,转换失败则抛异常。ConversionService会默认注册Converter,具体初始化源码WebMvcAutoConfiguration.mvcConversionService
  • PropertyEditor优先于ConversionService,即如果自定义了PropertyEditor,会优先使用自定义的PropertyEditor来做参数转换。

关于参数转换相关的topic会另起一篇文章,重点解释,涉及DataBinder,ConversionService的相关机制和原理,以及PropertyEditor, Formatter, Converter的应用场景

ServletModelAttributeMethodProcessor

支持哪些参数处理

ServletModelAttributeMethodProcessor没有重写supportsParameter,故执行的是其父类ModelAttributeMethodProcessor的方法,源码如下:

    public boolean supportsParameter(MethodParameter parameter) {
        return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
                (this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
    }

可以看到处理两类参数:

  • @ModelAttribute注解标记的入参
  • 没有注解标记(上面默认注册resolver的代码中有体现,catch-all 那段),且参数为非简单类型(正好和RequestParamMethodArgumentResolver互补,两者的并集就是全集了)。因此如果入参没有显式标注注解的话,默认处理的resolver要么是RequestParamMethodArgumentResolver,要么是ServletModelAttributeMethodProcessor

解析参数

执行父类ModelAttributeMethodProcessorresolveArgument方法

    public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

        Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");
        Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");

        String name = ModelFactory.getNameForParameter(parameter);
        ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
        if (ann != null) {
            mavContainer.setBinding(name, ann.binding());
        }

        Object attribute = null;
        BindingResult bindingResult = null;

        if (mavContainer.containsAttribute(name)) {
            attribute = mavContainer.getModel().get(name);
        }
        else {
            // Create attribute instance
            try {
                attribute = createAttribute(name, parameter, binderFactory, webRequest);
            }
            catch (BindException ex) {
                if (isBindExceptionRequired(parameter)) {
                    // No BindingResult parameter -> fail with BindException
                    throw ex;
                }
                // Otherwise, expose null/empty value and associated BindingResult
                if (parameter.getParameterType() == Optional.class) {
                    attribute = Optional.empty();
                }
                bindingResult = ex.getBindingResult();
            }
        }

        if (bindingResult == null) {
            // Bean property binding and validation;
            // skipped in case of binding failure on construction.
            WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
            if (binder.getTarget() != null) {
                if (!mavContainer.isBindingDisabled(name)) {
                    bindRequestParameters(binder, webRequest);
                }
                validateIfApplicable(binder, parameter);
                if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
                    throw new BindException(binder.getBindingResult());
                }
            }
            // Value type adaptation, also covering java.util.Optional
            if (!parameter.getParameterType().isInstance(attribute)) {
                attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
            }
            bindingResult = binder.getBindingResult();
        }

        // Add resolved attribute and BindingResult at the end of the model
        Map<String, Object> bindingResultModel = bindingResult.getModel();
        mavContainer.removeAttributes(bindingResultModel);
        mavContainer.addAllAttributes(bindingResultModel);

        return attribute;
    }

核心逻辑总结如下:

  • 变量名获取。这里有坑,如果不通过@ModelAttribute显式指定参数名,默认会根据参数名称来生成参数名。比如 List<String> names的参数名会变成stringList,自定义类会用类名做为参数名(自定义类的参数,其实参数名无所谓,不会根据参数名从前端请求中获取数据)
  • 创建复合类型参数的实例,通过反射调用类的构造方法进行初始化,即上述代码中的attribute = createAttribute(name, parameter, binderFactory, webRequest)
  • 如果参数有自定义转换逻辑(DataBinder),进行数据转换。与RequestParamMethodArgumentResolver的数据转换逻辑一样,入口同样是DataBinder.convertIfNecessary

返回参数处理

原理和输入参数类似,遍历HandlerMethodReturnValueHandler列表,根据返回参数类型,选择对应的HandlerMethodReturnValueHandler的实现类,通过调用方法handleReturnValue实现返回参数的处理。

HandlerMethodReturnValueHandler接口定义

public interface HandlerMethodReturnValueHandler {

    boolean supportsReturnType(MethodParameter returnType);

    void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
            ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;

}

这里不具体展开了,对于返回类型是ModelAndView的参数,对应的handlerModelAndViewMethodReturnValueHandler,处理逻辑比较简单,把返回参数的model,view,status变量赋值给ModelAndViewContainer

总结

此问主要介绍了RequestMappingHandlerAdapter的处理流程,包括输入参数解析、方法调用、返回参数处理,其中输入参数解析转换知识点比较多,特别是数据转换这块,后续会通过具体的例子详细介绍。此文先以HandlerMethodArgumentResolverServletModelAttributeMethodProcessor为例,大致对处理流程有一个概念,理解这两个resolver的应用场景,前者处理简单类型的入参(@RequestParam),后者处理复合类型的入参(可以将前端参数聚合映射为后端的自定义类,即数据模型,对应@ModelAttribute)。还有很多其他数据绑定相关的注解并未在本文介绍,比如@PathVariable, @CookieValue, @RequestHeader, @RequestBody等。


skyarthur
1.6k 声望1.3k 粉丝

技术支持业务,技术增强业务,技术驱动业务