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);这里我贴出参考值:
如下示例,它会正常打印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
,敬请关注~