我们通过Http请求提交的参数都以字符串的形式呈现,但最终在springMVC的方法入参中,我们却能得到各种类型的数据,包括Number、Boolean、复杂对象类型、集合类型、Map类型等,这些都是springMVC内置的数据类型转换器帮我们完成的。springMVC的将请求数据绑定到方法入参的流程如下所示:

Created with Raphaël 2.1.0数据绑定流程图解ServletRequestServletRequestDataBinderDataBinderConversionServiceConversionServiceValidatorValidatorBindingResultBindingResult请求数据提交数据类型转换格式化数据合法性验证生成数据绑定结果

在本文里,我们通过属性编辑器来理解springMVC的数据转换、绑定过程。

PropertyEditorRegistrySupport

而对于常见的数据类型,Spring在PropertyEditorRegistrySupport中提供了默认的属性编辑器,这些常见的数据类型如下图所示:

在PropertyEditorRegistrySupport中,有两个重要的Map类型成员变量:
1. private Map<Class<?>, PropertyEditor> defaultEditors:用于保存默认属性类型的编辑器,元素的key为属性类型,值为对应属性编辑器的实例
2. private Map<Class<?>, PropertyEditor> customEditors:用于保存用户自定义的属性编辑器,元素的键值和defaultEditors一致。

在PropertyEditorRegistrySupport中,有一个重要的成员方法:createDefaultEditors()来创建默认的属性编辑器,它的定义如下所示:

/*** Actually register the default editors for this registry instance.*/
private void createDefaultEditors() {//创建一个HashMap存储默认的属性编辑器this.defaultEditors = new HashMap<Class<?>, PropertyEditor>(64);// 简单的属性编辑器,没有参数化功能,在JDK中没有包含下列任意目标类型的编辑器//这里和我们上表的资源类相对应this.defaultEditors.put(Charset.class, new CharsetEditor());this.defaultEditors.put(Class.class, new ClassEditor());this.defaultEditors.put(Class[].class, new ClassArrayEditor());this.defaultEditors.put(Currency.class, new CurrencyEditor());this.defaultEditors.put(File.class, new FileEditor());this.defaultEditors.put(InputStream.class, new InputStreamEditor());this.defaultEditors.put(InputSource.class, new InputSourceEditor());this.defaultEditors.put(Locale.class, new LocaleEditor());this.defaultEditors.put(Pattern.class, new PatternEditor());this.defaultEditors.put(Properties.class, new PropertiesEditor());this.defaultEditors.put(Resource[].class, new ResourceArrayPropertyEditor());this.defaultEditors.put(TimeZone.class, new TimeZoneEditor());this.defaultEditors.put(URI.class, new URIEditor());this.defaultEditors.put(URL.class, new URLEditor());this.defaultEditors.put(UUID.class, new UUIDEditor());if (zoneIdClass != null) {this.defaultEditors.put(zoneIdClass, new ZoneIdEditor());}// 默认的集合类编辑器实例,这里和我们上表的集合类相对应// 我们能够通过注册自定义的相同类型属性编辑器来重写下面的默认属性编辑器this.defaultEditors.put(Collection.class, new CustomCollectionEditor(Collection.class));this.defaultEditors.put(Set.class, new CustomCollectionEditor(Set.class));this.defaultEditors.put(SortedSet.class, new CustomCollectionEditor(SortedSet.class));this.defaultEditors.put(List.class, new CustomCollectionEditor(List.class));this.defaultEditors.put(SortedMap.class, new CustomMapEditor(SortedMap.class));// 基本数据的数组类型的默认编辑器this.defaultEditors.put(byte[].class, new ByteArrayPropertyEditor());this.defaultEditors.put(char[].class, new CharArrayPropertyEditor());this.defaultEditors.put(char.class, new CharacterEditor(false));this.defaultEditors.put(Character.class, new CharacterEditor(true));this.defaultEditors.put(boolean.class, new CustomBooleanEditor(false));this.defaultEditors.put(Boolean.class, new CustomBooleanEditor(true));// JDK中没有Number包装类的相关属性编辑器// 通过自定义我们的CustomNumberEditor来重写JDK默认的属性编辑器this.defaultEditors.put(byte.class, new CustomNumberEditor(Byte.class, false));this.defaultEditors.put(Byte.class, new CustomNumberEditor(Byte.class, true));this.defaultEditors.put(short.class, new CustomNumberEditor(Short.class, false));this.defaultEditors.put(Short.class, new CustomNumberEditor(Short.class, true));this.defaultEditors.put(int.class, new CustomNumberEditor(Integer.class, false));this.defaultEditors.put(Integer.class, new CustomNumberEditor(Integer.class, true));this.defaultEditors.put(long.class, new CustomNumberEditor(Long.class, false));this.defaultEditors.put(Long.class, new CustomNumberEditor(Long.class, true));this.defaultEditors.put(float.class, new CustomNumberEditor(Float.class, false));this.defaultEditors.put(Float.class, new CustomNumberEditor(Float.class, true));this.defaultEditors.put(double.class, new CustomNumberEditor(Double.class, false));this.defaultEditors.put(Double.class, new CustomNumberEditor(Double.class, true));this.defaultEditors.put(BigDecimal.class, new CustomNumberEditor(BigDecimal.class, true));this.defaultEditors.put(BigInteger.class, new CustomNumberEditor(BigInteger.class, true));// 只有我们显式将configValueEditorsActive设为true,才会注册下面类型的编辑器if (this.configValueEditorsActive) {StringArrayPropertyEditor sae = new StringArrayPropertyEditor();this.defaultEditors.put(String[].class, sae);this.defaultEditors.put(short[].class, sae);this.defaultEditors.put(int[].class, sae);this.defaultEditors.put(long[].class, sae);}
}

PropertyEditor

PropertyEditor是Java原生的属性编辑器接口,它的核心功能是将一个字符串转换为一个java对象。
它的定义和常用方法如下所示:

public interface PropertyEditor {//设置属性的值,基本属性类型要以包装类传入void setValue(Object value);//返回属性的值,基本数据类型会被封装成相应的包装类Object getValue();//为属性提供一个表示初始值的字符串,属性编辑器以此值作为属性的默认值String getJavaInitializationString();//将属性对象用一个字符串表示,一遍外部的属性编辑器能以可视化的方式显示。//默认返回null,表示改属性不能以字符串形式表示String getAsText();//利用所给字符串text更新属性内部的值void setAsText(String text) throws java.lang.IllegalArgumentException;}

实例解析自定义属性编辑器

1. 自定义编辑器类

它的一个核心实现类是PropertyEditorSupport,如果我们要编写自定义的属性编辑器,只需要继承这个类,然后重写setAsText方法即可。下面我们来看一个自定义属性编辑器的实例:尝试将字符串“myName,1995-01-01,15k”转换为Person POJO对象,Person对象的定义如下:

package com.mvc.model;import java.util.Date;import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.NumberFormat;public class Person {private String name;private Date birthday;private Long salary;//ignore getter and setter @Overridepublic String toString() {return "Person [name=" + name + ", birthday=" + birthday + ", salary="+ salary + "]";}}

下面是我们自定义的属性编辑器:

public class MyEditor extends PropertyEditorSupport {@Overridepublic void setAsText(String text) throws IllegalArgumentException {String[] values = text.split(",");Person person = new Person();person.setName(values[0]);try {person.setBirthday(new SimpleDateFormat("yyyy-MM-dd").parse(values[1]));//格式化字符串并解析成日期类型} catch (ParseException e) {e.printStackTrace();}person.setSalary(Long.valueOf(values[2].replace("k", "000")));//转换为工资格式setValue(person);//调用setValue来将我们的Person对象设置为编辑器的属性值super.setAsText(text);}
}

2. 注册编辑器

自定义完属性编辑器后,我们需要将其注册才能生效,SpringMVC中使用自定义的属性编辑器有3种方法:

1. Controller方法中添加@InitBinder注解的方法

实例:

@InitBinder
public void initBinder(WebDataBinder binder) { binder.registerCustomEditor(Person.class, new MyEditor());
}

2. 实现 WebBindingInitializer接口

方法1是针对特定的控制器的,如果我们需要对全局控制器生效,可以编写自己的WebBindingInitializer,然后在spring容器中注册,如下所示:

public class MyWebBindingInitializer implements WebBindingInitializer {@Overridepublic void initBinder(WebDataBinder binder, WebRequest request) { binder.registerCustomEditor(Dept.class, new CustomDeptEditor());  }
}

在容器中注册:

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"><property name="webBindingInitializer"><bean class="com.mvc.editor.MyWebBindingInitializer" /></property>
</bean>

3. @ControllerAdvice注解

我们可以通过此注解配置一个控制器增强,

@ControllerAdvice
public class InitBinderControllerAdvice {@InitBinderpublic void initBinder(WebDataBinder binder) { binder.registerCustomEditor(Dept.class, new CustomDeptEditor());  }}

我们需要将其纳入<context:component-scan>的扫描路径中才能生效。

从上面的分析我们能看到,springMVC注册了大量的数据类型编辑器,恰是通过这些属性编辑器,springMVC帮助我们完成了请求参数字符串到入参数据的绑定。在一篇文章里,我们会谈到SpringMVC对新的转换器框架的支持。

springMVC4(9)属性编辑器剖析入参类型转换原理相关推荐

  1. SpringMVC自动将请求参数和入参对象的属性进行一一绑定;要求请求参数的名字和javaBean入参的对象里面的属性名是一样的||员工的增删改查案例

    SpringMVC自动将请求参数和入参对象的属性进行一一绑定:要求请求参数的名字和javaBean入参的对象里面的属性名是一样的 1.SpringMVC中配置HiddenHttpMethodFilte ...

  2. @Validated和@Valid区别:Spring validation验证框架对入参实体进行嵌套验证必须在相应属性(字段)加上@Valid而不是@Validated...

    Spring Validation验证框架对参数的验证机制提供了@Validated(Spring's JSR-303规范,是标准JSR-303的一个变种),javax提供了@Valid(标准JSR- ...

  3. java 数据抓取 动态获得cookies里变动的属性_@CookieValue获取Cookie信息,使用Servlet API作为入参,处理模型数据...

    @RequestMapping("/testCookieValue") public String testCookieValue(@CookieValue(value=" ...

  4. 【Java基础】属性编辑器PropertyEditor

    在Spring配置文件里,我们往往通过字面值为Bean各种类型的属性提供设置值:不管是double类型还是int类型,在配置文件中都对应字符串类型的字面值.BeanWrapper填充Bean属性时如何 ...

  5. Spring - Java/J2EE Application Framework 应用框架 第 4 章 属性编辑器,数据绑定,校验与BeanWeapper(Bean封装)

    第 4 章 属性编辑器,数据绑定,校验与BeanWeapper(Bean封装) 4.1. 简介 是否需要对业务逻辑进行验证是一个常见的问题. 有关这一点存在两种截然想法的回答,Spring提出的验证模 ...

  6. java值参_Java陷阱之慎用入参做返回值详解

    正常情况下,在Java中入参是不建议用做返回值的.除了造成代码不易理解.语义不清等问题外,可能还埋下了陷阱等你入坑. 问题背景 比如有这么一段代码: @Named public class AServ ...

  7. java什么时候用有参_Java有陷阱——慎用入参做返回值

    正常情况下,在Java中入参是不建议用做返回值的.除了造成代码不易理解.语义不清等问题外,可能还埋下了陷阱等你入坑. 问题背景 比如有这么一段代码: 上面代码,服务A希望调用服务B,以获取supply ...

  8. 接口入参形式_极光小课堂|手把手教你做接口测试

    接口测试是项目测试过程中非常重要的一环,测试的对象是接口,所以提早介入测试,对代码逻辑进行全面验证,就会更早的发现程序的问题.同时,接口测试比UI测试效率更高,并且更容易验证极端和异常的情况. 那么什 ...

  9. Java开发中业务层入参校验详细解析

    2019独角兽企业重金招聘Python工程师标准>>> 背景 首先,我们达成以下共识: 一个服务方法,如果入参太多,且基本为非pojo,会给调用方造成不必要的干扰.尽管可以把文档写的 ...

最新文章

  1. 整数中内存中的保存方式:大端、小端
  2. 尴尬!因软件 Bug ,美国数百名囚犯释放后无法出狱
  3. Docker的4种网络模式
  4. 全排列函数next_permutation
  5. 二叉查找树BST----java实现
  6. Silverlight 5 beta新特性探索系列:9.视频快进快退和TextSearch对象对文字项查询
  7. 自动部署 管道 ci cd_自动化测试在CI CD管道中的作用
  8. 【C++深度剖析教程1】C++中的经典问题解析-c++中的对象的构造顺序与析构顺序
  9. Rancher创始人谈Docker,创新愈发困难,未来将何去何从?
  10. 你可能不太熟知的布局技巧
  11. 如何在maven工程中加载oracle驱动
  12. 《推荐系统笔记(十一)》Learning to rank(LTR排序问题)的介绍和RankNet算法(内含详细推导)
  13. 廖雪峰Python教程学习笔记
  14. IT十八掌徐培成第三天笔记
  15. excel导出 服务器运行失败,SolidWorks 插入自制EXCEL明细表 启动服务器应用程序失败:启动excle服务器失败...
  16. 目标文件(.obj)的COFF文件结构
  17. 逃离x86架构-----CPU体系结构CISC与RISC之争
  18. (69)zabbix监控惠普打印机
  19. win10系统访问局域网服务器,Win10系统不能访问局域网共享磁盘的解决方法
  20. 司法制度类毕业论文文献包含哪些?

热门文章

  1. ArrayList 为什么要实现 RandomAccess 接口?
  2. 惊:FastThreadLocal吞吐量居然是ThreadLocal的3倍!!!
  3. 东半球最接地气的短链接系统设计
  4. 这样规范写代码,同事直呼“666”
  5. Spring Cloud第五章:服务网关Zuul
  6. 送一款巧克力式绝美键盘!真香!
  7. 伯克利在《我的世界》举办虚拟毕业典礼,致辞、抛礼帽一样不少!
  8. Spring基础专题——第五章(Aop代理)
  9. 美多商城之支付(支付宝介绍)
  10. SPU表管理之保存SPU表数据