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

我们通过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 

    @Override
    public String toString() {
        return "Person [name=" + name + ", birthday=" + birthday + ", salary="
                + salary + "]";
    }

}

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

public class MyEditor extends PropertyEditorSupport {
    @Override
    public 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 {

  @Override
  public 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 {

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

}

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

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

时间: 2024-09-14 02:45:49

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

springMVC4(12)复杂对象和集合类型入参绑定

1. 复杂对象参数绑定 对于普通的对象参数绑定,我们只需要对象成员变量名与请求参数名一一对应即可完成绑定. 而求对于组合对象,我们可以使用级联的方式来绑定方法参数.见下面实例: 我们先定义两个POJO类:User,Article其中Atricle是User的成员属性: public class Article { private Integer id; private String title; private String content; //忽略get和set方法 } package co

hibernate-validator实现入参校验(包含get与post)

    最近在实现一个功能时,由于入参特别多,有的入参需要不为空,有的入参可以为空,这中间如果手动一个一个判断,重复代码太多,所以就想到了用hibernate-validator来实现.中间诸多波折,现分享如下.     首先,引入maven依赖,具体的版本可以调整. <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId>

Mybatis调用PostgreSQL存储过程实现数组入参传递_PostgreSQL

前言 项目中用到了Mybatis调用PostgreSQL存储过程(自定义函数)相关操作,由于PostgreSQL自带数组类型,所以有一个自定义函数的入参就是一个int数组,形如: 复制代码 代码如下: CREATE OR REPLACE FUNCTION "public"."func_arr_update"(ids _int4)... 如上所示,参数是一个int数组,Mybatis提供了对调用存储过程的支持,那么PostgreSQL独有的数组类型作为存储过程的参数又

WinForm控件开发总结(九) 为属性提下拉式属性编辑器

在上一篇文章,我介绍了如何编写模态对话框属性编辑器,这篇文章我将介绍如何编写下拉式属性编 辑器.下拉式(DropDown)属性编辑器和模态对话框属性编辑器的不同之处就是,当你点击属性值修改的 时候,模态对话框编辑器是弹出一个模态对话框,而下拉式属性编辑器却是在紧贴着属性值的地方显示一 个下拉的控件.不知道大家注意到了没有,这里我说的是显示一个下拉的控件,而这个控件也是需要你去 开发的,接下来我还是以Scope属性为例,介绍一下具体的实现. 首先我们要创建一个用于编辑属性的控件,在本系列文章的开始

shift妙用之解决shell编程中的入参问题

shell编程经常会遇到参数个数不定的这种情况,这种情况怎么处理呢?shift就要闪亮登场了   我说过了,shell是我的常规武器,目前虽然还不纯熟,但是我爱shell这门语言,在Linux下面混,总要写脚本.程序员是有基因,对编程语言是有 偏好的,你让我写C代码,我会觉得很爽,会有困难,会有痛苦的摸索和学习,但是,我愿意:学习shell/python,我也很乐意,甚至Lisp这种冷 门的语言我也充满了好奇,虽然现在Go和Erlang我一点也不懂,但是我按耐不住对这两种语言的兴趣,只要我抽出手

请问 java 接口有什么好处?如果只是说在函数调用时,入参更方便的话,为什么不考虑用泛型?

问题描述 请问 java 接口有什么好处?如果只是说在函数调用时,入参更方便的话,为什么不考虑用泛型? 请问 java 接口有什么好处?如果只是说在函数调用时,入参更方便的话,为什么不考虑用泛型? 解决方案 记住一点,继承表示 是什么,接口表示 能做什么,就好像一个点击动作的接口,它跟被点击对象没有任何关系,所以只是实现做什么 解决方案二: 接口入参方便??我真没感觉出来. 接口具体作用对不同人来说都不同的. 如果你是一个码农: 可能好处就是说更换实现类更容易了吧. 例如你现在项目用的是mysq

堆栈 日志 反射-java能否动态的在程序中获得出错的值(入参)

问题描述 java能否动态的在程序中获得出错的值(入参) 现在想对项目中的日志进行改造.方便出问题时的解决效率.(出问题时每次都要对错误进行复现.尤其是流程很长的时候.花了大量的时间和精力). 举个简单的例子. public class 人 { private String 身高; private String 体重; private int 年龄; public String get身高() { return 身高; } public void set身高(String 身高) { this.

关于调用webservice接口,出参入参用json格式

问题描述 关于调用webservice接口,出参入参用json格式 想写一个工具类,调用webservice接口,出参入参用json格式 解决方案 webservice 需要WSDL,里面包含了描述服务的xml的schema,http请求承载也是xml脚本.看你的意思是想用JSON替换XML?当然可以,但是后台如果是C++的业务处理,你需要建立json到c++的对象模型映射.

sringmvc-mybatis存储过程 根据入参返回不同数据集

问题描述 mybatis存储过程 根据入参返回不同数据集 我的存储过程根据参数,返回的游标每次都是不一样的,在mybatis xml配置文件里,resultMap只能设置,看到有人设置多个的,但是那样的话,是单次存储过程返回多个结果集,我这里要每次返回不同的结果集