从原理层面掌握@ModelAttribute的使用(核心原理篇)【享学Spring MVC】(下)

简介: 从原理层面掌握@ModelAttribute的使用(核心原理篇)【享学Spring MVC】(下)

ModelAttributeMethodProcessor


从命名上看它是个Processor,所以根据经验它既能处理入参,也能处理方法的返回值:HandlerMethodArgumentResolver + HandlerMethodReturnValueHandler。解析@ModelAttribute注解标注的方法参数,并处理@ModelAttribute标注的方法返回值。


先看它对方法入参的处理(稍显复杂):


// 这个处理器用于处理入参、方法返回值~~~~
// @since 3.1
public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
  private static final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
  private final boolean annotationNotRequired;
  public ModelAttributeMethodProcessor(boolean annotationNotRequired) {
    this.annotationNotRequired = annotationNotRequired;
  }
  // 入参里标注了@ModelAttribute 或者(注意这个或者) annotationNotRequired = true并且不是isSimpleProperty()
  // isSimpleProperty():八大基本类型/包装类型、Enum、Number等等 Date Class等等等等
  // 所以划重点:即使你没标注@ModelAttribute  单子还要不是基本类型等类型,都会进入到这里来处理
  // 当然这个行为是是收到annotationNotRequired属性影响的,具体的具体而论  它既有false的时候  也有true的时候
  @Override
  public boolean supportsParameter(MethodParameter parameter) {
    return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
        (this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
  }
  // 说明:能进入到这里来的  证明入参里肯定是有对应注解的???
  // 显然不是,上面有说  这事和属性值annotationNotRequired有关的~~~
  @Override
  @Nullable
  public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    // 拿到ModelKey名称~~~(注解里有写就以注解的为准)
    String name = ModelFactory.getNameForParameter(parameter);
    // 拿到参数的注解本身
    ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
    if (ann != null) {
      mavContainer.setBinding(name, ann.binding());
    }
    Object attribute = null;
    BindingResult bindingResult = null;
    // 如果model里有这个属性,那就好说,直接拿出来完事~
    if (mavContainer.containsAttribute(name)) {
      attribute = mavContainer.getModel().get(name);
    } else { // 若不存在,也不能让是null呀
      // Create attribute instance
      // 这是一个复杂的创建逻辑:
      // 1、如果是空构造,直接new一个实例出来
      // 2、若不是空构造,支持@ConstructorProperties解析给构造赋值
      //   注意:这里就支持fieldDefaultPrefix前缀、fieldMarkerPrefix分隔符等能力了 最终完成获取一个属性
      // 调用BeanUtils.instantiateClass(ctor, args)来创建实例
      // 注意:但若是非空构造出来,是立马会执行valid校验的,此步骤若是空构造生成的实例,此步不会进行valid的,但是下一步会哦~
      try {
        attribute = createAttribute(name, parameter, binderFactory, webRequest);
      } catch (BindException ex) {
        if (isBindExceptionRequired(parameter)) {
          // No BindingResult parameter -> fail with BindException
          throw ex;
        }
        // Otherwise, expose null/empty value and associated BindingResult
        if (parameter.getParameterType() == Optional.class) {
          attribute = Optional.empty();
        }
        bindingResult = ex.getBindingResult();
      }
    }
    // 若是空构造创建出来的实例,这里会进行数据校验  此处使用到了((WebRequestDataBinder) binder).bind(request);  bind()方法  唯一一处
    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) {
        // 绑定request请求数据
        if (!mavContainer.isBindingDisabled(name)) {
          bindRequestParameters(binder, webRequest);
        }
        // 执行valid校验~~~~
        validateIfApplicable(binder, parameter);
        //注意:此处抛出的异常是BindException
        //RequestResponseBodyMethodProcessor抛出的异常是:MethodArgumentNotValidException
        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
    // at the end of the model  把解决好的属性放到Model的末尾~~~
    // 可以即使是标注在入参上的@ModelAtrribute的属性值,最终也都是会放进Model里的~~~可怕吧
    Map<String, Object> bindingResultModel = bindingResult.getModel();
    mavContainer.removeAttributes(bindingResultModel);
    mavContainer.addAllAttributes(bindingResultModel);
    return attribute;
  }
  // 此方法`ServletModelAttributeMethodProcessor`子类是有复写的哦~~~~
  // 使用了更强大的:ServletRequestDataBinder.bind(ServletRequest request)方法
  protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
    ((WebRequestDataBinder) binder).bind(request);
  }
}


模型属性首先从Model中获取,若没有获取到,就使用默认构造函数(可能是有无参,也可能是有参)创建,然后会把ServletRequest请求的数据绑定上来, 然后进行@Valid校验(若添加有校验注解的话),最后会把属性添加到Model里面


最后加进去的代码是:mavContainer.addAllAttributes(bindingResultModel);这里我贴出参考值:

image.png


如下示例,它会正常打印person的值,而不是null(因为Model内有person了~)

请求链接是:/testModelAttr?name=wo&age=10


    @GetMapping("/testModelAttr")
    public void testModelAttr(@Valid Person person, ModelMap modelMap) {
        Object personAttr = modelMap.get("person");
        System.out.println(personAttr); //Person(name=wo, age=10)
    }



注意:虽然person上没有标注@ModelAtrribute,但是modelMap.get("person")依然是能够获取到值的哦,至于为什么,原因上面已经分析了,可自行思考。



下例中:


    @GetMapping("/testModelAttr")
    public void testModelAttr(Integer age, Person person, ModelMap modelMap) {
        System.out.println(age); // 直接封装的值
        System.out.println("-------------------------------");
        System.out.println(modelMap.get("age"));
        System.out.println(modelMap.get("person"));
    }


请求:/testModelAttr?name=wo&age=10 输入为:

10
-------------------------------
null
Person(name=wo, age=10)


可以看到普通类型(注意理解这个普通类型)若不标注@ModelAtrribute,它是不会自动识别为Model而放进来的哟~~~若你这么写:


    @GetMapping("/testModelAttr")
    public void testModelAttr(@ModelAttribute("age") Integer age, Person person, ModelMap modelMap) {
        System.out.println(age); // 直接封装的值
        System.out.println("-------------------------------");
        System.out.println(modelMap.get("age"));
        System.out.println(modelMap.get("person"));
    }


打印如下:

10
-------------------------------
10
Person(name=wo, age=10)


请务必注意以上case的区别,加深记忆。使用的时候可别踩坑了~

再看它对方法(返回值)的处理(很简单):


public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
  // 方法返回值上标注有@ModelAttribute注解(或者非简单类型)  默认都会放进Model内哦~~
  @Override
  public boolean supportsReturnType(MethodParameter returnType) {
    return (returnType.hasMethodAnnotation(ModelAttribute.class) ||
        (this.annotationNotRequired && !BeanUtils.isSimpleProperty(returnType.getParameterType())));
  }
  // 这个处理就非常非常的简单了,注意:null值是不放的哦~~~~
  // 注意:void的话  returnValue也是null
  @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);
    }
  }
}


它对方法返回值的处理非常简单,只要不是null(当然不能是void)就都会放进Model里面,供以使用


总结


本文介绍的是@ModelAttribute的核心原理,他对我们实际使用有重要的理论支撑。下面系列文章主要在原理的基础上,展示各种各样场景下的使用Demo,敬请关注~



相关文章
|
8天前
|
XML Java 开发者
Spring Boot开箱即用可插拔实现过程演练与原理剖析
【11月更文挑战第20天】Spring Boot是一个基于Spring框架的项目,其设计目的是简化Spring应用的初始搭建以及开发过程。Spring Boot通过提供约定优于配置的理念,减少了大量的XML配置和手动设置,使得开发者能够更专注于业务逻辑的实现。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,为开发者提供一个全面的理解。
19 0
|
1月前
|
Java Spring 容器
Spring底层原理大致脉络
Spring底层原理大致脉络
|
1月前
|
JSON 前端开发 Java
SSM:SpringMVC
本文介绍了SpringMVC的依赖配置、请求参数处理、注解开发、JSON处理、拦截器、文件上传下载以及相关注意事项。首先,需要在`pom.xml`中添加必要的依赖,包括Servlet、JSTL、Spring Web MVC等。接着,在`web.xml`中配置DispatcherServlet,并设置Spring MVC的相关配置,如组件扫描、默认Servlet处理器等。然后,通过`@RequestMapping`等注解处理请求参数,使用`@ResponseBody`返回JSON数据。此外,还介绍了如何创建和配置拦截器、文件上传下载的功能,并强调了JSP文件的放置位置,避免404错误。
|
1月前
|
Java Spring 容器
Spring IOC、AOP与事务管理底层原理及源码解析
【10月更文挑战第1天】Spring框架以其强大的控制反转(IOC)和面向切面编程(AOP)功能,成为Java企业级开发中的首选框架。本文将深入探讨Spring IOC和AOP的底层原理,并通过源码解析来揭示其实现机制。同时,我们还将探讨Spring事务管理的核心原理,并给出相应的源码示例。
128 9
|
1月前
|
前端开发 Java 应用服务中间件
【Spring】Spring MVC的项目准备和连接建立
【Spring】Spring MVC的项目准备和连接建立
57 2
|
1月前
|
XML 前端开发 Java
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
本文阐述了Spring、Spring Boot和Spring MVC的关系与区别,指出Spring是一个轻量级、一站式、模块化的应用程序开发框架,Spring MVC是Spring的一个子框架,专注于Web应用和网络接口开发,而Spring Boot则是对Spring的封装,用于简化Spring应用的开发。
123 0
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
|
2月前
|
XML 缓存 前端开发
springMVC02,restful风格,请求转发和重定向
文章介绍了RESTful风格的基本概念和特点,并展示了如何使用SpringMVC实现RESTful风格的请求处理。同时,文章还讨论了SpringMVC中的请求转发和重定向的实现方式,并通过具体代码示例进行了说明。
springMVC02,restful风格,请求转发和重定向
|
1月前
|
XML 前端开发 Java
拼多多1面:聊聊Spring MVC的工作原理!
本文详细剖析了Spring MVC的工作原理,涵盖其架构、工作流程及核心组件。Spring MVC采用MVC设计模式,通过DispatcherServlet、HandlerMapping、Controller和ViewResolver等组件高效处理Web请求。文章还探讨了DispatcherServlet的初始化和请求处理流程,以及HandlerMapping和Controller的角色。通过理解这些核心概念,开发者能更好地构建可维护、可扩展的Web应用。适合面试准备和技术深挖
43 0
|
1月前
|
负载均衡 Java API
Spring Cloud原理详解
Spring Cloud原理详解
69 0
|
1月前
|
负载均衡 Java 网络架构
Spring Cloud原理详解
介绍了Spring Cloud的原理和核心组件,包括服务注册与发现、配置管理、负载均衡、断路器、智能路由、分布式消息传递、分布式追踪和服务熔断等,旨在帮助开发人员快速构建和管理微服务架构中的分布式系统。
56 0
下一篇
无影云桌面