springmvc--8-ServletModelAttributeMethodProcessor兜底处理@ModelAttribute注解和无注解

springmvc–ServletModelAttributeMethodProcessor兜底处理@ModelAttribute注解和无注解

1 简单介绍

如其名,它就是用来兜底处理@ModelAttribute注解和方法参数无注解情况

我们来看一下它的类图

在这里插入图片描述

从它继承的接口我们也能发现,该Processor拥有处理方法参数和返回值的功能

根据我们使用springmvc的经验,我们来看看@ModelAttribute注解和方法参数无注解这两种情况具体代表什么意思

  • @ModelAttribute注解

    • @ModelAttribute注解标注在方法上,这时候springmvc就会自动将方法返回值放入到Model
    • @ModelAttribute注解标注在参数上,这时候springmvc就会从Model中找到参数名对应的值,然后绑定到参数上
  • 方法参数无注解

    • 此种情况只适用于表单方式提交的请求,也就是说请求头的Content-Type字段必须为application/x-www-form-urlencoded
    • 只适用于javaBean类型参数,可以直接根据javaBean中的字段名进行数据绑定
    • 对于简单型参数,则是由RequestParamMethodArgumentResolver这个解析器处理的

1.1 创建位置

RequestMappingHandlerAdapter实现了InitializingBean接口,所以spring在它初始化阶段自动回调InitializingBean接口的afterPropertiesSet()方法,在这个方法中创建一些默认的参数解析器,其中就包含ServletModelAttributeMethodProcessor

@Override
public void afterPropertiesSet() {
    //.....................省略一些不相关的代码

    //用户未进行手动配置,就会使用springmvc默认配置的解析器
    if (this.argumentResolvers == null) {
        List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
        this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }

    //.....................省略一些不相关的代码
}

/**
 * Return the list of argument resolvers to use including built-in resolvers
 * and custom resolvers provided via {@link #setCustomArgumentResolvers}.
 */
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
    List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);

    // 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));
     /**
      * 在这里创建了ServletModelAttributeMethodProcessor对象
      * 注意,此时它的传入了true,表明可以没有@ModelAttribute注解
      */
    resolvers.add(new ServletModelAttributeMethodProcessor(true));

    return resolvers;
}

1.2 构造方法

/**
 * Class constructor.
 * @param annotationNotRequired if "true", non-simple method arguments and
 * return values are considered model attributes with or without a
 * {@code @ModelAttribute} annotation
 */
public ServletModelAttributeMethodProcessor(boolean annotationNotRequired) {
    super(annotationNotRequired);
}

看它父类ModelAttributeMethodProcessor的构造方法

//使用默认的参数名发现器
private static final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();

//@ModelAttribute注解是不是非必须的
private final boolean annotationNotRequired;


/**
 * Class constructor.
 * @param annotationNotRequired if "true", non-simple method arguments and
 * return values are considered model attributes with or without a
 * {@code @ModelAttribute} annotation
 */
public ModelAttributeMethodProcessor(boolean annotationNotRequired) {
    this.annotationNotRequired = annotationNotRequired;
}

可以看到,构造方法调用过程中只是简单的初始化了一个字段值

annotationNotRequired是一个重要的字段,它会参与判断该参数解析器能否解析指定参数(即supportsParameter()方法)

2 处理方法参数

这就离不开HandlerMethodArgumentResolver接口定义的方法了

public interface HandlerMethodArgumentResolver {

    /**
     * 判断是否支持处理该方法参数
     */
    boolean supportsParameter(MethodParameter parameter);

    /**
     * 解析该方法参数,得到参数值
     */
    @Nullable
    Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
                           NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;

}

那我们就按照顺序看这些方法的原理

2.1 supportsParameter(MethodParameter parameter)方法

/**
 * Returns {@code true} if the parameter is annotated with
 * {@link ModelAttribute} or, if in default resolution mode, for any
 * method parameter that is not a simple type.
 */
@Override
public boolean supportsParameter(MethodParameter parameter) {
    return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
            (this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
}

有两种判断通过条件

  • 方法参数上有@ModelAttribute注解,就支持解析该参数
  • annotationNotRequired字段为true,且非简单属性,就支持解析该参数

那么什么是简单属性呢?我们来看一看这个BeanUtils.isSimpleProperty()方法

/**
 * Check if the given type represents a "simple" property: a simple value
 * type or an array of simple value types.
 * <p>See {@link #isSimpleValueType(Class)} for the definition of <em>simple
 * value type</em>.
 * <p>Used to determine properties to check for a "simple" dependency-check.
 * @param type the type to check
 * @return whether the given type represents a "simple" property
 * @see org.springframework.beans.factory.support.RootBeanDefinition#DEPENDENCY_CHECK_SIMPLE
 * @see org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#checkDependencies
 * @see #isSimpleValueType(Class)
 */
public static boolean isSimpleProperty(Class<?> type) {
    Assert.notNull(type, "'type' must not be null");
    //getComponentType()方法 获取数组内元素的类型
    return isSimpleValueType(type) || (type.isArray() && isSimpleValueType(type.getComponentType()));
}



/**
 * Check if the given type represents a "simple" value type: a primitive or
 * primitive wrapper, an enum, a String or other CharSequence, a Number, a
 * Date, a Temporal, a URI, a URL, a Locale, or a Class.
 * <p>{@code Void} and {@code void} are not considered simple value types.
 * @param type the type to check
 * @return whether the given type represents a "simple" value type
 * @see #isSimpleProperty(Class)
 */
public static boolean isSimpleValueType(Class<?> type) {
    return (Void.class != type && void.class != type &&
            (ClassUtils.isPrimitiveOrWrapper(type) ||
             Enum.class.isAssignableFrom(type) ||
             CharSequence.class.isAssignableFrom(type) ||
             Number.class.isAssignableFrom(type) ||
             Date.class.isAssignableFrom(type) ||
             Temporal.class.isAssignableFrom(type) ||
             URI.class == type ||
             URL.class == type ||
             Locale.class == type ||
             Class.class == type));
}

简单类型如下所示:

  • 基本类型及其包装类型
  • Enum
  • CharSequence
  • Number
  • Date
  • Temporal
  • URI
  • URL
  • Locale
  • Class

这些类型都是这个参数解析器所不能处理的,除此之外,数组中元素类型如果是上面这些也不能处理

2.2 resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory)方法

/**
 * Resolve the argument from the model or if not found instantiate it with
 * its default if it is available. The model attribute is then populated
 * with request values via data binding and optionally validated
 * if {@code @java.validation.Valid} is present on the argument.
 * @throws BindException if data binding and validation result in an error
 * and the next method parameter is not of type {@link Errors}
 * @throws Exception if WebDataBinder initialization fails
 */
@Override
@Nullable
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");

    //获取参数名,见2.2.1
    String name = ModelFactory.getNameForParameter(parameter);
    //获取参数上标注的@ModelAttribute注解
    ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
    if (ann != null) {
        /**
         * @ModelAttribute注解的binding属性指定参数是否需要进行数据绑定
         * 这里通过setBinding()方法, 添加或删除不需要进行数据绑定的属性,见2.2.2
         */
        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 {
            //创建参数类型的实例,见2.2.3
            attribute = createAttribute(name, parameter, binderFactory, webRequest);
        }
        //捕获2.2.3中抛出的异常,再次进行处理
        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.
        //构造一个数据绑定器,用来将attribute对象绑定到处理器方法的name参数上
        WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
        //要绑定的对象不为null,即attribute不为null
        if (binder.getTarget() != null) {
            /**
             * 参数name需要进行数据绑定
             * isBindingDisabled()方法,实际上就是去验证2.2.2中
             * 不需要数据绑定的参数是否包含name
             */
            if (!mavContainer.isBindingDisabled(name)) {
                //将请求参数值绑定到JavaBean对象同名字段中,见2.2.4
                bindRequestParameters(binder, webRequest);
            }
            //校验参数,见2.2.5
            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
    //获取绑定结果的模型数据,见2.2.6
    Map<String, Object> bindingResultModel = bindingResult.getModel();
    //移除掉原来的,再重新添加
    mavContainer.removeAttributes(bindingResultModel);
    mavContainer.addAllAttributes(bindingResultModel);

    return attribute;
}

2.2.1 获取参数名

/**
 * Derive the model attribute name for the given method parameter based on
 * a {@code @ModelAttribute} parameter annotation (if present) or falling
 * back on parameter type based conventions.
 * @param parameter a descriptor for the method parameter
 * @return the derived name
 * @see Conventions#getVariableNameForParameter(MethodParameter)
 */
public static String getNameForParameter(MethodParameter parameter) {
    //@ModelAttribute注解的value属性值作为参数名
    ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
    String name = (ann != null ? ann.value() : null);
    //否则默认策略得到参数名
    return (StringUtils.hasText(name) ? name : Conventions.getVariableNameForParameter(parameter));
}

参数名的确定有两种方式

  • @ModelAttribute注解的value属性值作为参数名
  • 解析参数类型,得到参数类型的完全限定名,然后截取为短类名,首字母小写

2.2.2 指定参数是否需要进行数据绑定

//保存在该集合中属性不再参与数据绑定
private final Set<String> noBinding = new HashSet<>(4);

/**
 * Register whether data binding should occur for a corresponding model attribute,
 * corresponding to an {@code @ModelAttribute(binding=true/false)} declaration.
 * <p>Note: While this flag will be taken into account by {@link #isBindingDisabled},
 * a hard {@link #setBindingDisabled} declaration will always override it.
 * @param attributeName the name of the attribute
 * @since 4.3.13
 */
public void setBinding(String attributeName, boolean enabled) {
    if (!enabled) {
        this.noBinding.add(attributeName);
    }
    else {
        this.noBinding.remove(attributeName);
    }
}

2.2.3 创建参数类型的实例

/**
 * Instantiate the model attribute from a URI template variable or from a
 * request parameter if the name matches to the model attribute name and
 * if there is an appropriate type conversion strategy. If none of these
 * are true delegate back to the base class.
 * @see #createAttributeFromRequestValue
 */
@Override
protected final Object createAttribute(String attributeName, MethodParameter parameter,
                                       WebDataBinderFactory binderFactory, NativeWebRequest request) throws Exception {

    //尝试从模板变量或请求参数中获取方法参数名的对应值,见4.2
    String value = getRequestValueForAttribute(attributeName, request);
    if (value != null) {
        //参数名对应值进行类型转换,见4.3
        Object attribute = createAttributeFromRequestValue(
            value, attributeName, parameter, binderFactory, request);
        if (attribute != null) {
            return attribute;
        }
    }

    //父类方法完成参数类型实例的创建
    return super.createAttribute(attributeName, parameter, binderFactory, request);
}

该方法被子类ServletModelAttributeMethodProcessor重写,添加从模板变量或请求参数中获取方法参数名的对应值的能力。

/**
 * Extension point to create the model attribute if not found in the model,
 * with subsequent parameter binding through bean properties (unless suppressed).
 * <p>The default implementation typically uses the unique public no-arg constructor
 * if available but also handles a "primary constructor" approach for data classes:
 * It understands the JavaBeans {@link ConstructorProperties} annotation as well as
 * runtime-retained parameter names in the bytecode, associating request parameters
 * with constructor arguments by name. If no such constructor is found, the default
 * constructor will be used (even if not public), assuming subsequent bean property
 * bindings through setter methods.
 * @param attributeName the name of the attribute (never {@code null})
 * @param parameter the method parameter declaration
 * @param binderFactory for creating WebDataBinder instance
 * @param webRequest the current request
 * @return the created model attribute (never {@code null})
 * @throws BindException in case of constructor argument binding failure
 * @throws Exception in case of constructor invocation failure
 * @see #constructAttribute(Constructor, String, MethodParameter, WebDataBinderFactory, NativeWebRequest)
 * @see BeanUtils#findPrimaryConstructor(Class)
 */
protected Object createAttribute(String attributeName, MethodParameter parameter,
                                 WebDataBinderFactory binderFactory, NativeWebRequest webRequest) throws Exception {

    //获取到Optional容器中真实的方法参数
    MethodParameter nestedParameter = parameter.nestedIfOptional();
    //获取方法参数的嵌套类型
    Class<?> clazz = nestedParameter.getNestedParameterType();

    //获取主构造器,一般都是null,我们并没有指定
    Constructor<?> ctor = BeanUtils.findPrimaryConstructor(clazz);
    if (ctor == null) {
        //得到所有的构造器
        Constructor<?>[] ctors = clazz.getConstructors();
        //只有一个构造器,就使用唯一的一个
        if (ctors.length == 1) {
            ctor = ctors[0];
        }
        //多个构造器就获取无惨构造器
        else {
            try {
                ctor = clazz.getDeclaredConstructor();
            }
            catch (NoSuchMethodException ex) {
                throw new IllegalStateException("No primary or default constructor found for " + clazz, ex);
            }
        }
    }

    //使用构造器完成参数类型实例的创建
    Object attribute = constructAttribute(ctor, attributeName, parameter, binderFactory, webRequest);
    //很明显是Optional容器,包装一下
    if (parameter != nestedParameter) {
        attribute = Optional.of(attribute);
    }
    return attribute;
}

使用构造器完成参数类型实例的创建

/**
 * Construct a new attribute instance with the given constructor.
 * <p>Called from
 * {@link #createAttribute(String, MethodParameter, WebDataBinderFactory, NativeWebRequest)}
 * after constructor resolution.
 * @param ctor the constructor to use
 * @param attributeName the name of the attribute (never {@code null})
 * @param binderFactory for creating WebDataBinder instance
 * @param webRequest the current request
 * @return the created model attribute (never {@code null})
 * @throws BindException in case of constructor argument binding failure
 * @throws Exception in case of constructor invocation failure
 * @since 5.1
 */
@SuppressWarnings("deprecation")
protected Object constructAttribute(Constructor<?> ctor, String attributeName, MethodParameter parameter,
                                    WebDataBinderFactory binderFactory, NativeWebRequest webRequest) throws Exception {

    //该方法已经被废弃掉了,内部实现为空
    Object constructed = constructAttribute(ctor, attributeName, binderFactory, webRequest);
    if (constructed != null) {
        return constructed;
    }

    //无参构造器,直接实例化
    if (ctor.getParameterCount() == 0) {
        // A single default constructor -> clearly a standard JavaBeans arrangement.
        return BeanUtils.instantiateClass(ctor);
    }

    //有参构造器,解析构造器参数
    // A single data class constructor -> resolve constructor arguments from request parameters.
    //@ConstructorProperties注解,显式为构造器中的参数指定参数名
    ConstructorProperties cp = ctor.getAnnotation(ConstructorProperties.class);
    //获取构造器中所有的参数名
    String[] paramNames = (cp != null ? cp.value() : parameterNameDiscoverer.getParameterNames(ctor));
    Assert.state(paramNames != null, () -> "Cannot resolve parameter names for constructor " + ctor);
    //获取构造器中所有参数的类型
    Class<?>[] paramTypes = ctor.getParameterTypes();
    Assert.state(paramNames.length == paramTypes.length,
                 () -> "Invalid number of parameter names: " + paramNames.length + " for constructor " + ctor);

    Object[] args = new Object[paramTypes.length];
    //为处理器方法参数创建一个绑定器
    WebDataBinder binder = binderFactory.createBinder(webRequest, null, attributeName);
    //fieldDefaultPrefix = "!"
    String fieldDefaultPrefix = binder.getFieldDefaultPrefix();
    //fieldMarkerPrefix = "_"
    String fieldMarkerPrefix = binder.getFieldMarkerPrefix();
    boolean bindingFailure = false;
    Set<String> failedParams = new HashSet<>(4);

    for (int i = 0; i < paramNames.length; i++) {
        String paramName = paramNames[i];
        Class<?> paramType = paramTypes[i];
        //首先尝试直接通过构造器参数名获取
        Object value = webRequest.getParameterValues(paramName);
        if (value == null) {
            if (fieldDefaultPrefix != null) {
                //其次尝试通过"!"+构造器参数名获取
                value = webRequest.getParameter(fieldDefaultPrefix + paramName);
            }
            if (value == null && fieldMarkerPrefix != null) {
                //最后尝试通过"_"+构造器参数名获取
                if (webRequest.getParameter(fieldMarkerPrefix + paramName) != null) {
                    value = binder.getEmptyValue(paramType);
                }
            }
        }
        
        try {
            /**
             * 构造器参数对象
             * 这个类很简单,提供了两个字段
             * String parameterName	参数名
             * Annotation[] combinedAnnotations	合并的注解
             * ,并重写了getParameterAnnotations()方法
             * 用来合并父子类构造器参数上的注解信息
             */
            MethodParameter methodParam = new FieldAwareConstructorParameter(ctor, i, paramName);
            /**
             * 从请求参数中获取不到构造器参数名的对应值,且参数类型是Optional容器
             * 那么就直接创建一个空的Optional容器
             */
            if (value == null && methodParam.isOptional()) {
                args[i] = (methodParam.getParameterType() == Optional.class ? Optional.empty() : null);
            }
            //有值,类型转换
            else {
                args[i] = binder.convertIfNecessary(value, paramType, methodParam);
            }
        }
        catch (TypeMismatchException ex) {
            ex.initPropertyName(paramName);
            args[i] = value;
            failedParams.add(paramName);
            binder.getBindingResult().recordFieldValue(paramName, paramType, value);
            binder.getBindingErrorProcessor().processPropertyAccessException(ex, binder.getBindingResult());
            //只要出现异常就置为true,表明绑定失败
            bindingFailure = true;
        }
    }

    /**
     * 绑定失败的异常处理
     * 此处抛出的异常在外层方法中,又会被捕获
     */
    if (bindingFailure) {
        BindingResult result = binder.getBindingResult();
        for (int i = 0; i < paramNames.length; i++) {
            String paramName = paramNames[i];
            if (!failedParams.contains(paramName)) {
                Object value = args[i];
                result.recordFieldValue(paramName, paramTypes[i], value);
                validateValueIfApplicable(binder, parameter, ctor.getDeclaringClass(), paramName, value);
            }
        }
        throw new BindException(result);
    }

    //实例化
    return BeanUtils.instantiateClass(ctor, args);
}

总结一下创建参数实例的流程:

  • 先尝试从模板变量或请求参数中获取方法参数名的对应值,有值就直接进行类型转换
  • 否则尝试获取处理器方法参数的真实类型,使用反射得到它得构造器
  • 若是无参构造器,直接实例化
  • 若是有参构造器
    • 先尝试根据构造器参数名从请求参数中去获取对应值,有值就进行类型转换
    • 无值,则构造器参数类型是不是Optional容器,如果是,好办,直接创建一个空的Optional容器,否则构造器参数值为null
    • 上述过程中发送任何异常,被捕获之后,先进性初步处理,再抛出

2.2.4 将请求参数值绑定到JavaBean对象同名字段中

/**
 * This implementation downcasts {@link WebDataBinder} to
 * {@link ServletRequestDataBinder} before binding.
 * @see ServletRequestDataBinderFactory
 */
@Override
protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
    //获取原生的请求对象
    ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
    Assert.state(servletRequest != null, "No ServletRequest");
    ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;
    //请求参数值绑定到JavaBean对象同名字段中
    servletBinder.bind(servletRequest);
}

核心是数据绑定器的bind()方法,它内部完成请求参数值到JavaBean对象同名字段的绑定

2.2.5 参数校验

/**
 * Validate the model attribute if applicable.
 * <p>The default implementation checks for {@code @javax.validation.Valid},
 * Spring's {@link org.springframework.validation.annotation.Validated},
 * and custom annotations whose name starts with "Valid".
 * @param binder the DataBinder to be used
 * @param parameter the method parameter declaration
 * @see WebDataBinder#validate(Object...)
 * @see SmartValidator#validate(Object, Errors, Object...)
 */
protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
    //遍历参数上的所有注解,若标注了@Validated注解就进行校验
    for (Annotation ann : parameter.getParameterAnnotations()) {
        //获取@Validated注解的value属性值
        Object[] validationHints = determineValidationHints(ann);
        if (validationHints != null) {
            binder.validate(validationHints);
            break;
        }
    }
}


/**
 * Determine any validation triggered by the given annotation.
 * @param ann the annotation (potentially a validation annotation)
 * @return the validation hints to apply (possibly an empty array),
 * or {@code null} if this annotation does not trigger any validation
 * @since 5.1
 */
@Nullable
private Object[] determineValidationHints(Annotation ann) {
    //获取参数上标注的@Validated注解信息
    Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
    if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
        Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
        if (hints == null) {
            return new Object[0];
        }
        return (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
    }
    return null;
}

2.2.6 获取绑定结果的模型数据

/**
 * Return a model Map for the obtained state, exposing an Errors
 * instance as '{@link #MODEL_KEY_PREFIX MODEL_KEY_PREFIX} + objectName'
 * and the object itself.
 * <p>Note that the Map is constructed every time you're calling this method.
 * Adding things to the map and then re-calling this method will not work.
 * <p>The attributes in the model Map returned by this method are usually
 * included in the ModelAndView for a form view that uses Spring's bind tag,
 * which needs access to the Errors instance.
 * @see #getObjectName
 * @see #MODEL_KEY_PREFIX
 */
@Override
public Map<String, Object> getModel() {
    Map<String, Object> model = new LinkedHashMap<>(2);
    // Mapping from name to target object.
    //绑定的参数名->要绑定的对象
    model.put(getObjectName(), getTarget());
    // Errors instance, even if no errors.
    /**
     * MODEL_KEY_PREFIX=org.springframework.validation.BindingResult.
     * 前缀+绑定的参数名->绑定结果
     */
    model.put(MODEL_KEY_PREFIX + getObjectName(), this);
    return model;
}

保存两个映射关系

  • 绑定的参数名->要绑定的对象
  • 前缀+绑定的参数名->绑定结果

3 处理返回值

这就离不开HandlerMethodReturnValueHandler接口定义的方法了

public interface HandlerMethodReturnValueHandler {

   //判断是否支持处理该返回值
   boolean supportsReturnType(MethodParameter returnType);

   //处理返回值,写入响应体中
   void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
         ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;

}

那我们就按照顺序看这些方法的原理

3.1 supportsReturnType(MethodParameter returnType)方法

/**
 * Return {@code true} if there is a method-level {@code @ModelAttribute}
 * or, in default resolution mode, for any return value type that is not
 * a simple type.
 */
@Override
public boolean supportsReturnType(MethodParameter returnType) {
    return (returnType.hasMethodAnnotation(ModelAttribute.class) ||
            (this.annotationNotRequired && !BeanUtils.isSimpleProperty(returnType.getParameterType())));
}

有两种判断通过条件

  • 方法上有@ModelAttribute注解,就支持解析该返回值
  • annotationNotRequired字段为true,且方法声明的返回值类型是非简单属性,就支持解析该返回值

什么是非简单属性,见2.1

3.2 handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest)方法

/**
 * Add non-null return values to the {@link ModelAndViewContainer}.
 */
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
                              ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

    if (returnValue != null) {
        //生成返回值保存在模型中的名字
        String name = ModelFactory.getNameForReturnValue(returnValue, returnType);
        //返回值保存到模型中
        mavContainer.addAttribute(name, returnValue);
    }
}

生成返回值保存在模型中的名字

/**
 * Derive the model attribute name for the given return value. Results will be
 * based on:
 * <ol>
 * <li>the method {@code ModelAttribute} annotation value
 * <li>the declared return type if it is more specific than {@code Object}
 * <li>the actual return value type
 * </ol>
 * @param returnValue the value returned from a method invocation
 * @param returnType a descriptor for the return type of the method
 * @return the derived name (never {@code null} or empty String)
 */
public static String getNameForReturnValue(@Nullable Object returnValue, MethodParameter returnType) {
    //@ModelAttribute注解的value属性作为返回值的名字
    ModelAttribute ann = returnType.getMethodAnnotation(ModelAttribute.class);
    if (ann != null && StringUtils.hasText(ann.value())) {
        return ann.value();
    }
    //根据返回值的完全限定名,生成一个首字母小写的短类名作为名字
    else {
        Method method = returnType.getMethod();
        Assert.state(method != null, "No handler method");
        //包含返回值的类的clazz对象
        Class<?> containingClass = returnType.getContainingClass();
        //方法声明的返回值的泛型类型
        Class<?> resolvedType = GenericTypeResolver.resolveReturnType(method, containingClass);
        //一般都是首字母小写的短类名
        return Conventions.getVariableNameForReturnType(method, resolvedType, returnValue);
    }
}

返回值保存在模型中的名字,有两种选择

  • 用户可以在方法标注的@ModelAttribute注解上指定名字
  • 系统自动根据返回值类型生成一个首字母小写的短类名作为名字

4 ServletModelAttributeMethodProcessor拥有的一些重要方法

我将方法的实现细节抽取到该章节,有兴趣的就看一下

4.1 getUriTemplateVariables(NativeWebRequest request)方法,获取请求路径上模板变量的映射关系

protected final Map<String, String> getUriTemplateVariables(NativeWebRequest request) {
    /**
     * HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE=
     * org.springframework.web.servlet.HandlerMapping.uriTemplateVariables
     *
     * RequestAttributes.SCOPE_REQUEST=0,表示request作用域
     */
   Map<String, String> variables = (Map<String, String>) request.getAttribute(
         HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
   return (variables != null ? variables : Collections.emptyMap());
}

RequestMappingHandlerMapping获取请求路径对应的处理器方法时,就将请求路径完全解析了

  • 模板变量保存在请求域org.springframework.web.servlet.HandlerMapping.uriTemplateVariables属性中
  • 矩阵变量保存在请求域org.springframework.web.servlet.HandlerMapping.matrixVariables属性中

不熟悉的建议去了解一下RequestMappingHandlerMapping的处理过程

4.2 getRequestValueForAttribute(String attributeName, NativeWebRequest request)方法,从模板变量或请求参数中获取方法参数名的对应值

/**
 * Obtain a value from the request that may be used to instantiate the
 * model attribute through type conversion from String to the target type.
 * <p>The default implementation looks for the attribute name to match
 * a URI variable first and then a request parameter.
 * @param attributeName the model attribute name
 * @param request the current request
 * @return the request value to try to convert, or {@code null} if none
 */
@Nullable
protected String getRequestValueForAttribute(String attributeName, NativeWebRequest request) {
    //获取路径中模板变量的映射关系,见4.1
    Map<String, String> variables = getUriTemplateVariables(request);
    //从映射关系中获取方法参数名的对应值
    String variableValue = variables.get(attributeName);
    if (StringUtils.hasText(variableValue)) {
        return variableValue;
    }
    //从请求参数中获取方法参数名的对应值
    String parameterValue = request.getParameter(attributeName);
    if (StringUtils.hasText(parameterValue)) {
        return parameterValue;
    }
    return null;
}

该方法从两个途径获取参数名的对应值

  • 模板变量
  • 请求参数(请求参数包括表单提交的POST请求的请求体)

4.3 createAttributeFromRequestValue(String sourceValue, String attributeName,MethodParameter parameter, WebDataBinderFactory binderFactory, NativeWebRequest request)方法,参数名对应值进行类型转换

/**
 * Create a model attribute from a String request value (e.g. URI template
 * variable, request parameter) using type conversion.
 * <p>The default implementation converts only if there a registered
 * {@link Converter} that can perform the conversion.
 * @param sourceValue the source value to create the model attribute from
 * @param attributeName the name of the attribute (never {@code null})
 * @param parameter the method parameter
 * @param binderFactory for creating WebDataBinder instance
 * @param request the current request
 * @return the created model attribute, or {@code null} if no suitable
 * conversion found
 */
@Nullable
protected Object createAttributeFromRequestValue(String sourceValue, String attributeName,
                                                 MethodParameter parameter, WebDataBinderFactory binderFactory, NativeWebRequest request)
    throws Exception {

    //创建一个数据绑定器,主要是为了得到转换服务
    DataBinder binder = binderFactory.createBinder(request, null, attributeName);
    //使用统一转换服务将String->参数类型
    ConversionService conversionService = binder.getConversionService();
    if (conversionService != null) {
        TypeDescriptor source = TypeDescriptor.valueOf(String.class);
        TypeDescriptor target = new TypeDescriptor(parameter);
        if (conversionService.canConvert(source, target)) {
            return binder.convertIfNecessary(sourceValue, parameter.getParameterType(), parameter);
        }
    }
    return null;
}

该方法会将在4.2中得到的方法名对应值进行类型转换,转换为方法参数的实际类型。

  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值