SpringMVC常见组件之HandlerMethodArgumentResolver解析-2

本文涉及的产品
云解析DNS,个人版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: SpringMVC常见组件之HandlerMethodArgumentResolver解析-2

SpringMVC常见组件之HandlerMethodArgumentResolver解析-1https://developer.aliyun.com/article/1382039

【5】ServletModelAttributeMethodProcessor

如下图所示其继承自ModelAttributeMethodProcessor,实现了HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler两个接口,也就是说其提供了参数解析、返回结果处理两种功能。


0b68b5ff760c41d0bfa570bdaa2a4b54.png


① supportsParameter

如下所示,如果参数标注了@ModelAttribute注解或者参数非简单类型,则返回true。

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

② ServletModelAttributeMethodProcessor的几个方法

在正式分析参数解析前,我们先看一下ServletModelAttributeMethodProcessor的几个方法。其提供了createAttribute、getRequestValueForAttribute、getUriTemplateVariables、createAttributeFromRequestValue、bindRequestParameters以及resolveConstructorArgument六个方法。

① createAttribute


方法如下所示,首先从从URI Template Variables解析attributeName,如果得不到value则从request中解析。如果得到的value不为null,则尝试获取attribute(需要进行类型转换)。如果得到的value为null,则委托给基类处理。

@Override
protected final Object createAttribute(String attributeName, MethodParameter parameter,
    WebDataBinderFactory binderFactory, NativeWebRequest request) throws Exception {
  String value = getRequestValueForAttribute(attributeName, request);
  if (value != null) {
    Object attribute = createAttributeFromRequestValue(
        value, attributeName, parameter, binderFactory, request);
    if (attribute != null) {
      return attribute;
    }
  }
  return super.createAttribute(attributeName, parameter, binderFactory, request);
}


该方法是一个模板方法,分别调用了getRequestValueForAttributecreateAttributeFromRequestValue以及父类的createAttribute方法。


② getRequestValueForAttribute

@Nullable
protected String getRequestValueForAttribute(String attributeName, NativeWebRequest request) {
  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;
}
protected final Map<String, String> getUriTemplateVariables(NativeWebRequest request) {
  @SuppressWarnings("unchecked")
  Map<String, String> variables = (Map<String, String>) request.getAttribute(
      HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
  return (variables != null ? variables : Collections.emptyMap());
}

方法如上所示首先从请求域中获取HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE属性值Map variables,然后尝试从variables解析attributeName,如果得到值则返回。如果得不到值则采用基本的request.getParameter(attributeName);解析值,然后返回值或者null。


String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + “.uriTemplateVariables”

③ createAttributeFromRequestValue

当获取到属性值后,将会创建一个Model Attribute。直白点就是获取一个DataBinder 得到conversionService 然后尝试进行类型转换,将转换后的值(是一个复杂类型哦,比如SysUser)返回或者返回null。

@Nullable
protected Object createAttributeFromRequestValue(String sourceValue, String attributeName,
    MethodParameter parameter, WebDataBinderFactory binderFactory, NativeWebRequest request)
    throws Exception {
  DataBinder binder = binderFactory.createBinder(request, null, attributeName);
  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;
}

④ resolveConstructorArgument

解析构造参数对象,首先调用基类的resolveConstructorArgument方法,如果得到value不为null,则返回。然后尝试从servletRequest获取属性HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;的值Map uriVars,然后尝试从uriVars中解析paramName获取值返回。

@Override
@Nullable
public Object resolveConstructorArgument(String paramName, Class<?> paramType, NativeWebRequest request)
    throws Exception {
  Object value = super.resolveConstructorArgument(paramName, paramType, request);
  if (value != null) {
    return value;
  }
  ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
  if (servletRequest != null) {
    String attr = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
    @SuppressWarnings("unchecked")
    Map<String, String> uriVars = (Map<String, String>) servletRequest.getAttribute(attr);
    return uriVars.get(paramName);
  }
  return null;
}

⑤ bindRequestParameters


绑定请求参数,这时从请求参数到解析后的目标参数非常重要的一步。如下所示其转换为ServletRequestDataBinder 然后调用servletBinder.bind(servletRequest);方法进行处理。

@Override
protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
  ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
  Assert.state(servletRequest != null, "No ServletRequest");
  ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;
  servletBinder.bind(servletRequest);
}


这里我们可以先回顾一下ServletRequestDataBinder的继承树示意图

关于.ServletRequestDataBinder#bind更多详细过程可以参考博文SpringMVC常见组件之DataBinder数据绑定器分析

③ 父类核心方法resolveArgument

@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
    NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
// 首先校验 mavContainer  binderFactory 不能为null
  Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");
  Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");
//根据目标方法参数获取参数名字
  String name = ModelFactory.getNameForParameter(parameter);
  // 获取@ModelAttribute注解信息
  ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
  if (ann != null) {
    mavContainer.setBinding(name, ann.binding());
  }
// 目标方法参数对应的值对象
  Object attribute = null;
  // 数据绑定结果,通常保存Erros
  BindingResult bindingResult = null;
// 直接从model中获取
  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();
      }
      else {
        attribute = ex.getTarget();
      }
      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;
}


方法解释如下


① 校验mavContainer、binderFactory是否存在;

② 获取参数名字,如果标注了@ModelAttribute且其value不为null ,那么参数名字为value值;否则按照通用规则获取参数名;

③ 如果ModelAttribute注解不为null,则告诉mavContainer当前对象绑定;

④ 如果mavContainer有当前name(前面确定的参数名字,也就是key),那么从model中获取值对象attribute;

⑤ !④否则createAttribute(name, parameter, binderFactory, webRequest);获取一个值对象attribute;

⑥ 如果bindingResult为null(也就是前面没有抛出异常),那么执行如下步骤:

⑦ 创建WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);

⑧ 如果binder.getTarget() != null,且未进行过绑定,那么调用bindRequestParameters(binder, webRequest);为target对象属性赋值;

⑨ 调用validateIfApplicable(binder, parameter);判断是否需要校验,需要校验则调用校验器进行处理;

⑩ 如果绑定结果有error,且方法参数有Errors类型但是没有紧跟此处的目标参数(也就是非SysUser User ,Errors errors格式),将会抛出异常BindException

(11) 如果参数类型与值对象attribute类型不合适,那么尝试进行转换;

(12) bindingResultModel 数据放入mavContainer的model中。bindingResultModel 数据实例如下所示:

  • 下面我们看下这个过程中createAttribute方法。

① createAttribute

我们先回顾一下其子类ServletModelAttributeMethodProcessor的createAttribute方法:


① 尝试从URI Template Variables中获取参数name对应的值value;

② 如果①获取的value不为空则返回,否则尝试从request中获取参数name对应的value;

③ 如果前面获取的value不为null,则尝试创建一个值对象attribute不为null则返回;

④ 调用父类的createAttribute方法

protected Object createAttribute(String attributeName, MethodParameter parameter,
    WebDataBinderFactory binderFactory, NativeWebRequest webRequest) throws Exception {
  MethodParameter nestedParameter = parameter.nestedIfOptional();
  Class<?> clazz = nestedParameter.getNestedParameterType();
  Constructor<?> ctor = BeanUtils.getResolvableConstructor(clazz);
  Object attribute = constructAttribute(ctor, attributeName, parameter, binderFactory, webRequest);
  if (parameter != nestedParameter) {
    attribute = Optional.of(attribute);
  }
  return attribute;
}

父类的createAttribute方法如上所示,首先获取原始的具体的MethodParameter ,然后获取其Class对象,拿到其构造器后反射实例化一个空对象attribute 。

② validateValueIfApplicable

如果可以,那么进行校验。方法会获取参数的注解,检测是否能够进行校验。如果可以校验,那么会调用当前数据绑定器拥有的校验器进行校验。如果校验结果有异常信息将会放到BindingResult中。

protected void validateValueIfApplicable(WebDataBinder binder, MethodParameter parameter,
    Class<?> targetType, String fieldName, @Nullable Object value) {
  for (Annotation ann : parameter.getParameterAnnotations()) {
    Object[] validationHints = determineValidationHints(ann);
    if (validationHints != null) {
      for (Validator validator : binder.getValidators()) {
        if (validator instanceof SmartValidator) {
          try {
            ((SmartValidator) validator).validateValue(targetType, fieldName, value,
                binder.getBindingResult(), validationHints);
          }
          catch (IllegalArgumentException ex) {
            // No corresponding field on the target class...
          }
        }
      }
      break;
    }
  }
}

那么如何检测当前是否能够进行校验呢?如下所示如果注解是Validated类型或者注解名字以Valid开头,那么就可以进行校验。

@Nullable
private Object[] determineValidationHints(Annotation ann) {
  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;
}


【6】PathVariableMethodArgumentResolver

路径变量解析器,也就是解析URI中的参数值赋予方法参数。如下图所示当url为/testUser/1时,这里方法参数id为1。



① supportsParameter

方法如下所示,其支持解析两种类型:

  • @PathVariable !Map也就是标注了@PathVariable注解并且类型不是Map;
  • @PathVariable("指定名字") Map map 也就是说标注了@PathVariable注解并且类型是Map且@PathVariable注解value值不为空
@Override
public boolean supportsParameter(MethodParameter parameter) {
  if (!parameter.hasParameterAnnotation(PathVariable.class)) {
    return false;
  }
  if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
    PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class);
    return (pathVariable != null && StringUtils.hasText(pathVariable.value()));
  }
  return true;
}


② resolveName


方法如下所示,从request获取key为HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE属性得到一个Map类型的uriTemplateVars 。然后尝试从uriTemplateVars 解析属性name得到value返回,如果uriTemplateVars 为null则直接返回null。

@Override
@SuppressWarnings("unchecked")
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
  Map<String, String> uriTemplateVars = (Map<String, String>) request.getAttribute(
      HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
  return (uriTemplateVars != null ? uriTemplateVars.get(name) : null);
}

HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE是什么呢?


String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";。如下图所示:

3f57e993052b43408a3020fe7fc69ea1.png



什么时候将URL上的路径变量值放到请求中的呢?有两个地方(其实就是针对两大类HandlerMapping):

第一个地方

AbstractUrlHandlerMapping.lookupHandler() 
-->AbstractUrlHandlerMapping.buildPathExposingHandler
-->chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables)); 
-->preHandle()方法 
-->exposeUriTemplateVariables(this.uriTemplateVariables, request); 
-->request.setAttribute(URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables);


第二个地方是

AbstractHandlerMethodMapping.lookupHandlerMethod()
->RequestMappingInfoHandlerMapping.handleMatch()
–>RequestMappingInfoHandlerMapping.extractMatchDetails()
–>request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern);
request.setAttribute(URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriVariables);


③ PathVariableMapMethodArgumentResolver

我们顺带看一下其姊妹类PathVariableMapMethodArgumentResolver。如下所示其解析@PathVariable Map也就是说标注了@PathVariable注解,注解value为空,参数类型为Map。

@Override
public boolean supportsParameter(MethodParameter parameter) {
  PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);
  return (ann != null && Map.class.isAssignableFrom(parameter.getParameterType()) &&
      !StringUtils.hasText(ann.value()));
}

其解析值就更简单了,拿到uriTemplateVars后直接根据是否为空选择返回new LinkedHashMap<>(uriTemplateVars)或者Collections.emptyMap();

@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
    NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
  @SuppressWarnings("unchecked")
  Map<String, String> uriTemplateVars =
      (Map<String, String>) webRequest.getAttribute(
          HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
  if (!CollectionUtils.isEmpty(uriTemplateVars)) {
    return new LinkedHashMap<>(uriTemplateVars);
  }
  else {
    return Collections.emptyMap();
  }
}


目录
相关文章
|
3天前
|
Java 开发者 Spring
深入解析 @Transactional:Spring 事务管理的艺术及实战应对策略
深入解析 @Transactional:Spring 事务管理的艺术及实战应对策略
9 2
|
3天前
|
存储 缓存 Java
深入解析Spring框架中的ReflectionUtils
深入解析Spring框架中的ReflectionUtils
9 1
|
4天前
|
Java 数据库连接 Spring
Spring 整合 MyBatis 底层源码解析
Spring 整合 MyBatis 底层源码解析
|
4天前
|
前端开发 安全 Java
Spring EL表达式:概念、特性与应用深入解析
Spring EL表达式:概念、特性与应用深入解析
|
4天前
|
调度
SpringMVC----执行流程+底层解析
SpringMVC----执行流程+底层解析
|
2天前
|
JSON 缓存 Java
Spring Boot中的JSON解析优化
Spring Boot中的JSON解析优化
|
3天前
|
前端开发 程序员 UED
全面解析layui:掌握基础知识与实用技能(1. 核心组件与模块 2. 布局与容器 3. 弹出层与提示框;1. 数据表格与数据表单 2. 表单验证与提交 3. 图片轮播与导航菜单)
全面解析layui:掌握基础知识与实用技能(1. 核心组件与模块 2. 布局与容器 3. 弹出层与提示框;1. 数据表格与数据表单 2. 表单验证与提交 3. 图片轮播与导航菜单)
4 0
|
3天前
|
JSON 缓存 Java
Spring Boot中的JSON解析优化
Spring Boot中的JSON解析优化
|
3天前
|
Java 容器 Spring
Spring5源码解析5-ConfigurationClassPostProcessor (上)
Spring5源码解析5-ConfigurationClassPostProcessor (上)
|
3天前
|
Java 测试技术 应用服务中间件
Spring 与 Spring Boot:深入解析
Spring 与 Spring Boot:深入解析
8 0

推荐镜像

更多