Spring5源码解析-@ModelAttribute
之前我们讨论了Spring中如何通过验证器来达到校验目的。其中有几行我们提到了@ModelAttribute注解。但是,单单理解这个概念还不够,总感觉飘如浮萍。
本文将对@ModelAttribute
进行解析。将分为两部分。首先将介绍此注解的用法。第二部分将通过具体的代码来分析这个注解和其相应的解析器的细节。
什么是@ModelAttribute注解?
@ModelAttribute
注解主要用来将请求转换为使用此注解指定的对象。例如,如果在@ModelAttribute
旁边指定了一个Article
实例,则与Article
的字段对应的所有请求参数将被用作Article
的字段值。什么意思呢,例如,POST提交
后参数title
的值将被设置为Article
的title
字段。这里推荐一篇文章解释的很清晰:http://blog.csdn.net/hejingyuan6/article/details/49995987
因此,此注解允许开发人员通过请求来持久化一个对象。没有它,Spring认为必须创建一个新对象。另外,它直接显示一个对象模型来查看。你不需要在方法中再调用model.setAttribute()。在视图部分,可以通过注解中的指定值查找指定对象(例如,@ModelAttribute(“articleView”)可以在jsp中通过${articleView}
获取相应的值)或对象的类名称(例如@ModelAttribute()Article article
将在视图层获取方式就是${article}
)。
@ModelAttribute注解相关代码详解
还是分两波来说吧,也参考了不少其他解析的文章,看了很多相关评论,大都觉得各种迷迷糊糊所以就旧版新版都说说咯,反正都是源码学习,4.3版本之前和之后(4.2之后机制已经改了,下面讲新版本的时候会看到源码相关注释),4.3版本之后被废弃掉了,先谈老版本的。
老版本
总体来看,有三个关键类协助@ModelAttribute
来转换得到我们所需要的预期对象。第一个是org.springframework.web.bind.annotation.support.HandlerMethodResolver。它包含一个Set类型的私有字段,称为modelAttributeMethods
。此字段包含被@ModelAttribute
注解了的方法。在init()方法中,解析器将所有相关方法放在此集合中。
1 | private final Set<Method> modelAttributeMethods = new LinkedHashSet<Method>(); |
之后,org.springframework.web.bind.annotation.support.HandlerMethodInvoker就可以开始干活了。在其方法invokeHandlerMethod()
中,它从modelAttributeMethods Set
遍历所有方法。如果之前model属性没有解析,它将通过创建对象来将请求参数绑定到对象的相应字段。
1 | for (Method attributeMethod : this.methodResolver.getModelAttributeMethods()) { |
通过org.springframework.web.method.annotation.ModelAttributeMethodProcessor来做绑定。更确切地说,它是通过方法protected void bindRequestParameters(WebDataBinder binder,NativeWebRequest request)来将请求绑定到目标对象。而更准确地说,它使用WebRequestDataBinder
的bind()
方法来做到这一点。
1 | /** |
WebRequestDataBinder
的bind()
1 | /** |
跟着源码 再追下去的话,会发现在其父类DataBinder
中:
1 | /** |
DataBinder
的applyPropertyValues
方法中来对字段值进行设置:
1 | protected void applyPropertyValues(MutablePropertyValues mpvs) { |
首先,它得到一个org.springframework.beans.AbstractPropertyAccessor类(getPropertyAccessor
)的实现。之后,通过具体实现这个抽象方法public void setPropertyValue(String propertyName,Object value)将HTTP
请求中找到的值放入解析对象中。此方法由org.springframework.beans包中的BeanWrapperImpl
和DirectFieldAccessor
类实现。默认情况下,ModelAttributeMethodProcessor
使用的类是org.springframework.beans.BeanWrapperImpl,这是BeanWrapper
的默认实现。此默认实现可以设置和获取bean
的属性(类字段)。它以这种方式实现一个setPropertyValue
方法:
1 | public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyAccessor { |
1 | /** |
结果被转移到private void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv),这里就不详细介绍了。你只需要知道它是一个设置一个字段的值的方法。set字段可以是一个简单的类型(String,int等),也可以是一个集合(List,Map)。
以上介绍了在老版本中关于@ModelAttribute如何在Spring Web应用程序解析的。如上所看到的,代码执行的基本流程以HandlerMethodResolver
对象开头,并以ModelAttributeMethodProcessor
实例解析的可选对象结束。整个过程基于数据绑定,在DataBinder子类中实现。他们通过属性访问器(默认BeanWrapperImpl)从请求中获取键值对并将其放在目标对象中。
新版本
通过上面可以看出,老版本的代码其实穿梭的蛮复杂的,这里就通过新版的代码再来梳理下:
@ModelAttribute
注解的方法是作用于整个Controller
的,实际上在执行Controller
的每个请求时都会执行@ModelAttribute
注解的方法。
执行过程在org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
中查看,每次执行Controller时都会执行@ModelAttribute
注解的方法:
1 | /** |
modelFactory.initModel(webRequest, mavContainer, invocableMethod)
中会执行@ModelAttribute
注解的方法(org.springframework.web.method.annotation.ModelFactory中可查看):
1 | /** |
在private void invokeModelAttributeMethods(NativeWebRequest request, ModelAndViewContainer container)
中会判断方法上是否被@ModelAttribute
注解,如果是则会执行这个方法,并将返回值放到container
中:
1 | /** |
我们进入org.springframework.web.method.support.InvocableHandlerMethod 的invokeForRequest
方法,在给定request
请求的上下文中解析其参数值后调用该方法,参数值通常通过 HandlerMethodArgumentResolver
来解析。
1 | /** |
org.springframework.web.method.support.HandlerMethodArgumentResolverComposite
1 | /** |
回到org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,可以看到:
1 | public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter |
又回到老版本的resolveArgument
这里了,就不往下解释了
关于@ModelAttribute
的例子请看这篇博客文章的,自己就不整例子了http://blog.csdn.net/hejingyuan6/article/details/49995987
总之,通过源码可以看出,当@ModelAttribute
注解方法时,这个方法在每次访问Controller
时都会被执行,其执行到的原理就是在每次执行Controller
时都会判断一次,并执行@ModelAttribute
的方法,将执行后的结果值放到container
中,其实我们只需要知道这么多就成了,背后的机制无论新老版本都是解析绑定这4个字。