Spring源码研究:数据绑定
在做Spring MVC时,我们只需用@Controllor来标记Controllor的bean,再用@RequestMapping("标记")来标记需要接受请求的方法,方法中第一个参数为HttpServletRequest类型,最后一个参数为Model类型,中间可以为任何POJO,只要符合标准,有set和get,Spring即可以根据网页请求中的参数名,自动绑定到POJO对象的属性名,这是相当方便的。其中的原理是什么呢?看下源代码就可以知道了。
首先,要知道Method对象的invoke(调用,借助)方法,看下这一段代码:
Class clazz = Class.forName("TaskProvidePropsList");//这里的类的全名
Object obj = clazz.newInstance();//获取类的实例
Field[] fields = clazz.getDeclaredFields();//获取属性列表
//写数据
for(Field f : fields) {
PropertyDescriptor pd = new PropertyDescriptor(f.getName(), clazz);//获取属性对象,要标明是哪个类
Method wM = pd.getWriteMethod();//获得写方法,即setter
obj = wM.invoke(obj, 2);//执行obj对象的wm方法,2为参数。因为知道是int类型的属性,所以传个int过去就是了。。实际情况中需要判断下他的参数类型,这里可以有返回值,为obj对象的引用
}
//读数据
for(Field f : fields) {
PropertyDescriptor pd = new PropertyDescriptor(f.getName(), clazz);//获取属性对象,要标明是哪个类
Method rM = pd.getReadMethod();//获得读方法,即getter
Integer num = (Integer) rM.invoke(obj);//执行obj对象的rm方法。因为知道是int类型的属性,所以转换成integer就是了。。也可以不转换直接打印
System.out.println(num);
}
用法还是比较简单的。
接下来就是Spring中是怎么调用这个方法为POJO赋值的。
最先要追溯到 org.springframework.web.servlet.FrameworkServlet类中的doGet(HttpServletRequest request, HttpServletResponse response)等do方法(do方法是servlet中的基本方法,相当于Spring接管了所有do方法,然后去分发方法。若不使用框架,则需要自己去写servlet继承HttpServlet实现所有do方法,也是可以使用的,但是服务器较大时这样就太复杂了,框架就是为了这个而存在的)
这个方法中调用processRequest(request, response),processRequest中调用doService(request, response),doService是所有do方法的核心,负责把该请求根据情况分发给不同的service(@RequestMapping注解标明的方法)。
doService在FrameworkServlet的子类DispatcherServlet(Dispatch:派遣,调度)中实现,doService中又调用doDispatch(request, response),把当前请求分配给合适的Service的合适方法。
doDispatch方法中调用(HandlerAdapter)ha.handle(processedRequest, response, mappedHandler.getHandler())方法去寻找与该请求挂钩的方法(processedRequest是由checkMultipart(request)转换而来的)(注释中写的Actually invoke the handler.)。
接着HandlerAdapter中的handle方法调用handleInternal(request, response, (HandlerMethod) handler)返回一个ModelAndView。
handleInternal方法中调用invokeHandleMethod(request, response, handlerMethod)方法返回一个ModelAndView。
然后invokeHandleMethod中调用ServletInvocableHandlerMethod类的invokeAndHandle(webRequest, mavContainer)方法。
该方法中第一句为:Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);这里终于到核心了,为请求调用绑定类。
webRequest为httpRequest包装后的类,添加了uri成员,标记请求uri以便方便分配。mavContainer为ModelAndViewContainer类对象,用于绑定模块(POJO)和视图(view即网页)。
在invokeForRequest方法中,第一句为Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);该方法返回与该uri请求匹配的Mapping方法的参数列表。
getMethodArgumentValues中第一句为MethodParameter[] parameters = getMethodParameters();获取方法列表,经过处理后,返回方法中所有参数类型的Bean的一个实例并赋值,为args数组。
关键点来的:上一步中有对实例赋值的过程,这个值是从哪里来的呢?没错就是从Request的参数列表中来的,绑定数据就是在getMethodArgumentValues方法中做的。具体是怎么做的呢?
private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception { MethodParameter[] parameters = getMethodParameters();//获取所有参数所属的类
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
GenericTypeResolver.resolveParameterType(parameter, getBean().getClass());
args[i] = resolveProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
if (this.argumentResolvers.supportsParameter(parameter)) {
try {
args[i] = this.argumentResolvers.resolveArgument(
parameter, mavContainer, request, this.dataBinderFactory);
continue;
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(getArgumentResolutionErrorMessage("Error resolving argument", i), ex);
}
throw ex;
}
}
if (args[i] == null) {
String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i);
throw new IllegalStateException(msg);
}
}
return args;
} private String getArgumentResolutionErrorMessage(String message, int index) {
MethodParameter param = getMethodParameters()[index];
message += " [" + index + "] [type=" + param.getParameterType().getName() + "]";
return getDetailedErrorMessage(message);
}
就是这一句:args[i] = resolveProvidedArgument(parameter, providedArgs);和args[i] = this.argumentResolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);(dataBinderFactory为数据绑定工厂方法)
主要起作用的是后一句,HandlerMethodArgumentResolverComposite.resolveArgument中调用org.springframework.web.method.support.HandlerMethodArgumentResolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
在resolveArgument(多态方法,Request和Response和Model和普通POJO都有重写方法)中,以POJO重写方法为例:
public final Object resolveArgument(
MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest request, WebDataBinderFactory binderFactory)
throws Exception { String name = ModelFactory.getNameForParameter(parameter);
Object attribute = (mavContainer.containsAttribute(name)) ?
mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, request); WebDataBinder binder = binderFactory.createBinder(request, attribute, name);
if (binder.getTarget() != null) {
bindRequestParameters(binder, request);
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors()) {
if (isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
} // Add resolved attribute and BindingResult at the end of the model Map<String, Object> bindingResultModel = binder.getBindingResult().getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel); return binder.getTarget();
}
通过ModelFactiry获取Model的类名,之后获取mavContainer中该属性名称的对象,若没有则调用createAttribute(name, parameter, binderFactory, request)创建,从String value = getRequestValueForAttribute(attributeName, request);Request中获取这个名称的值,如果没有则调用父类的createAttribute,父类的createAttribute返回一个Bean的实体类,所有属性为空。
之后binderFactory.createBinder(request, attribute, name);为实体类创建一个绑定器,绑定器包含实体类,实体类名和http请求。然后初始化绑定器,为绑定器赋Request。
接着调用bindRequestParameters(binder, request);开始为Bean绑定值。bindRequestParameters中获取servetBinder和servletRequest,之后servletBinder.bind(servletRequest);
bind方法中,MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);获取请求中所有参数。addBindValues(mpvs, request);doBind(mpvs);
doBind中checkFieldDefaults(mpvs);checkFieldMarkers(mpvs);再调用父类的doBind。checkAllowedFields(mpvs);checkRequiredFields(mpvs);检查参数
applyPropertyValues(mpvs);方法把所有属性绑定到bean实体类中。再调用getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());设置值。
public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)
throws BeansException { List<PropertyAccessException> propertyAccessExceptions = null;
List<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues ?
((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues()));
for (PropertyValue pv : propertyValues) {
try {
// This method may throw any BeansException, which won't be caught
// here, if there is a critical failure such as no matching field.
// We can attempt to deal only with less serious exceptions.
setPropertyValue(pv);
}
catch (NotWritablePropertyException ex) {
if (!ignoreUnknown) {
throw ex;
}
// Otherwise, just ignore it and continue...
}
catch (NullValueInNestedPathException ex) {
if (!ignoreInvalid) {
throw ex;
}
// Otherwise, just ignore it and continue...
}
catch (PropertyAccessException ex) {
if (propertyAccessExceptions == null) {
propertyAccessExceptions = new LinkedList<PropertyAccessException>();
}
propertyAccessExceptions.add(ex);
}
} // If we encountered individual exceptions, throw the composite exception.
if (propertyAccessExceptions != null) {
PropertyAccessException[] paeArray =
propertyAccessExceptions.toArray(new PropertyAccessException[propertyAccessExceptions.size()]);
throw new PropertyBatchUpdateException(paeArray);
}
}
List<PropertyValue> propertyValues存储所有PropertyValue,每个PropertyValue中包含属性名(键名)与值(从网页Request中传过来的,json方式最好),还有一个属性的map。之后调用setPropertyValue(pv);
其中nestedBw = getBeanWrapperForPropertyPath(propertyName);获取上面实体类。tokens = getPropertyNameTokens(getFinalPath(nestedBw, propertyName));使用propertyName存储所有属性(去除方括号和大括号),nestedBw.setPropertyValue(tokens, pv);为实体类设置值。最终有BeanWrapperImpl类的setPropertyValue方法,为Request中传过来的所有参数name值,与RequestMapping中函数传入参数Bean类型的成员变量名比较,如果有相同的,则调用相应的写方法setter给该变量设置值(其实还有更复杂的List,Array,Map类型绑定,都在BeanWrapperImpl类中实现,可以详细看看)。
总的来说:先遍历所有Mapping中参数的Bean对象,再遍历Request中所有参数和值对,接着再遍历Bean对象中所有方法,找到Bean中有set方法的和Request中参数名相同的成员变量,调用set方法设置该成员变量值。
Spring源码研究:数据绑定的更多相关文章
- Spring源码研究--下载-编译-导入eclipse-验证
一,环境配置 操作系统:Unbutu14.04LTS JDK: 1.8.0_40 git: 1.9.1 gradle: 2.2.1 二,源码下载-编译-导入eclipse-验证 1,下载 使用git直 ...
- spring源码研究1 如何导入源码
环境 jdk8 windows8 1.下载源码 https://github.com/spring-projects/spring-framework 2.编译为eclipse项目 源码下载无法直接导 ...
- spring源码研究2 自定义标签实现及使用
1.自定义标签实现及使用参考: http://blog.csdn.net/fighterandknight/article/details/50112701 1)创建一个需要扩展的组件 User.ja ...
- spring源码研究之IoC容器在web容器中初始化过程
转载自 http://ljbal.iteye.com/blog/497314 前段时间在公司做了一个项目,项目用了spring框架实现,WEB容器是Tomct 5,虽然说把项目做完了,但是一直对spr ...
- Spring源码阅读笔记
前言 作为一个Java开发者,工作了几年后,越发觉力有点不从心了,技术的世界实在是太过于辽阔了,接触的东西越多,越感到前所未有的恐慌. 每天捣鼓这个捣鼓那个,结果回过头来,才发现这个也不通,那个也不精 ...
- spring源码学习之路---深入AOP(终)
作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 上一章和各位一起看了一下sp ...
- spring源码学习之路---AOP初探(六)
作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 最近工作很忙,但当初打算学习 ...
- spring源码学习之路---IOC初探(二)
作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 上一章当中我没有提及具体的搭 ...
- spring源码学习之路---环境搭建(一)
作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 最近已经开始了spring源 ...
随机推荐
- git 基本操作 https://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000/0013744142037508cf42e51debf49668810645e02887691000
1.创建版本库 (即仓库 repository)简单理解为一个目录,这个目录里的所有文件都可以被git管理起来,每个文件的修改删除,git都能跟踪,一边任何时刻都可以追踪历史,或者在将来某个时刻可以 ...
- selenium新的定位方法,更简洁很方便
亲测是可以的 self.driver.find_element('id','kw').send_keys(u"凯宾斯基")
- java代码反转toCharAT()的用法
总结:反转注意for循环里面的变化 package clientFrame; //字符串反转 public class we { public static void main(String[] ar ...
- 分布式缓存系统 Memcached 哈希表操作
memcached 中有两张hash 表,一个是“主hash 表”(primary_hashtable),另外一个是“原hash 表”(old_hashtable).一般情况下都在主表中接受操作,在插 ...
- 1126 Eulerian Path
题意:若图是连通图,且所有结点的度均为偶数,则称为Eulerian:若有且仅有两个结点的度为奇数,则称为semi-Eulerian.现给出一个图,要我们判断其是否为Eulerian,semi-Eule ...
- PHP大小写:函数名和类名不区分,变量名区分
PHP对大小写敏感问题的处理比较乱,写代码时可能偶尔出问题,所以这里总结一下. 但我不是鼓励大家去用这些规则.推荐大家始终坚持“大小写敏感”,遵循统一的代码规范. 1. 变量名区分大小写 <?p ...
- python's twenty-third day for me 面向对象进阶
普通方法:对象和类绑定的过程. class A: def func1(self):pass def func2(self):pass def func3(self):pass def func4(se ...
- 8.solr学习速成之FacetPivot
什么是Facet.pivot Facet.pivot就是按照多个维度进行分组查询,是Facet的加强,在实际运用中经常用到,一个典型的例子就是商品目录树 NamedList解释: NamedList ...
- Flask之单元测试
5.2单元测试 为什么要测试? Web程序开发过程一般包括以下几个阶段:[需求分析,设计阶段,实现阶段,测试阶段].其中测试阶段通过人工或自动来运行测试某个系统的功能.目的是检验其是否满足需求,并得出 ...
- Redhat 无线(Wifi)上网命令行配置
小结两种命令行模式下配置无线wife的方法,实践测试通过(Red Hat Enterprise Linux release 6.0 Beta(Santiago)) 一.使用wpa_supplicant ...