string转换成date类型_你竟然知道SpringMVC是如何完成类型转换和数据绑定的吗?...

8f3f5f68761c8eda590d98d933f2bb43.png# 前言

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

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

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

183c1d85da6e20d3bac64e6799b7652f.png

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

# 属性编辑器介绍

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

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

PropertyEditor是属性编辑器的接口。

53970e7864a22a376c3fd5f3cd22fcfb.png

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

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

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

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

0b57aad1edebd003dccd09ff46bf7516.png

CustomBooleanEditor继承PropertyEditorSupport并重写setAsText方法。

ecb8c5229e041ee52dc55cfbf2587d13.png

# 重要接口和类介绍

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

1.PropertyEditorRegistry接口

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

5b98d5847b431693416e3271a5e8a28b.png

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

  150d071440f513445a5c449f53ed12e6.png

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

  87fa94c1329e133cdd28d469d66aee2c.png

  434caf4e4cf55b17470b331cb1c966d0.png

3.TypeConverter接口

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

  fd55c8f0a6fa3d425b2ffbf2a5243b2e.png

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的工厂接口。

c4fb3cc9504e6699eacd3320c4028768.png

4d36f0b51b4bf6f700bd44b00a0c9e82.png

515b8e3ebe93160fc7eb649697cabf94.png

以如下方法为例:

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;}

boolean类型的参数会被RequestParamMethodArgumentResolver这个HandlerMethodArgumentResolver处理。

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

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

865543e1c508b0b669b9546ada6d41c6.png

ServletRequestDataBinderFactory创建ExtendedServletRequestDataBinder。

6eb1bfa71fdf4879b9f0e6c1d625b383.png

ExtendedServletRequestDataBinder属于DataBinder的子类。

ed2b422b37b2a845d67c6939392d3bc5.png

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

下面看看测试:

 1e620917d607cbec296ed4fc1712a3e6.png

4e54479f0cfbf7309dc31a2fc89a1d37.png

b45b25614dfbebb19f3c6bb6166747f4.png

3ca1695d95e1461ab7273bf0f0c60818.png

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

777902f7b494c1f6cca5e758d654e6df.png

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中定义的:

0b6455ed01432ae9b7857e32fb12f15c.png

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

6eed37112c76d5562f223a0c5e4595e4.png

# 编写自定义的属性编辑器

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注解的方法

@InitBinderpublic 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注解

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

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

最终结果:

ff3a897ddbbea50477671417fd8d8fc4.png

# 总结

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

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

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

# 参考资料

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

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

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

作者:Format 来源:https://www.cnblogs.com/fangjian0423/p/springMVC-databind-typeconvert.html

 往期推荐 

?

  • Maven的高级玩法,你用过吗?

  • 为什么Spring MVC能自动转换json/xml,你研究过它背后的原理吗?

  • 王者峡谷"一呼百应",弹幕“666”,背后都离不开长连接,如何实现千万级高性能的长连接网关?

d32faa575a2d4f2a9b41a8194035b450.png

48e84d44ca10c6d87f35bcda8f40efb1.gif 

点击

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值