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 文件存储
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录—— logback.xml 配置文件解析
本文解析了 `logback.xml` 配置文件的详细内容,包括日志输出格式、存储路径、控制台输出及日志级别等关键配置。通过定义 `LOG_PATTERN` 和 `FILE_PATH`,设置日志格式与存储路径;利用 `&lt;appender&gt;` 节点配置控制台和文件输出,支持日志滚动策略(如文件大小限制和保存时长);最后通过 `&lt;logger&gt;` 和 `&lt;root&gt;` 定义日志级别与输出方式。此配置适用于精细化管理日志输出,满足不同场景需求。
24 1
|
3月前
|
前端开发 JavaScript
React 步骤条组件 Stepper 深入解析与常见问题
步骤条组件是构建多步骤表单或流程时的有力工具,帮助用户了解进度并导航。本文介绍了在React中实现简单步骤条的方法,包括基本结构、状态管理、样式处理及常见问题解决策略,如状态管理库的使用、自定义Hook的提取和CSS Modules的应用,以确保组件的健壮性和可维护性。
102 17
|
21天前
|
索引
【Flutter 开发必备】AzListView 组件全解析,打造丝滑索引列表!
在 Flutter 开发中,AzListView 是实现字母索引分类列表的理想选择。它支持 A-Z 快速跳转、悬浮分组标题、自定义 UI 和高效性能,适用于通讯录、城市选择等场景。本文将详细解析 AzListView 的核心参数和实战示例,助你轻松实现流畅的索引列表。
39 7
|
2月前
|
XML Java 开发者
Spring底层架构核心概念解析
理解 Spring 框架的核心概念对于开发和维护 Spring 应用程序至关重要。IOC 和 AOP 是其两个关键特性,通过依赖注入和面向切面编程实现了高效的模块化和松耦合设计。Spring 容器管理着 Beans 的生命周期和配置,而核心模块为各种应用场景提供了丰富的功能支持。通过全面掌握这些核心概念,开发者可以更加高效地利用 Spring 框架开发企业级应用。
95 18
|
3月前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
1月前
|
传感器 监控 安全
智慧工地云平台的技术架构解析:微服务+Spring Cloud如何支撑海量数据?
慧工地解决方案依托AI、物联网和BIM技术,实现对施工现场的全方位、立体化管理。通过规范施工、减少安全隐患、节省人力、降低运营成本,提升工地管理的安全性、效率和精益度。该方案适用于大型建筑、基础设施、房地产开发等场景,具备微服务架构、大数据与AI分析、物联网设备联网、多端协同等创新点,推动建筑行业向数字化、智能化转型。未来将融合5G、区块链等技术,助力智慧城市建设。
|
3月前
|
前端开发 UED
React 文本区域组件 Textarea:深入解析与优化
本文介绍了 React 中 Textarea 组件的基础用法、常见问题及优化方法,包括状态绑定、初始值设置、样式自定义、性能优化和跨浏览器兼容性处理,并提供了代码案例。
140 8
|
4月前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
325 2
|
11天前
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
61 29
|
8天前
|
前端开发 数据安全/隐私保护 CDN
二次元聚合短视频解析去水印系统源码
二次元聚合短视频解析去水印系统源码
27 3

推荐镜像

更多