springmvc–ServletModelAttributeMethodProcessor
兜底处理@ModelAttribute
注解和无注解
文章目录
- springmvc--`ServletModelAttributeMethodProcessor`兜底处理`@ModelAttribute`注解和无注解
- 1 简单介绍
- 2 处理方法参数
- 3 处理返回值
- 4 `ServletModelAttributeMethodProcessor`拥有的一些重要方法
- 4.1 `getUriTemplateVariables(NativeWebRequest request) `方法,获取请求路径上模板变量的映射关系
- 4.2 `getRequestValueForAttribute(String attributeName, NativeWebRequest request)`方法,从模板变量或请求参数中获取方法参数名的对应值
- 4.3 `createAttributeFromRequestValue(String sourceValue, String attributeName,MethodParameter parameter, WebDataBinderFactory binderFactory, NativeWebRequest request)`方法,参数名对应值进行类型转换
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
中得到的方法名对应值进行类型转换,转换为方法参数的实际类型。