SpringMVC类型转换、数据绑定

摘要:
SpringMVC类型转换,数据绑定详细信息[带有源代码分析]目录前言属性编辑器介绍了重要的接口和类部分类和接口测试源代码分析编写自定义属性编辑器摘要参考前言SpringMVC是当前主流的WebMVC框架之一。生日将自动转换为Date对象(需要为Date转换配置属性编辑器)。我们通常使用属性编辑器将String对象转换为所需的Java对象。

SpringMVC类型转换、数据绑定详解[附带源码分析]

目录 前言

SpringMVC是目前主流的Web MVC框架之一。 

如果有同学对它不熟悉,那么请参考它的入门blog:http://www.cnblogs.com/fangjian0423/p/springMVC-introduction.html

public String method(Integer num, Date birth) {
  ...
}

Http请求传递的数据都是字符串String类型的,上面这个方法在Controller中定义,如果该方法对应的地址接收到到浏览器的请求的话,并且请求中含有num和birth参数,那么num会被自动转换成Integer对象;birth会被自动转为Date对象(Date转换需要配置属性编辑器)。

SpringMVC类型转换、数据绑定第1张

本文将分析这一原理,解释SpringMVC是如何实现数据类型的转换。

属性编辑器介绍

在讲解核心内容之前,我们先来了解一下Java中定义的属性编辑器。

sun设计属性编辑器主要是为IDE服务的,让IDE能够以可视化的方式设置JavaBean的属性。

PropertyEditor是属性编辑器的接口。

SpringMVC类型转换、数据绑定第2张

我们使用属性编辑器一般都是将String对象转换成我们需要的java对象而使用的。

有个方法setAsText很重要。 比如String对象"1"要使用属性编辑器转换成Integer对象,通过setAsText里Integer.parseInt(text)得到Integer对象,然后将Integer对象保存到属性中。

它的基本实现类是PropertyEditorSupport,一般我们要编写自定义的属性编辑器只需要继承这个类即可。

Spring中有很多自定义的属性编辑器,都在spring-beans jar包下的org.springframework.beans.propertyeditors包里。

SpringMVC类型转换、数据绑定第3张

CustomBooleanEditor继承PropertyEditorSupport并重写setAsText方法。

SpringMVC类型转换、数据绑定第4张

重要接口和类介绍

 刚刚分析了sun设计的属性编辑器。 下面我们来看下Spring对这方面的设计。

1.PropertyEditorRegistry接口

  封装方法来给JavaBean注册对应的属性编辑器。

SpringMVC类型转换、数据绑定第5张

2.PropertyEditorRegistrySupport:PropertyEditorRegistry接口的基础实现类

  SpringMVC类型转换、数据绑定第6张

  PropertyEditorRegistrySupport类有个createDefaultEditors方法,会创建默认的属性编辑器。

  SpringMVC类型转换、数据绑定第7张

  SpringMVC类型转换、数据绑定第8张

3.TypeConverter接口

  类型转换接口。 通过该接口,可以将value转换为requiredType类型的对象。

  SpringMVC类型转换、数据绑定第9张

4.TypeConverterSupport:TypeConverter基础实现类,并继承了PropertyEditorRegistrySupport  

  有个属性typeConverterDelegate,类型为TypeConverterDelegate,TypeConverterSupport将类型转换委托给typeConverterDelegate操作。

5.TypeConverterDelegate

  类型转换委托类。具体的类型转换操作由此类完成。

6.SimpleTypeConverter

  TypeConverterSupport的子类,使用了PropertyEditorRegistrySupport(父类TypeConverterSupport的父类PropertyEditorRegistrySupport)中定义的默认属性编辑器。

7.PropertyAccessor接口

  对类中属性操作的接口。

8.BeanWrapper接口

  继承ConfigurablePropertyAccessor(继承PropertyAccessor、PropertyEditorRegistry、TypeConverter接口)接口的操作Spring中JavaBean的核心接口。

9.BeanWrapperImpl类

  BeanWrapper接口的默认实现类,TypeConverterSupport是它的父类,可以进行类型转换,可以进行属性设置。

10.DataBinder类

  实现PropertyEditorRegistry、TypeConverter的类。支持类型转换,参数验证,数据绑定等功能。

  有个属性SimpleTypeConverter,用来进行类型转换操作。

11.WebDataBinder

  DataBinder的子类,主要是针对Web请求的数据绑定。

部分类和接口测试

由于BeanWrapper支持类型转换,属性设置。以BeanWrapper接口为例,做几个测试,让读者对它们有更清晰的认识:

以TestModel这个JavaBean为例,属性:

  private int age;
  private Date birth;
  private String name;
  private boolean good;
  private long times;

测试方法1:

TestModel tm = new TestModel();
BeanWrapper bw = new BeanWrapperImpl(tm);
bw.setPropertyValue("good", "on");
//bw.setPropertyValue("good", "1");
//bw.setPropertyValue("good", "true");
//bw.setPropertyValue("good", "yes");
System.out.println(tm);

good是boolean属性,使用BeanWrapperImpl设置属性的时候,内部会使用类型转换(父类TypeConverterSupport提供),将String类型转换为boolean,CustomBooleanEditor对于String值是on,1,true,yes都会转换为true,本文介绍PropertyEditorRegistrySupport的时候说明过,CustomBooleanEditor属于默认的属性编辑器。

测试方法2:

TestModel tm = new TestModel();
BeanWrapperImpl bw = new BeanWrapperImpl(false);
bw.setWrappedInstance(tm);
bw.setPropertyValue("good", "1");
System.out.println(tm);

不使用默认的属性编辑器进行类型转换。 很明显,这段代码报错了,没有找到合适的属性编辑,String类型不能作为boolean类型的值。

错误信息:Failed to convert property value of type 'java.lang.String' to required type 'boolean' for property 'good'; 

测试方法3:

TestModel tm = new TestModel();
BeanWrapper bw = new BeanWrapperImpl(tm);
bw.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), true));
bw.setPropertyValue("birth", "1990-01-01");
System.out.println(tm);

默认属性编辑器中并没有日期类型的属性编辑器,我们注册一个Spring提供的CustomDateEditor属性编辑器,对应Date对象,并且为空。有了CustomDateEditor,设置birth的时候会通过类型转换自动转化成Date对象。

关于其他属性的设置,读者自行测试吧。

源码分析

本文所使用的Spring版本是4.0.2

在分析RequestParamMethodArgumentResolver处理请求参数之前,我们简单回顾一下SpringMVC是如何对http请求进行处理的。

HandlerAdapter会对每个请求实例化一个ServletInvocableHandlerMethod对象进行处理,我们仅看下WebDataBinderFactory的构造过程。

WebDataBinderFactory接口是一个创建WebDataBinder的工厂接口。

SpringMVC类型转换、数据绑定第10张

SpringMVC类型转换、数据绑定第11张

SpringMVC类型转换、数据绑定第12张

以如下方法为例:

public ModelAndView test(boolean b, ModelAndView view) {
  view.setViewName("test/test");
  if(b) {
      view.addObject("attr", "b is true");
  } else {
      view.addObject("attr", "b is false");
  }
  return view;
}

楼主在另外一篇博客http://www.cnblogs.com/fangjian0423/p/springMVC-request-param-analysis.html中已经分析,boolean类型的参数会被RequestParamMethodArgumentResolver这个HandlerMethodArgumentResolver处理。

下面我们进入RequestParamMethodArgumentResolver看看是如何处理的。

RequestParamMethodArgumentResolver的resolveArgument方法是由它的父类AbstractNamedValueMethodArgumentResolver中定义的:

SpringMVC类型转换、数据绑定第13张

ServletRequestDataBinderFactory创建ExtendedServletRequestDataBinder。

SpringMVC类型转换、数据绑定第14张

ExtendedServletRequestDataBinder属于DataBinder的子类。

SpringMVC类型转换、数据绑定第15张

我们在介绍重要接口的时候说过DataBinder进行类型转换的时候内部会使用SimpleTypeConverter进行数据转换。

下面看看测试:

 SpringMVC类型转换、数据绑定第16张

SpringMVC类型转换、数据绑定第17张

SpringMVC类型转换、数据绑定第18张

SpringMVC类型转换、数据绑定第19张

CustomBooleanEditor处理ohmygod会抛出IllegalArgumentException。 最终被截获处理成http 400错误。

SpringMVC类型转换、数据绑定第20张

PS:以上例子boolean类型改成Boolean类型的话,不传参数的话b就是null,我们解释默认属性编辑器的时候Boolean类型的参数是允许空的。但是boolean类型不传参数的话,默认会是false,而不会抛出异常。 原因就是resolveArgument方法中handleNullValue处理null值,spring进行了特殊的处理,如果参数类型是boolean的话,取false。 读者可以试试。

再看看个例子:

public ModelAndView testObj(Employee e, ModelAndView view) {
  view.setViewName("test/test");
  view.addObject("attr", e.toString());
  return view;
}

该方法会被ServletModelAttributeMethodProcessorr这个HandlerMethodArgumentResolver处理。

ServletModelAttributeMethodProcessorr的resolveArgument方法是由它的父类ModelAttributeMethodProcessor中定义的:

SpringMVC类型转换、数据绑定第21张

 这里WebDataBinder方法bind中会使用BeanWrapper构造对象,然后设置对应的属性。BeanWrapper本文已介绍过。

SpringMVC类型转换、数据绑定第22张

编写自定义的属性编辑器

Spring提供的编辑器肯定不会满足我们日常开发的功能,因此开发自定义的属性编辑器也是很有必要的。

下面我们就写1个自定义的属性编辑器。

public class CustomDeptEditor extends PropertyEditorSupport {
  
  @Override
  public void setAsText(String text) throws IllegalArgumentException { 
    if(text.indexOf(",") > 0) {
        Dept dept = new Dept();
        String[] arr = text.split(",");
        dept.setId(Integer.parseInt(arr[0]));
        dept.setName(arr[1]);
        setValue(dept);
    } else {
        throw new IllegalArgumentException("dept param is error");
    }
  }
  
}

SpringMVC中使用自定义的属性编辑器有3种方法:

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

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

2. 实现WebBindingInitializer接口

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

之前分析源码的时候,HandlerAdapter构造WebDataBinderFactory的时候,会传递HandlerAdapter的属性webBindingInitializer。

因此,我们在配置文件中构造RequestMappingHandlerAdapter的时候传入参数webBindingInitializer。

3. @ControllerAdvice注解

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

加上ControllerAdvice别忘记配置文件component-scan需要扫描到这个类。

最终结果:

SpringMVC类型转换、数据绑定第23张

总结

分析了Spring的数据转换功能,并解释这个神奇的转换功能是如何实现的,之后编写了自定义的属性编辑器。

大致讲解了下Spring类型转换中重要的类及接口。

SpringMVC这个系列目前准备在写3篇,拦截器、HandlerAdapter和视图。希望本系列文章对读者有帮助。

文章难免会出现错误,希望读者能够指出。

参考资料

http://jinnianshilongnian.iteye.com/blog/1866350

http://jinnianshilongnian.iteye.com/blog/1723270

http://www.iteye.com/topic/1123628

 
 
分类: java
标签: javaspringspringmvc

免责声明:文章转载自《SpringMVC类型转换、数据绑定》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇利用dstat来监控Linux系统资源使用情况——超级【赞】Matlab中的静态文本框中显示多行内容下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

DGL学习(二): 使用DGL构造图

有许多方法可以构造DGLGraph。文档中建议使用的方法有四种,分别如下: ① 使用两个数组,分别存储源节点和目标节点对象 (数组类型可以是numpy 也可以是 tensor)。 ② scipy 中的稀疏矩阵(),表示要构造的图的邻接矩阵。 ③ networkx 的图对象(DGLGraph 和 networkx 可以互转)。 ④ 整数对形式的边列表。 下面...

sql记录查询重复注意事项(经验提升),in的用法和效率

sql查询重复记录,使用: select * from dimappnamenew as appn where id in (   select id   from dimappnamenew group by packagename,storename,app_name having count(*)>1 )  id是表的主键,三个属性映射后可以...

NAT回流(Twice NAT)Hairping 参数详解

内网用户需要通过域名访问内网的服务器,一般商用环境是无法访问的,需要经过以下配置,原理不说了,直接说配置。 nat (inside,inside) source dynamic inside-net  interface destination static outside-ip  web-ip service  outside-port web-real...

Python接口测试入门

接口测试基础篇用几个简单的案例,带你轻松了解接口测试 首先了解一下接口测试的重要性: 接口测试有什么要求呢? 首先需要清晰的接口文档,标准如下: 接口名称 接口类型 输入参数 每个参数名; 每个参数类型; 每个参数业务含义; 每个是否可空; 每个字段长度(可选,一般需要提供,有严格要求的字段需特别注明); 输出参数 状态码; 提示信息; 每个参...

ABP官方文档翻译 3.1 实体

实体 实体类 聚合根类领域事件 常规接口 审计 软删除 激活/失活实体 实体改变事件 IEntity接口   实体是DDD(领域驱动设计)的核心概念之一。Eric Evans描述它为"An object that is not fundamentally defined by its attributes, but rather by a t...

自动化运维工具puppet详解(一)

一、puppet 介绍   1、puppet是什么   puppet是一个IT基础设施自动化管理工具,它能够帮助系统管理员管理基础设施的整个生命周期: 供应(provisioning)、配置(configuration)、联动(orchestration)及报告(reporting)。  基于puppet ,可实现自动化重复任务、快速部署关键性应用以及在本...