3、验证,数据绑定和类型转换
验证,数据绑定和类型转换
考虑将验证作为业务逻辑是有利有弊,Spring 提供了一种验证(和数据绑定)设计,但并不排除其中任何一个。具体来说,验证不应与网络层绑定,并且应该易于本地化,并且应该可以插入任何可用的验证器。考虑到这些问题,Spring 提供了一个Validator
接口,该接口既基本又可以在应用程序的每一层使用。
数据绑定对于使用户 Importing 动态绑定到应用程序(或用于处理用户 Importing 的任何对象)域模型很有用。 Spring 提供了恰当地命名为DataBinder
的名称。 Validator接口
和DataBinder类
组成了org.springframework.validation程序包,该程序包主要用于但不限于 MVC 框架。
BeanWrapper接口(
org.springframework.beans)
是 Spring 框架中的一个基本概念,在很多地方都使用过。但是,您可能不需要直接使用BeanWrapper
。如果您将要使用它,则在尝试将数据绑定到对象时最有可能使用它。
Spring 的DataBinder
和较低级别的BeanWrapper
都使用PropertyEditorSupport
实现来解析和格式化属性值。 PropertyEditor接口
和PropertyEditorSuppor
t
类(java.beans)是 JavaBeans 规范的一部分。
Spring 3 引入了core.convert
软件包,该软件包提供了常规的类型转换工具,以及用于格式化 UI 字段值的高级“格式”软件包。可以将这些软件包用作PropertyEditorSupport
实现的更简单替代方案。
JSR-303/JSR-349 Bean 验证
从 4.0 版本开始,Spring 框架支持 Bean 验证 1.0(JSR-303)和 Bean 验证 1.1(JSR-349),以支持设置并使其适应 Spring 的Validator
接口。
应用程序可以选择一次全局启用 Bean 验证(如Spring Validation中所述),并将其专用于所有验证需求。
应用程序还可以为每个DataBinder
实例注册其他 Spring Validator
实例,如配置一个 DataBinder中所述。
3.1. 使用 Spring 的 Validator 接口进行验证
Spring 具有Validator
接口,可用于验证对象。 Validator
接口通过使用Errors
对象工作,因此验证器可以在验证时向Errors
对象报告验证失败。
考虑以下小型数据对象的示例:
public class Person { private String name; private int age; // the usual getters and setters... }
下一个示例通过实现org.springframework.validation.Validator
接口的以下两种方法来提供Person
类的验证行为:
-
supports(Class)
:此Validator
是否可以验证提供的Class
的实例。 -
validate(Object, org.springframework.validation.Errors)
:验证给定的对象,并在验证错误的情况下,将其注册到给定的Errors
对象。
实现Validator
非常简单,尤其是当您知道 Spring 框架还提供的ValidationUtils
helper 类时。以下示例为Person
实例实现Validator
:
public class PersonValidator implements Validator { /** * This Validator validates *only* Person instances */ public boolean supports(Class clazz) { return Person.class.equals(clazz); } public void validate(Object obj, Errors e) { ValidationUtils.rejectIfEmpty(e, "name", "name.empty"); Person p = (Person) obj; if (p.getAge() < 0) { e.rejectValue("age", "negativevalue"); } else if (p.getAge() > 110) { e.rejectValue("age", "too.darn.old"); } } }
ValidationUtils
类上的static
rejectIfEmpty(..)
方法用于拒绝name
属性(如果它是null
或空字符串)。
虽然可以实现单个Validator
类来验证丰富对象中的每个嵌套对象,但是最好将每个嵌套类的验证逻辑封装在自己的Validator
实现中。一个“丰富”对象的简单示例是一个Customer
,它由两个String
属性(名字和第二个名称)和一个复杂的Address
对象组成。 Address
对象可以独立于Customer
对象使用,因此已实现了不同的AddressValidator
。如果希望CustomerValidator
重用AddressValidator
类中包含的逻辑而不求助于复制和粘贴,则可以在CustomerValidator
中依赖注入或实例化AddressValidator
,如以下示例所示:
public class CustomerValidator implements Validator { private final Validator addressValidator; public CustomerValidator(Validator addressValidator) { if (addressValidator == null) { throw new IllegalArgumentException("The supplied [Validator] is " + "required and must not be null."); } if (!addressValidator.supports(Address.class)) { throw new IllegalArgumentException("The supplied [Validator] must " + "support the validation of [Address] instances."); } this.addressValidator = addressValidator; } /** * This Validator validates Customer instances, and any subclasses of Customer too */ public boolean supports(Class clazz) { return Customer.class.isAssignableFrom(clazz); } public void validate(Object target, Errors errors) { ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "field.required"); ValidationUtils.rejectIfEmptyOrWhitespace(errors, "surname", "field.required"); Customer customer = (Customer) target; try { errors.pushNestedPath("address"); ValidationUtils.invokeValidator(this.addressValidator, customer.getAddress(), errors); } finally { errors.popNestedPath(); } } }
验证错误会报告给传递给验证器的Errors
对象。对于 Spring Web MVC,可以使用<spring:bind/>
标签检查错误消息,但是也可以自己检查Errors
对象。
3.2. 将代码解析为错误消息
我们介绍了数据绑定和验证。本节介绍与验证错误相对应的输出消息。在preceding section所示的示例中,我们拒绝了name
和age
字段。如果要使用MessageSource
输出错误消息,可以使用拒绝字段时提供的错误代码(在这种情况下为“名称”和“年龄”)进行输出。当您从Errors
接口调用(直接或间接使用ValidationUtils
类)rejectValue
或其他reject
方法时,基础实现不仅会注册您传入的代码,还会注册许多其他错误代码。 MessageCodesResolver
确定Errors
接口寄存器的错误代码。默认情况下,使用DefaultMessageCodesResolver
,例如,它不仅使用您提供的代码注册一条消息,而且还注册包含您传递给 reject 方法的字段名称的消息。因此,如果您使用rejectValue("age", "too.darn.old")
拒绝字段,除了too.darn.old
代码,Spring 还会注册too.darn.old.age
和too.darn.old.age.int
(第一个包含字段名称,第二个包含字段类型)。这样做是为了方便开发人员在定位错误消息时提供帮助。
3.3. Bean 操作和 BeanWrapper
org.springframework.beans
软件包遵循 JavaBeans 标准。 JavaBean 是具有默认无参数构造函数的类,并且遵循命名约定,其中(例如)名为bingoMadness
的属性将具有 setter 方法setBingoMadness(..)
和 getter 方法getBingoMadness()
。
Bean 包中的一个非常重要的类是BeanWrapper
接口及其相应的实现(BeanWrapperImpl(
org.springframework.beans)
)。正如从 Javadoc 引用的那样,BeanWrapper
提供了以下功能:设置和获取属性值(单独或批量),获取属性 Descriptors 以及查询属性以确定它们是否可读或可写。此外,BeanWrapper
还支持嵌套属性,从而可以将子属性上的属性设置为无限深度。 BeanWrapper
还支持添加标准 JavaBean PropertyChangeListeners
和VetoableChangeListeners
的能力,而无需在目标类中支持代码。最后但并非最不重要的一点是,BeanWrapper
支持设置索引属性。 BeanWrapper
通常不直接由应用程序代码使用,而由DataBinder
和BeanFactory
使用。
BeanWrapper
的工作方式部分地由其名称表示:它包装一个 bean 以对该 bean 执行操作,例如设置和检索属性。
3.3.1. BeanWrapper设置和获取基本和嵌套属性
设置和获取属性的方法是使用setPropertyValue
,setPropertyValues
,getPropertyValue
和getPropertyValues
方法,这些方法带有一些重载的变体。
表 11.属性示例
Expression | Explanation |
---|---|
name |
表示与name 或isName() 和setName(..) 方法相对应的属性name 。 |
account.name |
表示与getAccount().setName() 或getAccount().getName() 方法相对应的属性account 的嵌套属性name 。 |
account[2] |
指示索引属性account 的* third *元素。索引属性可以是array ,list 或其他自然排序的集合。 |
account[COMPANYNAME] |
表示由account Map 属性的COMPANYNAME 键索引的 Map 条目的值。 |
以下两个示例类使用BeanWrapper
来获取和设置属性:
public class Company { private String name; private Employee managingDirector; public String getName() { return this.name; } public void setName(String name) { this.name = name; } public Employee getManagingDirector() { return this.managingDirector; } public void setManagingDirector(Employee managingDirector) { this.managingDirector = managingDirector; } }
public class Employee { private String name; private float salary; public String getName() { return this.name; } public void setName(String name) { this.name = name; } public float getSalary() { return salary; } public void setSalary(float salary) { this.salary = salary; } }
BeanWrapper company = new BeanWrapperImpl(new Company()); company.setPropertyValue("name", "Some Company Inc.");
// 或者 PropertyValue value = new PropertyValue("name", "Some Company Inc."); company.setPropertyValue(value); BeanWrapper jim = new BeanWrapperImpl(new Employee()); jim.setPropertyValue("name", "Jim Stravinsky"); company.setPropertyValue("managingDirector", jim.getWrappedInstance()); // retrieving the salary of the managingDirector through the company Float salary = (Float) company.getPropertyValue("managingDirector.salary");
3.3.2. 内置的 PropertyEditor(属性编辑器)实现
Spring 使用PropertyEditor
的概念来实现Object
和String
之间的转换。以不同于对象本身的方式表示属性可能很方便。例如,Date
可以以人类可读的方式表示(如String
:'2007-14-09'
),而我们仍然可以将人类可读的形式转换回原始日期(或者更好的是,转换以人类可读的形式 Importing 的任何日期)回到Date
个对象)。通过注册java.beans.PropertyEditor
类型的自定义编辑器可以实现此行为。在BeanWrapper
上或在特定的 IoC 容器中注册自定义编辑器(如上一章所述),使它具有如何将属性转换为所需类型的知识。有关PropertyEditor
的更多信息,请参见Oracle 的 java.beans 包的 javadoc。
在 Spring 中使用属性编辑的几个示例:
-
通过使用
PropertyEditor
实现在 bean 上设置属性。当使用java.lang.String
作为在 XML 文件中声明的某些 bean 的属性值时,Spring(如果相应属性的设置器具有Class
参数)将使用ClassEditor
尝试将参数解析为Class
对象。 -
在 Spring 的 MVC 框架中,通过使用各种
PropertyEditor
实现来解析 HTTP 请求参数,您可以在CommandController
的所有子类中手动绑定这些实现。
Spring 具有许多内置的PropertyEditor
实现,以简化生活。它们都位于org.springframework.beans.propertyeditors
包中。默认情况下,大多数(但不是全部,如下表所示)由BeanWrapperImpl
注册。如果可以通过某种方式配置属性编辑器,则仍可以注册自己的变体以覆盖默认变体。下表描述了 Spring 提供的各种PropertyEditor
实现:
表 12.内置PropertyEditor
实现
Class | Explanation |
---|---|
ByteArrayPropertyEditor |
字节数组的编辑器。将字符串转换为其相应的字节表示形式。默认情况下由BeanWrapperImpl 注册。 |
ClassEditor |
将表示类的字符串解析为实际类,反之亦然。如果找不到类,则会抛出IllegalArgumentException 。默认情况下,由BeanWrapperImpl 注册。 |
CustomBooleanEditor |
Boolean 个属性的可定制属性编辑器。默认情况下,由BeanWrapperImpl 注册,但是可以通过将其自定义实例注册为自定义编辑器来覆盖。 |
CustomCollectionEditor |
集合的属性编辑器,可将任何源Collection 转换为给定目标Collection 类型。 |
CustomDateEditor |
java.util.Date 的可定制属性编辑器,支持定制DateFormat 。默认未注册。必须根据需要以适当的格式进行用户注册。 |
CustomNumberEditor |
任何Number 子类(例如Integer ,Long ,Float 或Double )的可定制属性编辑器。默认情况下,由BeanWrapperImpl 注册,但是可以通过将其自定义实例注册为自定义编辑器来覆盖。 |
FileEditor |
将字符串解析为java.io.File 个对象。默认情况下,由BeanWrapperImpl 注册。 |
InputStreamEditor |
单向属性编辑器,它可以获取字符串并产生(通过中间的ResourceEditor 和Resource )InputStream ,以便可以将InputStream 属性直接设置为字符串。请注意,默认用法不会为您关闭InputStream 。默认情况下,由BeanWrapperImpl 注册。 |
LocaleEditor |
可以将字符串解析为Locale 对象,反之亦然(字符串格式为[country][variant] ,与Locale 的toString() 方法相同)。默认情况下,由BeanWrapperImpl 注册。 |
PatternEditor |
可以将字符串解析为java.util.regex.Pattern 个对象,反之亦然。 |
PropertiesEditor |
可以将字符串(格式为java.util.Properties 类的 javadoc 中定义的格式)转换为Properties 对象。默认情况下,由BeanWrapperImpl 注册。 |
StringTrimmerEditor |
修剪字符串的属性编辑器。 (可选)允许将空字符串转换为null 值。默认情况下未注册—必须是用户注册的。 |
URLEditor |
可以将 URL 的字符串表示形式解析为实际的URL 对象。默认情况下,由BeanWrapperImpl 注册。 |
Spring 使用java.beans.PropertyEditorManager
来设置可能需要的属性编辑器的搜索路径。搜索路径还包括sun.bean.editors
,其中sun.bean.editors
包括针对Font
,Color
等类型和大多数基本类型的PropertyEditor
实现。还要注意,如果标准 JavaBeans 基础结构与它们处理的类在相同的程序包中并且与该类具有相同的名称并附加了Editor
,则自动发现PropertyEditor
类(无需显式注册它们)。例如,可能具有以下类和包结构,足以识别SomethingEditor
类并将其用作Something
类型的属性的PropertyEditor
。
com
chank
pop
Something
SomethingEditor // the PropertyEditor for the Something class
您也可以在此处使用标准的BeanInfo
JavaBeans 机制(在某种程度上已描述here)。下面的示例使用BeanInfo
机制使用关联类的属性显式注册一个或多个PropertyEditor
实例:
com chank pop Something SomethingBeanInfo // the BeanInfo for the Something class
所引用的SomethingBeanInfo
类的以下 Java 源代码将CustomNumberEditor
与Something
类的age
属性相关联:
public class SomethingBeanInfo extends SimpleBeanInfo {
public PropertyDescriptor[] getPropertyDescriptors() {
try {
final PropertyEditor numberPE = new CustomNumberEditor(Integer.class, true);
PropertyDescriptor ageDescriptor = new PropertyDescriptor("age", Something.class) {
public PropertyEditor createPropertyEditor(Object bean) {
return numberPE;
};
};
return new PropertyDescriptor[] { ageDescriptor };
}
catch (IntrospectionException ex) {
throw new Error(ex.toString());
}
}
}
注册其他自定义 PropertyEditor 实现
当将 bean 属性设置为字符串值时,Spring IoC 容器最终使用标准 JavaBeans PropertyEditor
实现将这些字符串转换为属性的复杂类型。 Spring 预注册了许多自定义PropertyEditor
实现(例如,将表示为字符串的类名转换为Class
对象)。另外,Java 的标准 JavaBeans PropertyEditor
查找机制允许为类的PropertyEditor
适当命名,并与它提供支持的类放在同一包中,以便可以自动找到它。
如果需要注册其他自定义PropertyEditors
,则可以使用几种机制。
最手动的方法(通常不方便或不建议使用)是使用ConfigurableBeanFactory
接口的registerCustomEditor()
方法(假设您有BeanFactory
引用)。
另一种(稍微方便些)的机制是使用称为CustomEditorConfigurer
的特殊BeanFactoryPostProcessor。
尽管您可以将BeanFactoryPostProcessor与BeanFactory
实现一起使用,但是CustomEditorConfigurer
具有嵌套的属性设置,因此我们强烈建议您将CustomEditorConfigurer
与ApplicationContext
一起使用,在这里您可以以与其他任何 Bean 类似的方式部署它,并且可以将其放置在自动检测并应用。
请注意,所有 beanFactory和ApplicationContext通过使用BeanWrapper
来处理属性转换,都会自动使用许多内置的属性编辑器。 BeanWrapper
寄存器的标准属性编辑器在上一节中列出。此外,ApplicationContexts
还重写或添加其他编辑器,以适合于特定应用程序上下文类型的方式处理资源查找。
标准 JavaBeans PropertyEditor
实例用于将以字符串表示的属性值转换为该属性的实际复杂类型。您可以使用 Bean 工厂后处理程序CustomEditorConfigurer
来方便地向ApplicationContext
添加对其他PropertyEditor
实例的支持。
使用CustomEditorConfigurer注册自定义属性编译器
考虑以下示例,该示例定义了一个名为ExoticType
的用户类和另一个名为DependsOnExoticType
的类,该类需要将ExoticType
设置为属性:
package example; public class ExoticType { private String name; public ExoticType(String name) { this.name = name; } } public class DependsOnExoticType { private ExoticType type; public void setType(ExoticType type) { this.type = type; } }
正确设置之后,我们希望能够将 type 属性分配为字符串,PropertyEditor
会将其转换为实际的ExoticType
实例。以下 bean 定义显示了如何构建这种关系:
<bean id="sample" class="example.DependsOnExoticType">
<property name="type" value="aNameForExoticType"/>
</bean>
PropertyEditor
的实现可能类似于以下内容:
package example; public class ExoticTypeEditor extends PropertyEditorSupport { public void setAsText(String text) { setValue(new ExoticType(text.toUpperCase())); } }
最后,以下示例显示了如何使用CustomEditorConfigurer
向ApplicationContext
注册新的PropertyEditor
,然后便可以根据需要使用它了:
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer"> <property name="customEditors"> <map> <entry key="example.ExoticType" value="example.ExoticTypeEditor"/> </map> </property> </bean>
使用 PropertyEditorRegistrar注册自定义属性编译器
向 Spring 容器注册属性编辑器的另一种机制是创建并使用PropertyEditorRegistrar
。
当需要在几种不同情况下使用同一组属性编辑器时,此接口特别有用。您可以编写相应的注册商,并在每种情况下重复使用它。
PropertyEditorRegistrar实例与名为PropertyEditorRegistry
的接口配合使用,PropertyEditorRegistry接口由 Spring BeanWrapper
(和DataBinder
)实现。
PropertyEditorRegistrar
实例与CustomEditorConfigurer
(描述为here)结合使用时特别方便,后者公开了称为setPropertyEditorRegistrars(..)
的属性。以这种方式添加到CustomEditorConfigurer
的PropertyEditorRegistrar
实例可以轻松地与DataBinder
和 Spring MVC 控制器共享。
此外,它避免了在自定义编辑器上进行同步的需要:PropertyEditorRegistrar
有望为每次 bean 创建尝试创建新的PropertyEditor
实例。
以下示例显示了如何创建自己的PropertyEditorRegistrar
实现:
package com.foo.editors.spring; public final class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar { public void registerCustomEditors(PropertyEditorRegistry registry) { registry.registerCustomEditor(ExoticType.class, new ExoticTypeEditor()); // you could register as many custom property editors as are required here... } }
另请参见org.springframework.beans.support.ResourceEditorRegistrar
以获取示例PropertyEditorRegistrar
的实现。请注意,在实现registerCustomEditors(..)
方法时,它如何创建每个属性编辑器的新实例。
下一个示例显示了如何配置CustomEditorConfigurer
并将CustomPropertyEditorRegistrar
实例注入其中:
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer"> <property name="propertyEditorRegistrars"> <list> <ref bean="customPropertyEditorRegistrar"/> </list> </property> </bean> <bean id="customPropertyEditorRegistrar" class="com.foo.editors.spring.CustomPropertyEditorRegistrar"/>
将PropertyEditorRegistrars
与数据绑定Controllers
(例如SimpleFormController
)结合使用会非常方便。下面的示例在initBinder(..)
方法的实现中使用PropertyEditorRegistrar
:
public final class RegisterUserController extends SimpleFormController { private final PropertyEditorRegistrar customPropertyEditorRegistrar; public RegisterUserController(PropertyEditorRegistrar propertyEditorRegistrar) { this.customPropertyEditorRegistrar = propertyEditorRegistrar; } protected void initBinder(HttpServletRequest request,ServletRequestDataBinder binder) throws Exception { this.customPropertyEditorRegistrar.registerCustomEditors(binder); } // other methods to do with registering a User }
这种PropertyEditor
注册样式可以导致代码简洁(initBinder(..)
的实现只有一行长),并且可以将通用的PropertyEditor
注册代码封装在一个类中,然后根据需要在多个Controllers
之间共享。
3.4. Spring Type Conversion
Spring 3 引入了core.convert
软件包,该软件包提供了常规的类型转换系统。该系统定义了一个用于实现类型转换逻辑的 SPI 和一个用于在运行时执行类型转换的 API。
在 Spring 容器中,您可以将此系统用作PropertyEditor
实现的替代方法,以将外部化的 bean 属性值字符串转换为所需的属性类型。您还可以在应用程序中需要类型转换的任何地方使用公共 API。
3.4.1. converter SPI
如以下接口定义所示,用于实现类型转换逻辑的 SPI 非常简单且具有强类型。
package org.springframework.core.convert.converter; public interface Converter<S, T> { T convert(S source); }
要创建自己的转换器,请实现Converter
界面,并将S
作为要转换的类型,并将T
参数化为要转换的类型。如果需要将S
的集合或数组转换为T
的数组或集合,您也可以透明地应用此类转换器,前提是已注册了委派的数组或集合转换器(默认情况下DefaultConversionService
这样做)。
对于每次convert(S)
的调用,保证源参数不为 null。如果转换失败,您的Converter
可能会引发任何未经检查的异常。具体来说,它应该抛出IllegalArgumentException
以报告无效的源值。注意确保Converter
实现是线程安全的。
为方便起见,在core.convert.support
包中提供了几种转换器实现。这些包括从字符串到数字以及其他常见类型的转换器。下面的清单显示了StringToInteger
类,这是一个典型的Converter
实现:
package org.springframework.core.convert.support; final class StringToInteger implements Converter<String, Integer> { public Integer convert(String source) { return Integer.valueOf(source); } }
3.4.2. 使用 ConverterFactory
当需要集中整个类层次结构的转换逻辑时(例如,从 String 转换为 java.lang.Enum 对象时),可以实现ConverterFactory
,如以下示例所示:
package org.springframework.core.convert.converter; public interface ConverterFactory<S, R> { <T extends R> Converter<S, T> getConverter(Class<T> targetType); }
参数化 S 为您要转换的类型,参数化 R 为定义可以转换为的类的“范围”的基本类型。然后实现 getConverter(Class),其中 T 是 R 的子类。
package org.springframework.core.convert.support; final class StringToEnumConverterFactory implements ConverterFactory<String, Enum> { public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) { return new StringToEnumConverter(targetType); } private final class StringToEnumConverter<T extends Enum> implements Converter<String, T> { private Class<T> enumType; public StringToEnumConverter(Class<T> enumType) { this.enumType = enumType; } public T convert(String source) { return (T) Enum.valueOf(this.enumType, source.trim()); } } }
3.4.3. 使用 GenericConverter
当您需要复杂的Converter
实现时,请考虑使用GenericConverter
接口。 GenericConverter
具有比Converter
更灵活但类型不那么强的签名,支持GenericConverter
在多种源类型和目标类型之间进行转换。另外,GenericConverter
提供了实现转换逻辑时可以使用的源字段和目标字段上下文。这种上下文允许类型转换由字段注解或在字段签名上声明的通用信息驱动。以下清单显示了GenericConverter
的接口定义:
package org.springframework.core.convert.converter; public interface GenericConverter { public Set<ConvertiblePair> getConvertibleTypes(); Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType); }
要实现GenericConverter
,让getConvertibleTypes()
返回支持的源→目标类型对。然后实现convert(Object, TypeDescriptor, TypeDescriptor)
以包含您的转换逻辑。源TypeDescriptor
提供对包含正在转换的值的源字段的访问。目标TypeDescriptor
提供对要设置转换值的目标字段的访问。
GenericConverter
的一个很好的例子是在 Java 数组和集合之间进行转换的转换器。这样的ArrayToCollectionConverter
会检查声明目标集合类型的字段以解析集合的元素类型。这样就可以在将集合设置到目标字段上之前,将源数组中的每个元素转换为集合元素类型。
Note
由于GenericConverter
是更复杂的 SPI 接口,因此仅应在需要时使用它。Converter
或ConverterFactory
以满足基本类型转换需求。
使用 ConditionalGenericConverter
有时,您希望Converter
仅在满足特定条件的情况下运行。例如,您可能只想在目标字段上存在特定注解时才运行Converter
,或者仅在目标类上定义了特定方法(例如static valueOf
方法)时才运行Converter
。 ConditionalGenericConverter
是GenericConverter
和ConditionalConverter
接口的并集,可用于定义以下自定义匹配条件:
public interface ConditionalConverter { boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType); } public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter { }
ConditionalGenericConverter
的一个很好的例子是EntityConverter
,它在持久性实体标识符和实体引用之间转换。仅当目标实体类型声明静态查找器方法(例如findAccount(Long)
)时,此类EntityConverter
才可能匹配。您可以在matches(TypeDescriptor, TypeDescriptor)
的实现中执行这种 finder 方法检查。
3.4.4. ConversionService API
ConversionService
定义了一个统一的 API,用于在运行时执行类型转换逻辑。转换器通常在以下外观界面后面执行:
package org.springframework.core.convert; public interface ConversionService { boolean canConvert(Class<?> sourceType, Class<?> targetType); <T> T convert(Object source, Class<T> targetType); boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType); Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType); }
大多数ConversionService
实现还实现ConverterRegistry
,该ConverterRegistry
提供了用于注册转换器的 SPI。在内部,ConversionService
实现委派其注册的转换器执行类型转换逻辑。
core.convert.support
软件包中提供了一种强大的ConversionService
实现。 GenericConversionService
是适用于大多数环境的通用实现。 ConversionServiceFactory
提供了一个方便的工厂来创建通用的ConversionService
配置。
3.4.5. 配置 ConversionService
ConversionService
是无状态对象,旨在在应用程序启动时实例化,然后在多个线程之间共享。在 Spring 应用程序中,通常为每个 Spring 容器(或ApplicationContext
)配置一个ConversionService
实例。 Spring 需要ConversionService
并在框架需要执行类型转换时使用它。您也可以将此ConversionService
注入到您的任何 bean 中并直接调用它。
Note
如果没有ConversionService
向 Spring 注册,则使用基于PropertyEditor
的原始系统。
要向 Spring 注册默认的ConversionService
,请添加以下 Bean 定义,并将id命名为
conversionService
:
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"/>
默认的ConversionService
可以在字符串,数字,枚举,集合,Map 和其他常见类型之间进行转换。要用您自己的自定义转换器补充或覆盖默认转换器,请设置converters
属性。属性值可以实现Converter
,ConverterFactory
或GenericConverter
接口中的任何一个。
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <set> <bean class="example.MyCustomConverter"/> </set> </property> </bean>
在 Spring MVC 应用程序中通常使用ConversionService
。
3.4.6. 以编程方式使用 ConversionService
要以编程方式使用ConversionService
实例,可以像对其他任何 bean 一样注入对该实例的引用。以下示例显示了如何执行此操作:
@Service public class MyService { @Autowired public MyService(ConversionService conversionService) { this.conversionService = conversionService; } public void doIt() { this.conversionService.convert(...) } }
对于大多数使用情况,可以使用指定targetType
的convert
方法,但不适用于更复杂的类型,例如参数化元素的集合。例如,如果要以编程方式将Integer
的List
转换为String
的List
,则需要提供源类型和目标类型的正式定义。
幸运的是,TypeDescriptor
提供了各种选项来使操作变得如此简单,如以下示例所示:
DefaultConversionService cs = new DefaultConversionService(); List<Integer> input = .... cs.convert(input, TypeDescriptor.forObject(input), // List<Integer> type descriptor TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class)));
请注意,DefaultConversionService
自动注册适用于大多数环境的转换器。这包括集合转换器,标量转换器和基本的Object
到String
转换器。您可以使用DefaultConversionService
类上的静态addDefaultConverters
方法将相同的转换器注册到任何ConverterRegistry
中。
值类型的转换器可重用于数组和集合,因此,无需使用特定的转换器即可将S
的Collection
转换为T
的Collection
,前提是需要标准的集合处理。
3.5. Spring Field Formatting
如上一节所述,core.convert是通用类型转换系统。它提供了统一的ConversionService
API 以及强类型的Converter
SPI,用于实现从一种类型到另一种类型的转换逻辑。 Spring 容器使用此系统绑定 bean 属性值。此外,Spring Expression Language(SpEL)和DataBinder
都使用此系统绑定字段值。例如,当 SpEL 需要将Short
强制转换为Long
才能完成expression.setValue(Object bean, Object value)
尝试时,core.convert
系统将执行强制转换。
现在考虑典型 Client 端环境(例如 Web 或桌面应用程序)的类型转换要求。在这种环境中,通常您会从String
转换为支持 Client 端回发过程,而从String
转换为支持视图渲染过程。另外,您通常需要本地化String
值。更为通用的core.convert
Converter
SPI 不能直接解决此类格式化要求。为了直接解决这些问题,Spring 3 引入了一个方便的Formatter
SPI,它为 Client 端环境提供了一种简单且健壮的替代PropertyEditor
实现的方法。
通常,当您需要实现通用类型转换逻辑时(例如,在java.util.Date
和java.lang.Long
之间进行转换),可以使用Converter
SPI。在 Client 端环境(例如 Web 应用程序)中工作并且需要解析和打印本地化的字段值时,可以使用Formatter
SPI。 ConversionService
为两个 SPI 提供统一的类型转换 API。
3.5.1. 格式化器 SPI
用于实现字段格式逻辑的Formatter
SPI 非常简单且类型严格。以下清单显示了Formatter
接口定义:
package org.springframework.format;
public interface Formatter<T> extends Printer<T>, Parser<T> {
}
Formatter
来自Printer
和Parser
构件块接口。以下清单显示了这两个接口的定义:
public interface Printer<T> { String print(T fieldValue, Locale locale); } import java.text.ParseException; public interface Parser<T> { T parse(String clientValue, Locale locale) throws ParseException; }
要创建自己的Formatter
,请实现前面显示的Formatter
接口。将T
参数化为您希望格式化的对象类型(例如java.util.Date
)。实现print()
操作以打印T
的实例以在 Client 端语言环境中显示。实现parse()
操作,以从 Client 端语言环境返回的格式化表示形式解析T
的实例。如果解析尝试失败,则您的Formatter
应该抛出ParseException
或IllegalArgumentException
。注意确保Formatter
实现是线程安全的。
为了方便起见,format
子软件包提供了Formatter
个实现。
number
包提供NumberStyleFormatter
,CurrencyStyleFormatter
和PercentStyleFormatter,用来将java.lang.Number
对象格式化为java.text.NumberFormat
。
datetime
包提供了DateFormatter
,用于将java.util.Date
对象格式化为java.text.DateFormat
。
以下DateFormatter是示例Formatter的实现: package org.springframework.format.datetime; public final class DateFormatter implements Formatter<Date> { private String pattern; public DateFormatter(String pattern) { this.pattern = pattern; } public String print(Date date, Locale locale) { if (date == null) { return ""; } return getDateFormat(locale).format(date); } public Date parse(String formatted, Locale locale) throws ParseException { if (formatted.length() == 0) { return null; } return getDateFormat(locale).parse(formatted); } protected DateFormat getDateFormat(Locale locale) { DateFormat dateFormat = new SimpleDateFormat(this.pattern, locale); dateFormat.setLenient(false); return dateFormat; } }
3.5.2.注解驱动的格式
可以通过字段类型或注解配置字段格式。要将注解绑定到Formatter
,请实现AnnotationFormatterFactory
。以下清单显示了AnnotationFormatterFactory
接口的定义:
package org.springframework.format;
public interface AnnotationFormatterFactory<A extends Annotation> {
Set<Class<?>> getFieldTypes();
Printer<?> getPrinter(A annotation, Class<?> fieldType);
Parser<?> getParser(A annotation, Class<?> fieldType);
}
要创建一个实现:将 A 参数化为您希望与格式逻辑关联的字段annotationType
,例如org.springframework.format.annotation.DateTimeFormat
。 让getFieldTypes()
返回可以在其上使用注解的字段类型 。让getPrinter()
返回Printer
以打印带注解的字段的值。 让getParser()
返回Parser
来解析带注解字段的clientValue
。
示例AnnotationFormatterFactory
实现将@NumberFormat
注解 绑定到格式化程序,以指定数字样式或模式:
public final class NumberFormatAnnotationFormatterFactory implements AnnotationFormatterFactory<NumberFormat> { public Set<Class<?>> getFieldTypes() { return new HashSet<Class<?>>(asList(new Class<?>[] { Short.class, Integer.class, Long.class, Float.class, Double.class, BigDecimal.class, BigInteger.class })); } public Printer<Number> getPrinter(NumberFormat annotation, Class<?> fieldType) { return configureFormatterFrom(annotation, fieldType); } public Parser<Number> getParser(NumberFormat annotation, Class<?> fieldType) { return configureFormatterFrom(annotation, fieldType); } private Formatter<Number> configureFormatterFrom(NumberFormat annotation, Class<?> fieldType) { if (!annotation.pattern().isEmpty()) { return new NumberStyleFormatter(annotation.pattern()); } else { Style style = annotation.style(); if (style == Style.PERCENT) { return new PercentStyleFormatter(); } else if (style == Style.CURRENCY) { return new CurrencyStyleFormatter(); } else { return new NumberStyleFormatter(); } } } }
要触发格式,可以使用@NumberFormat注解 字段,如以下示例所示:
public class MyModel { @NumberFormat(style=Style.CURRENCY) private BigDecimal decimal; }
格式 注解API
org.springframework.format.annotation
软件包中存在可移植格式 注解API。您可以使用@NumberFormat
格式化 java.lang.Number 字段,使用@DateTimeFormat
格式化java.util.Date
,java.util.Calendar
,java.util.Long
或 Joda-Time 字段。
下面的示例使用@DateTimeFormat
格式化java.util.Date
作为 ISO 日期(yyyy-MM-dd):
public class MyModel { @DateTimeFormat(iso=ISO.DATE) private Date date; }
3.5.3. FormatterRegistry SPI
FormatterRegistry
是用于注册格式器和转换器的 SPI。 FormattingConversionService
是适用于大多数环境的FormatterRegistry
的实现。您可以使用FormattingConversionServiceFactoryBean
以编程方式或声明方式将此实现配置为 Spring bean。因为此实现还实现了ConversionService
,所以您可以直接配置它以与 Spring 的DataBinder
和 Spring Expression Language(SpEL)一起使用。
以下清单显示了FormatterRegistry
SPI:
package org.springframework.format;
public interface FormatterRegistry extends ConverterRegistry {
void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser);
void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter);
void addFormatterForFieldType(Formatter<?> formatter);
void addFormatterForAnnotation(AnnotationFormatterFactory<?, ?> factory);
}
如前面的清单所示,您可以按字段类型或注解注册格式化程序。
FormatterRegistry
SPI 使您可以集中配置格式设置规则,而不必在控制器之间复制此类配置。例如,您可能需要强制以某种方式设置所有日期字段的格式或以某种方式设置带有特定注解的字段的格式。使用共享的FormatterRegistry
,您一次定义这些规则,并在需要格式化时将它们应用。
3.5.4. FormatterRegistrar SPI
FormatterRegistrar
是用于通过 FormatterRegistry 注册格式器和转换器的 SPI。以下清单显示了其接口定义:
package org.springframework.format; public interface FormatterRegistrar { void registerFormatters(FormatterRegistry registry); }
为给定的格式类别(例如日期格式)注册多个相关的转换器和格式器时,FormatterRegistrar
很有用。在声明式注册不充分的情况下它也很有用。例如,当格式化程序需要在不同于其自己的<T>
的特定字段类型下进行索引时,或者在注册Printer
/Parser
对时。下一节将提供有关转换器和格式化程序注册的更多信息。
3.5.5. 在 Spring MVC 中配置格式
3.6. 配置全局日期和时间格式
默认情况下,未使用@DateTimeFormat
注解 的日期和时间字段是使用DateFormat.SHORT
样式从字符串转换的。如果愿意,可以通过定义自己的全局格式来更改此设置。
为此,您需要确保 Spring 不注册默认格式器。相反,您应该手动注册所有格式化程序。使用org.springframework.format.datetime.joda.JodaTimeFormatterRegistrar
或org.springframework.format.datetime.DateFormatterRegistrar
类,具体取决于您使用的是 Joda-Time 库。
@Configuration public class AppConfig { @Bean public FormattingConversionService conversionService() { // Use the DefaultFormattingConversionService but do not register defaults DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(false); // Ensure @NumberFormat is still supported conversionService.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory()); // Register date conversion with a specific global format DateFormatterRegistrar registrar = new DateFormatterRegistrar(); registrar.setFormatter(new DateFormatter("yyyyMMdd")); registrar.registerFormatters(conversionService); return conversionService; } }
如果您喜欢基于 XML 的配置,则可以使用FormattingConversionServiceFactoryBean
。以下示例显示了如何执行此操作(这次使用 Joda Time):
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd> <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="registerDefaultFormatters" value="false" /> <property name="formatters"> <set> <bean class="org.springframework.format.number.NumberFormatAnnotationFormatterFactory" /> </set> </property> <property name="formatterRegistrars"> <set> <bean class="org.springframework.format.datetime.joda.JodaTimeFormatterRegistrar"> <property name="dateFormatter"> <bean class="org.springframework.format.datetime.joda.DateTimeFormatterFactoryBean"> <property name="pattern" value="yyyyMMdd"/> </bean> </property> </bean> </set> </property> </bean> </beans>
Note
Joda-Time 提供了单独的不同类型来表示date
,time
和date-time
值。 JodaTimeFormatterRegistrar
的dateFormatter
,timeFormatter
和dateTimeFormatter
属性应用于为每种类型配置不同的格式。 DateTimeFormatterFactoryBean
提供了一种创建格式化程序的便捷方法。
Note
如果您使用 Spring MVC,请记住显式配置所使用的转换服务。对于基于 Java 的@Configuration
,这意味着扩展WebMvcConfigurationSupport
类并覆盖mvcConversionService()
方法。对于 XML,应使用mvc:annotation-driven
元素的conversion-service
属性。有关详情,请参见转换和格式化。
3.7. Spring 验证
Spring 3 对其验证支持进行了一些增强。首先,完全支持 JSR-303 Bean 验证 API。其次,当以编程方式使用时,Spring 的DataBinder
可以验证对象并绑定到它们。第三,Spring MVC 支持以声明方式验证@Controller
Importing。
3.7.1. JSR-303 Bean 验证 API 概述
JSR-303 标准化了 Java 平台的验证约束声明和元数据。通过使用此 API,您可以使用声明性验证约束来注解域模型属性,并且运行时会强制执行它们。您可以使用许多内置约束。您还可以定义自己的自定义约束。
考虑以下示例,该示例显示了具有两个属性的简单PersonForm
模型:
public class PersonForm { private String name; private int age; }
JSR-303 允许您针对此类属性定义声明性验证约束,如以下示例所示:
public class PersonForm { @NotNull @Size(max=64) private String name; @Min(0) private int age; }
3.7.2. 配置 Bean 验证提供程序
Spring 提供了对 Bean 验证 API 的全面支持。这包括对将 JSR-303 或 JSR-349 Bean 验证提供程序引导为 Spring Bean 的便捷支持。这样,您就可以在应用程序中需要验证的地方注入javax.validation.ValidatorFactory
或javax.validation.Validator
。
您可以使用LocalValidatorFactoryBean
将默认的 Validator 配置为 Spring Bean,如以下示例所示:
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
注入验证器
LocalValidatorFactoryBean
实现javax.validation.ValidatorFactory
和javax.validation.Validator
以及 Spring 的org.springframework.validation.Validator
。您可以将对这些接口之一的引用注入需要调用验证逻辑的 bean 中。
如果您希望直接使用 Bean Validation API,则可以插入对javax.validation.Validator
的引用,如以下示例所示:
import javax.validation.Validator; @Service public class MyService { @Autowired private Validator validator;
如果您的 bean 需要使用 Spring Validation API,则可以注入对org.springframework.validation.Validator
的引用,如以下示例所示:
import org.springframework.validation.Validator; @Service public class MyService { @Autowired private Validator validator; }
配置自定义约束
每个 bean 验证约束由两部分组成:* @Constraint
注解,用于声明约束及其可配置属性。 * javax.validation.ConstraintValidator
接口的实现,用于实现约束的行为。
要将声明与实现相关联,每个@Constraint
注解都引用一个对应的ConstraintValidator
实现类。在运行时,当域模型中遇到约束注解时,ConstraintValidatorFactory
实例化引用的实现。
默认情况下,LocalValidatorFactoryBean
配置使用 Spring 创建ConstraintValidator
实例的SpringConstraintValidatorFactory
。这使您的自定义ConstraintValidators
像任何其他 Spring bean 一样受益于依赖项注入。
以下示例显示了一个自定义@Constraint
声明,后跟一个关联的ConstraintValidator
实现,该实现使用 Spring 进行依赖项注入:
@Target({ElementType.METHOD, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy=MyConstraintValidator.class) public @interface MyConstraint { } import javax.validation.ConstraintValidator; public class MyConstraintValidator implements ConstraintValidator { @Autowired; private Foo aDependency; ... }
如前面的示例所示,ConstraintValidator实现可以像其他任何 Spring bean 一样具有其依赖关系@Autowired。
Spring 驱动的方法验证
您可以通过MethodValidationPostProcessor
bean 定义将 Bean Validation 1.1(以及作为自定义扩展,还包括 Hibernate Validator 4.3)支持的方法验证功能通过MethodValidationPostProcessor
bean 定义集成到 Spring 上下文中,如下所示:
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>
为了有资格通过 Spring 驱动的方法验证,所有目标类都需要使用 Spring 的@Validated
注解 进行 注解。
其他配置选项
在大多数情况下,默认的LocalValidatorFactoryBean
配置就足够了。从消息插值到遍历解析,有许多用于各种 Bean 验证构造的配置选项。
3.7.3. 配置一个 DataBinder
从 Spring 3 开始,您可以使用Validator
配置DataBinder
实例。配置完成后,您可以通过调用binder.validate()
来调用Validator
。任何验证Errors
都会自动添加到 Binder 的BindingResult
中。
下面的示例显示在绑定到目标对象之后,如何以编程方式使用DataBinder
来调用验证逻辑:
Foo target = new Foo(); DataBinder binder = new DataBinder(target); binder.setValidator(new FooValidator()); // bind to the target object binder.bind(propertyValues); // validate the target object binder.validate(); // get BindingResult that includes any validation errors BindingResult results = binder.getBindingResult();
您还可以通过dataBinder.addValidators
和dataBinder.replaceValidators
配置具有多个Validator
实例的DataBinder
。当将全局配置的 bean 验证与在 DataBinder 实例上本地配置的 Spring Validator
结合使用时,这很有用。
BeanWrapper
BeanWrapper是一个方便开发人员使用字符串来对Java Bean的属性执行get、set操作的工具类。它为那些UI类app提供了极大的便利,是以字符串和用户交互的
-
Foo foo = new Foo();
-
BeanWrapperImpl fooWrapper = new BeanWrapperImpl(foo);
-
fooWrapper.setPropertyValue("intProperty", "1");
-
Object intProperty = fooWrapper.getPropertyValue("intProperty");
另外,BeanWrapper内部使用了两种机制:
1. PropertyEditor
BeanWrapper和java bean的内省模式密切关联,而PropertyEditor(只提供了String >> Object的转换)隶属于Java Bean规范。
2. ConversionService
Spring自3.0之后提供的替代PropertyEditor的机制
注:按照Spring官方文档的说法,当容器内没有注册ConversionService的时候,会退回使用PropertyEditor机制。
ConversionService
onversionService及其相关一套类型转换机制是一套通用的类型转换SPI,相比PropertyEditor只提供String >> Object的转换,ConversionService能够提供任意Object >> Object的转换。
由此我们可以看出,Spring为何要使用ConversionService替代PropertyEditor有三个原因:
- ConversionService功能更强大,支持的类型转换范围更广
- ConverterFactory支持一整个class hierarchy的转换(也就是多态),PropertyEditor则不行
- Java Bean这个规范最初是和Java GUI(Swing)一起诞生的,PropertyEditor接口里有大量和GUI相关的方法,显然已经过时了。顺便提一句,Java Bean和POJO不是一个概念,Java Bean不仅有setter、getter,还有一系列和Java GUI配套的东西。
Formatter
Formatter SPI是另外一套和PropertyEditor类似的,String<->Object的转换机制,但是有两个优点:
- 接口更干净,没有关于GUI的部分,只有 Printer.print() 和 Parser.parse() 两个方法
- 基于注解,支持同一类型的属性根据不同的格式来做String<->Object的转换。比如日期类型,一个字段的格式是yyyy-MM-dd,另一个格式是yyyyMMdd,如果利用PropertyEditor是比较麻烦,但是在这里就可以利用
@DateTimeFormat
来达到这个效果。
Spring提供了DefaultFormattingConversionService来支持Formatter SPI,也就是说如果要使用Formatter SPI,依然可以利用ConversionService接口。
注:Formatter SPI必须基于注解才可以使用,这点和ConversionService基于类型不同。
DataBinder
DataBinder主要提供了两个功能:
- 利用BeanWrapper,给对象的属性设值
- 在设值的同时做Validation
前四者关系图
ConversionService有两种实现,也就是说,如果要支持Formatter SPI,只需要让BeanWrapper切换使用不同的ConversionService即可。
- DefaultConversionService,不支持Formatter SPI
- DefaultFormattingConversionService,支持Formatter SPI
Validator
Validator
较简单,如果类路径上存在 Bean Validation(例如,Hibernate Validator),则将LocalValidatorFactoryBean
注册为全局Validator,以便与@Valid一起使用在controller 方法参数上。
非Spring MVC的使用
Spring Core Context(要解析spring xml的)其实也使用ConversionService,但是是非强制的。让Spring Core Context使用conversionService的方式很简单,配置一个名字叫做conversionService
的Bean即可。需要注意的是,因为这个Bean是在非常早的时候就被使用的(AbstractApplicationContext#L834),因此它最好不要依赖过多的其他的Bean,避免造成启动失败。
Spring在读取xml配置文件的时候,因为xml文件实际上是一个文本文件,所有值的设置都是String,这个时候如果给bean的复杂类型属性设置值,它会用到PropertyEditor或ConversionService。
<bean id="someBean" class="a.b.c.SomeBean">
<property name="color" value="red"/>
</bean>
例子中的color
属性是Color
类型,这时就会利用到PropertyEditor和ConversionService。
Spring MVC的使用
Spring MVC对于conversionService的使用比较特殊,它自己会注册一个名字叫做mvcConversionService
类型为DefaultFormattingConversionService
的Bean。因此会存在以下陷阱:
- 如果Core Context中也定义了一个ConversionService,那么在MVC环境下,会有两个ConversionService的Bean。
- 针对Core Context的
ConversionService
做的Customize如FormatterRegistrar、ConverterRegistry
、FormatterRegistry
、ConversionServiceFactoryBean
、FormattingConversionServiceFactoryBean
是不会应用到MVC的那个ConversionService上。
HttpMessageConverter和ConversionService是什么关系?
个人理解,可以他们是不同的两种东西,二者各司其职,前者转换请求body信息和响应body信息,后者用于请求参数的转换。都可以接受文本信息,最终解析成对象。根本的区别:
HttpMessageConvert
官方文档中的说明:We can use the @RequestBody annotation on the argument of a Controller method to indicate that the body of the HTTP Request is deserialized to that particular Java entity. To determine the appropriate converter, Spring will use the “Content-Type” header from the client request.
对,@RequestBody决定了要使用HttpMessageConverter,而Content-Type则是选择具体某一个配置器。HttpMessageConverter<T>,默认有很多配置器:StringHttpMessageConverter,ByteArrayHttpMessageConverter,SourceHttpMessageConverter,FormHttpMessageConverter 。
ConversionService
它使用的是 WebDataBinder (extends DataBinder),处理url或@RequestParam等非@RequestBody参数,它数据绑定是这样的流程:
- 将ServletRequest对象及处理方法入参对象实例传给DataBinder
- DataBinder 调用转配在Spring Web上下文中的ConversionService进行数据类型转换、数据格式化等工作,将ServletRequest中的消息填充到入参对象中
- 调用 Validator 对已经绑定的请求信息数据的入参对象进行数据合法性校验,生成数据绑定结果 BindingResult。BindingResult 包含完成绑定的入参对象和相应的校验错误对象。而后将 BindingResult 中的入参对象及校验错误对象赋给处理方法的入参。