书接上文
接下来到 Spring framework core 的第三大块 —— 验证、数据绑定和类型转换
验证、数据绑定和类型转换
考虑将验证作为业务逻辑有利有弊,Spring 提供了一种验证(和数据绑定)设计。具体来说,验证不应与 Web 层绑定,并且应该易于本地化,并且应该可以插入任何可用的验证器。考虑到这些问题,Spring 提供了一个 Validator
合同,该合同既基本又可以在应用程序的每个层中使用。
数据绑定对于使用户输入动态绑定到应用程序的域模型(或用于处理用户输入的任何对象)非常有用。Spring 提供了恰当地命名为 DataBinder
的功能。 Validator
和 DataBinder
组成了验证包,该验证包主要用于但不限于 Web 层。
BeanWrapper
是 Spring 框架中的基本概念,并在很多地方使用。但是,您可能不需要直接使用 BeanWrapper
。但是,因为这是参考文档,所以我们认为可能需要进行一些解释。我们将在本章中解释 BeanWrapper
,因为如果您要使用它,那么在尝试将数据绑定到对象时最有可能使用它。
Spring 的 DataBinder
和较低级别的 BeanWrapper
都使用 PropertyEditorSupport
实现来解析和格式化属性值。 PropertyEditor
和 PropertyEditorSupport
类型是 JavaBeans 规范的一部分,本章还将对此进行说明。 Spring 3 引入了 core.convert
包,该包提供了常规的类型转换工具,以及用于格式化 UI 字段值的高级“format”包。您可以将这些包用作 PropertyEditorSupport
实现的更简单替代方案。本章还将对它们进行讨论。
Spring 通过设置基础结构和 Spring 自己的 Validator
合同的适配器来支持 Java Bean 验证。应用程序可以全局启用一次 Bean 验证,如 Java Bean 验证中所述,并将其专用于所有验证需求。在 Web 层中,应用程序可以每个 DataBinder
进一步注册控制器本地的 Spring Validator
实例,如配置 DataBinder
中所述,这对于插入自定义验证逻辑很有用。
使用 Spring 的 Validator 接口进行验证
Spring 具有 Validator
接口,可用于验证对象。 Validator
接口通过使用 Errors
对象来工作,以便验证器在验证时可以将验证失败报告给 Errors
对象。
1 | public class Person { |
下一个示例通过实现 org.springframework.validation.Validator
接口的以下两个方法来提供 Person
类的验证行为:
supports(Class)
:此验证程序可以验证提供的 Class 的实例吗?validate(Object, org.springframework.validation.Errors)
:验证给定的对象,并在发生验证错误的情况下,向给定的Errors
对象注册这些对象。
实施 Validator
非常简单,尤其是当您知道 Spring Framework 也提供的 ValidationUtils
帮助器类时。 以下示例实现了用于 Person
实例的 Validator
:
1 | public class PersonValidator implements Validator { |
ValidationUtils
类上的静态 rejectIfEmpty(..)
方法用于拒绝 name 属性(如果该属性为 null 或为空字符串)。查看 ValidationUtils
javadoc,看看它除了提供前面显示的示例外还提供什么功能。
虽然可以实现单个 Validator
类来验证丰富对象中的每个嵌套对象,但最好在其自己的 Validator
实现中封装对象的每个嵌套类的验证逻辑。一个“丰富”对象的简单示例是一个 Customer
,它由两个 String 属性(第一个和第二个名称)和一个复杂的 Address
对象组成。地址对象可以独立于客户对象使用,因此已实现了不同的 AddressValidator
。如果希望 CustomerValidator
重用 AddressValidator
类中包含的逻辑而不求助于复制和粘贴,则可以在 CustomerValidator
中依赖注入或实例化一个 AddressValidator
,如以下示例所示:
1 | public class CustomerValidator implements Validator { |
验证错误将报告给传递给验证器的 Errors
对象。 对于 Spring Web MVC,可以使用 <spring:bind/>
标记检查错误消息,但是也可以自己检查 Errors
对象。关于它提供的方法的更多信息可以在 javadoc 中找到。
将代码解析为错误消息
我们介绍了数据绑定和验证。本节介绍与验证错误相对应的输出消息。在上一节显示的示例中,我们拒绝了名称和年龄字段。如果要使用 MessageSource
输出错误消息,可以使用拒绝字段时提供的错误代码(在这种情况下为“名称”和“年龄”)来进行输出。当您从 Errors
接口调用(直接或间接通过使用诸如 ValidationUtils
类的直接或间接)rejectValue
或其他拒绝方法之一时,基础实现不仅注册您传入的代码,还注册许多其他错误代码。MessageCodesResolver
确定 Errors
接口寄存器中的哪个错误代码。默认情况下,使用 DefaultMessageCodesResolver
,它(例如)不仅使用您提供的代码注册消息,而且还注册包含传递给拒绝方法的字段名称的消息。因此,如果您通过使用 rejectValue("age", "too.darn.old")
拒绝字段,除了 too.darn.old
代码外,Spring 还会注册 too.darn.old.age
和 too.darn.old.age.int
(第一个包含字段名称,第二个包含字段类型)。这样做是为了方便开发人员在定位错误消息时提供帮助。
有关 MessageCodesResolver 和默认策略的更多信息,可以分别在 MessageCodesResolver
和 DefaultMessageCodesResolver
的 javadoc 中找到。
Bean 操作和 BeanWrapper
org.springframework.beans
包遵循 JavaBeans 标准。 JavaBean 是具有默认无参数构造函数的类,并且遵循命名约定,在该命名约定下,例如,名为 bingoMadness
的属性将具有 setter 方法 setBingoMadness(..)
和 getter 方法 getBingoMadness()
。有关 JavaBean 和规范的更多信息,请参见 javabeans。
Bean 包中的一个非常重要的类是 BeanWrapper
接口及其相应的实现(BeanWrapperImpl
)。就像从 Javadoc 引用的那样,BeanWrapper
提供了以下功能:设置和获取属性值(单独或批量),获取属性描述符以及查询属性以确定它们是否可读或可写。此外,BeanWrapper
还支持嵌套属性,从而可以将子属性上的属性设置为无限深度。 BeanWrapper
还支持添加标准 JavaBeans PropertyChangeListeners
和 VetoableChangeListeners
的功能,而无需在目标类中支持代码。最后但并非最不重要的一点是,BeanWrapper
支持设置索引属性。 BeanWrapper
通常不直接由应用程序代码使用,而是由 DataBinder
和 BeanFactory
使用。
BeanWrapper
的工作方式部分由其名称表示:它包装一个 Bean,以对该 Bean 执行操作,例如设置和检索属性。
设置和获取基本和嵌套属性
设置和获取属性是通过 BeanWrapper
的 setPropertyValue
和 getPropertyValue
重载方法变体完成的。有关详细信息,请参见其 Javadoc。下表显示了这些约定的一些示例:
表达式 | 说明 |
---|---|
name | 表示与 getName() 或 isName() 和 setName(..) 方法相对应的 name 属性 。 |
account.name | 表示与 getAccount().setName() 或 getAccount().getName() 方法相对应的 account 属性的 name 嵌套属性。 |
account[2] | 表示索引属性的第三个元素 account 。索引属性可能是的 array ,list 或其它天然有序集合。 |
account[COMPANYNAME] | 表示account 这个 Map 由 COMPANYNAME 键索引的条目的值。 |
(如果您不打算直接使用 BeanWrapper
,那么下一部分对您而言并不是至关重要的。如果仅使用 DataBinder
和 BeanFactory
及其默认实现,则应跳到 PropertyEditors
的部分。)
以下两个示例类使用 BeanWrapper
来获取和设置属性:
1 | public class Company { |
1 | public class Employee { |
以下代码段显示了一些有关如何检索和操纵实例化的 Companies
和 Employees
的某些属性的示例:
1 | BeanWrapper company = new BeanWrapperImpl(new Company()); |
内置的 PropertyEditor
实现
pring 使用 PropertyEditor
的概念来实现对象和字符串之间的转换。以不同于对象本身的方式表示属性可能很方便。例如,日期可以用人类可读的方式表示(如字符串:”2007-14-09”),而我们仍然可以将人类可读的形式转换回原始日期(或者更好的是,转换任何日期以人类可读的形式输入到 Date
对象)。通过注册类型为 java.beans.PropertyEditor
的自定义编辑器,可以实现此行为。在 BeanWrapper
上或在特定的 IoC 容器中注册自定义编辑器(如上一章所述),使它具有如何将属性转换为所需类型的知识。有关 PropertyEditor
的更多信息,请参见 Oracle 的 java.beans 包的 javadoc。
在 Spring 中使用属性编辑的两个示例:
- 通过使用
PropertyEditor
实现在 bean 上设置属性。当使用String
作为在 XML 文件中声明的某个 bean 的属性的值时,Spring(如果相应属性的设置器具有Class
参数)将使用ClassEditor
尝试将参数解析为Class
对象。 - 在 Spring 的 MVC 框架中,通过使用各种
PropertyEditor
实现来解析 HTTP 请求参数,您可以在CommandController
的所有子类中手动绑定这些实现。
Spring 具有许多内置的 PropertyEditor
实现,以简化生活。它们都位于org.springframework.beans.propertyeditors
包中。默认情况下,大多数(但不是全部,如下表所示)由 BeanWrapperImpl
注册。如果可以通过某种方式配置属性编辑器,则仍可以注册自己的变体以覆盖默认变体。下表描述了 Spring 提供的各种 PropertyEditor
实现:
类 | 说明 |
---|---|
ByteArrayPropertyEditor |
字节数组的编辑器。将字符串转换为其相应的字节表示形式。默认情况下由 BeanWrapperImpl 注册。 |
ClassEditor |
将代表类的字符串解析为实际类,反之亦然。当找不到类时,将抛出 IllegalArgumentException 。默认情况下,由 BeanWrapperImpl 注册。 |
CustomBooleanEditor |
布尔属性的可定制属性编辑器。默认情况下,由 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 |
修剪字符串的属性编辑器。(可选)允许将空字符串转换为空值。默认情况下未注册——必须是用户注册的。 |
URLEditor |
可以将 URL 的字符串表示形式解析为实际的 URL 对象。默认情况下,由 BeanWrapperImpl 注册 |
Spring 使用 java.beans.PropertyEditorManager
设置可能需要的属性编辑器的搜索路径。搜索路径还包括 sun.bean.editors
,其中包括针对诸如 Font
,Color
和大多数基本类型的类型的 PropertyEditor
实现。 还要注意,如果标准 JavaBeans 基础结构与它们处理的类在同一包中,并且与该类具有相同的名称,并附加了 Editor,则标准 JavaBeans 基础结构将自动发现 PropertyEditor
类(无需显式注册它们)。例如,可能具有以下类和包结构,足以使 SomethingEditor
类被识别并用作 Something
类型的属性的 PropertyEditor
。
1 | com |
注意,您也可以在此处使用标准的 BeanInfo
JavaBeans 机制(在某种程度上进行了描述)。 以下示例使用 BeanInfo
机制使用关联类的属性显式注册一个或多个 PropertyEditor
实例:
1 | com |
所引用的 SomethingBeanInfo
类的以下 Java 源代码将 CustomNumberEditor
与 Something
类的 age
属性相关联:
1 | public class SomethingBeanInfo extends SimpleBeanInfo { |
注册其他自定义 PropertyEditor 实现
当将 bean 属性设置为字符串值时,Spring IoC 容器最终使用标准 JavaBeans PropertyEditor
实现将这些字符串转换为属性的复杂类型。Spring 预注册了许多自定义的 PropertyEditor
实现(例如,将表示为字符串的类名称转换为 Class
对象)。此外,Java 的标准 JavaBeans PropertyEditor
查找机制允许适当地命名类的 PropertyEditor
,并将其与提供支持的类放在同一包中,以便可以自动找到它。
如果需要注册其他自定义 PropertyEditor
,则可以使用几种机制。最手动的方法(通常不方便或不建议使用)是使用 ConfigurableBeanFactory
接口的 registerCustomEditor()
方法,并假设您有 BeanFactory
引用。另一种(稍微方便些)的机制是使用一种称为 CustomEditorConfigurer
的特殊 bean 工厂后处理器。尽管您可以将 Bean 工厂后处理器与 BeanFactory 实现一起使用,但 CustomEditorConfigurer
具有嵌套的属性设置,因此我们强烈建议您将其与 ApplicationContext
一起使用,在这里可以将其以与其他任何 Bean 相似的方式进行部署,并且可以在任何位置进行部署。自动检测并应用。
请注意,所有的 bean 工厂和应用程序上下文通过使用 BeanWrapper 来处理属性转换,都会自动使用许多内置的属性编辑器。上一节列出了 BeanWrapper 注册的标准属性编辑器。此外,ApplicationContext
还以适合特定应用程序上下文类型的方式重写或添加其他编辑器,以处理资源查找。
标准 JavaBeans PropertyEditor
实例用于将以字符串表示的属性值转换为该属性的实际复杂类型。您可以使用 bean 工厂的后处理器 CustomEditorConfigurer
来方便地将对其他 PropertyEditor
实例的支持添加到 ApplicationContext
。
考虑以下示例,该示例定义了一个名为 ExoticType
的用户类和另一个名为 DependsOnExoticType
的类,该类需要将 ExoticType
设置为属性:
1 | package example; |
正确设置之后,我们希望能够将 type 属性分配为字符串,PropertyEditor
会将其转换为实际的 ExoticType
实例。 以下 bean 定义显示了如何建立这种关系:
1 | <bean id="sample" class="example.DependsOnExoticType"> |
PropertyEditor
实现可能类似于以下内容:
1 | // converts string representation to ExoticType object |
Spring 类型转换
Spring 3 引入了 core.convert
包,该包提供了通用的类型转换系统。系统定义了一个用于实现类型转换逻辑的 SPI 和一个用于在运行时执行类型转换的 API。在 Spring 容器中,可以使用此系统作为 PropertyEditor
实现的替代方法,以将外部化的 bean 属性值字符串转换为所需的属性类型。 您还可以在应用程序中需要类型转换的任何地方使用公共 API。
转换器 SPI
如以下接口定义所示,用于实现类型转换逻辑的 SPI 非常简单且具有强类型:
1 | package org.springframework.core.convert.converter; |
要创建自己的转换器,请实现 Converter 接口并将 S 设置为要转换的类型,并将 T 设置为要转换的类型。如果还需要注册一个委托数组或集合转换器(默认情况下 DefaultConversionService 会这样做),则也可以透明地应用此类转换器,如果需要将 S 的集合或数组转换为 T 的数组或集合。
对于每次对 convert(S)的调用,保证源参数不为 null。如果转换失败,您的转换器可能会引发任何未经检查的异常。具体来说,它应该抛出 IllegalArgumentException 以报告无效的源值。注意确保您的 Converter 实现是线程安全的。
为了方便起见,在 core.convert.support 软件包中提供了几种转换器实现。这些包括从字符串到数字和其他常见类型的转换器。下面的清单显示了 StringToInteger 类,它是一个典型的 Converter 实现:
1 | package org.springframework.core.convert.support; |
使用 ConverterFactory
当需要集中整个类层次结构的转换逻辑时(例如,从 String 转换为 Enum 对象时),可以实现 ConverterFactory,如以下示例所示:
1 | package org.springframework.core.convert.converter; |
参数化 S 为您要转换的类型,参数化 R 为基类型,定义可以转换为的类的范围。 然后实现 getConverter(Class
以 StringToEnumConverterFactory 为例:
1 | package org.springframework.core.convert.support; |
使用 GenericConverter
当您需要复杂的 Converter
实现时,请考虑使用 GenericConverter
接口。 与 Converter
相比,GenericConverter
具有比 Converter
更灵活但强度不高的签名,支持在多种源类型和目标类型之间进行转换。此外,GenericConverter
使您可以在实现转换逻辑时使用可用的源字段和目标字段上下文。 这种上下文允许类型转换由字段注释或在字段签名上声明的通用信息驱动。 以下清单显示了 GenericConverter
的接口定义:
1 | package org.springframework.core.convert.converter; |
要实现 GenericConverter
,请让 getConvertibleTypes()
返回支持的源 → 目标类型对。 然后实现 convert(Object,TypeDescriptor,TypeDescriptor)
包含您的转换逻辑。 源 TypeDescriptor
提供对包含正在转换的值的源字段的访问。 使用目标 TypeDescriptor
,可以访问要设置转换值的目标字段。
GenericConverter
的一个很好的例子是在 Java 数组和集合之间进行转换的转换器。 这样的 ArrayToCollectionConverter
会对声明目标集合类型的字段进行内省,以解析集合的元素类型。 这样就可以在将集合设置到目标字段上之前,将源数组中的每个元素转换为集合元素类型。
由于
GenericConverter
是一个更复杂的 SPI 接口,因此仅应在需要时使用它。 支持Converter
或ConverterFactory
以满足基本的类型转换需求。
使用 ConditionalGenericConverter
有时,您希望 Converter 仅在满足特定条件时才运行。 例如,您可能只想在目标字段上存在特定注释时才运行 Converter,或者可能仅在目标类上定义了特定方法(例如静态 valueOf 方法)时才运行 Converter。 ConditionalGenericConverter 是 GenericConverter 和 ConditionalConverter 接口的联合,可让您定义以下自定义匹配条件:
1 | public interface ConditionalConverter { |
ConditionalGenericConverter
的一个很好的例子是 EntityConverter
,它在持久实体标识符和实体引用之间进行转换。仅当目标实体类型声明静态查找器方法(例如 findAccount(Long)
)时,此类 EntityConverter
才可能匹配。 您可以在 matchs(TypeDescriptor,TypeDescriptor)
的实现中执行这种 finder 方法检查。
ConversionService
API
ConversionService
定义了一个统一的 API,用于在运行时执行类型转换逻辑。转换器通常在以下外观接口后面执行:
1 | package org.springframework.core.convert; |
大多数 ConversionService
实现也都实现 ConverterRegistry
,该转换器提供用于注册转换器的 SPI。在内部,ConversionService
实现委派其注册的转换器执行类型转换逻辑。
core.convert.support
软件包中提供了一个强大的 ConversionService
实现。 GenericConversionService
是适用于大多数环境的通用实现。ConversionServiceFactory
提供了一个方便的工厂来创建通用的 ConversionService
配置。
注册一个 ConversionService
ConversionService
是无状态对象,旨在在应用程序启动时实例化,然后在多个线程之间共享。在 Spring 应用程序中,通常为每个 Spring 容器(或 ApplicationContext
)配置一个 ConversionService
实例。当框架需要执行类型转换时,Spring 会调用该 ConversionService
并使用它。 您还可以将此 ConversionService
注入到任何 bean 中,然后直接调用它。
如果未向 Spring 注册任何
ConversionService
,则使用原始的基于PropertyEditor
的系统。
要向 Spring 注册默认的 ConversionService
,请添加以下 bean 定义,其 id 为 conversionService
:
1 | <bean id="conversionService" |
默认的 ConversionService
可以在字符串,数字,枚举,集合,映射和其他常见类型之间进行转换。 要用您自己的自定义转换器补充或覆盖默认转换器,请设置 converters
属性。 属性值可以实现 Converter
,ConverterFactory
或 GenericConverter
接口中的任何一个。
1 | <bean id="conversionService" |
在 Spring MVC 应用程序中使用 ConversionService
也很常见。 参见 Spring MVC 一章中的转换和格式化。
在某些情况下,您可能希望在转换过程中应用格式设置。 有关使用 FormattingConversionServiceFactoryBean
的详细信息,请参见 FormatterRegistry
SPI。
使用 ConversionService 编码
要以编程方式使用 ConversionService 实例,可以像对其他任何 bean 一样注入对该实例的引用。 以下示例显示了如何执行此操作:
1 |
|
对于大多数用例,可以使用指定 targetType 的 convert 方法,但不适用于更复杂的类型,例如参数化元素的集合。 例如,如果要以编程方式将整数列表转换为字符串列表,则需要提供源类型和目标类型的正式定义。
幸运的是,如下面的示例所示,TypeDescriptor 提供了各种选项来使操作变得简单明了:
1 | DefaultConversionService cs = new DefaultConversionService(); |
请注意,DefaultConversionService
自动注册适用于大多数环境的转换器。 这包括集合转换器,标量转换器和基本的对象到字符串转换器。 您可以使用 DefaultConversionService
类上的静态 addDefaultConverters
方法向任何 ConverterRegistry
注册相同的转换器。
值类型的转换器可重用于数组和集合,因此,假设标准的集合处理适当,则无需创建特定的转换器即可将 S 的集合转换为 T 的集合。
Spring 字段格式
如上一节所述,core.convert
是一种通用类型转换系统。它提供了统一的 ConversionService
API 和强类型的 Converter
SPI,用于实现从一种类型到另一种类型的转换逻辑。 Spring 容器使用此系统绑定 bean 属性值。此外,Spring Expression Language(SpEL)和 DataBinder
都使用此系统绑定字段值。例如,当 SpEL 需要强制将 Short
转换为 Long
来完成 expression.setValue(Object bean, Object value)
尝试时,core.convert
系统将执行强制转换。
现在考虑典型客户端环境(例如 Web 或桌面应用程序)的类型转换要求。在这样的环境中,您通常会从 String
转换为支持客户端回发过程,然后又转换为 String
以支持视图渲染过程。另外,您通常需要本地化 String
值。更通用的 core.convert
Converter
SPI 不能直接满足此类格式化要求。为了直接解决这些问题,Spring 3 引入了方便的 Formatter
SPI,它为客户端环境提供了 PropertyEditor
实现的简单而强大的替代方案。
通常,当您需要实现通用类型转换逻辑时(例如,用于在 java.util.Date
和 Long
之间进行转换),可以使用 Converter
SPI。在客户端环境(例如 Web 应用程序)中工作并且需要解析和打印本地化的字段值时,可以使用 Formatter
SPI。 ConversionService
为两个 SPI 提供统一的类型转换 API。
Formatter SPI
用于实现字段格式化逻辑的 Formatter
SPI 非常简单且类型严格。 以下清单显示了 Formatter
接口定义:
1 | package org.springframework.format; |
Formatter
从 Printer
和 Parser
构建块接口扩展。 以下清单显示了这两个接口的定义:
1 | public interface Printer<T> { |
1 | import java.text.ParseException; |
要创建自己的 Formatter
,请实现前面显示的 Formatter
接口。将 T 参数化为您希望格式化的对象的类型(例如 java.util.Date
)。实现 print()
操作以打印 T 的实例以在客户端语言环境中显示。实现 parse()
操作,以从客户端语言环境返回的格式化表示形式解析 T 的实例。如果解析尝试失败,则 Formatter
应该抛出 ParseException
或 IllegalArgumentException
。注意确保您的 Formatter
实现是线程安全的。
format
子包为方便起见提供了几种 Formatter
实现。数字程序包提供 NumberStyleFormatter``,CurrencyStyleFormatter
和 PercentStyleFormatter
来格式化使用 java.text.NumberFormat
的 Number
对象。datetime
包提供了一个 DateFormatter
,用于使用 java.text.DateFormat
格式化 java.util.Date
对象。datetime.joda
包基于 Joda-Time 库提供了全面的日期时间格式支持。
以下 DateFormatter
是 Formatter
实现的示例:
1 | package org.springframework.format.datetime; |
Spring 团队欢迎社区推动的 Formatter
贡献。 请参阅 GitHub 问题以做出贡献。
注释驱动的格式
可以通过字段类型或注释配置字段格式。要将注释绑定到 Formatter
,请实现 AnnotationFormatterFactory
。以下显示了AnnotationFormatterFactory
接口的定义:
1 | package org.springframework.format; |
要创建一个实现:将 A 参数化为要与格式逻辑关联的字段注解类型,例如 org.springframework.format.annotation.DateTimeFormat
。让 getFieldTypes()
返回可在其上使用注释的字段类型。让 getPrinter()
返回 Printer
以打印带注释的字段的值。让 getParser()
返回解析器以解析带注释字段的 clientValue
。
以下示例 AnnotationFormatterFactory
实现将 @NumberFormat
批注绑定到格式化程序,以指定数字样式或模式:
1 | public final class NumberFormatAnnotationFormatterFactory |
要触发格式,可以使用 @NumberFormat
注释字段,如以下示例所示:
1 | public class MyModel { |
格式注释 API
org.springframework.format.annotation
包中存在一个可移植的格式注释 API。 您可以使用 @NumberFormat
格式化数字字段(例如 Double 和 Long),并使用 @DateTimeFormat
格式化 java.util.Date
,java.util.Calendar
,Long
(用于毫秒时间戳)以及 JSR-310 java.time
和 Joda-Time 值类型。
以下示例使用 @DateTimeFormat
将 java.util.Date
格式化为 ISO 日期(yyyy-MM-dd):
1 | public class MyModel { |
FormatterRegistry
SPI
FormatterRegistry
是用于注册格式器和转换器的 SPI。FormattingConversionService
是适用于大多数环境的 FormatterRegistry
的实现。 您可以通过编程方式或声明方式将此变体配置为 Spring Bean,例如通过使用 FormattingConversionServiceFactoryBean
。 由于此实现还实现了 ConversionService
,因此您可以直接将其配置为与 Spring 的 DataBinder
和 Spring 表达式语言(SpEL)一起使用。
1 | package org.springframework.format; |
如前面的清单所示,您可以按字段类型或批注注册格式化程序。
FormatterRegistry
SPI 使您可以集中配置格式设置规则,而不必在控制器之间复制此类配置。 例如,您可能要强制所有日期字段以某种方式设置格式或带有特定注释的字段以某种方式设置格式。 使用共享的 FormatterRegistry
,您可以一次定义这些规则,并在需要格式化时应用它们。
FormatterRegistrar
SPI
FormatterRegistrar
是一个 SPI,用于通过 FormatterRegistry
注册格式器和转换器。 以下清单显示了其接口定义:
1 | package org.springframework.format; |
为给定的格式类别(例如日期格式)注册多个相关的转换器和格式器时,FormatterRegistrar
很有用。 在声明式注册不足的情况下(例如,当格式化程序需要在不同于其自身 <T>
的特定字段类型下建立索引或注册 Printer
/Parser
对时),它也很有用。 下一节将提供有关转换器和格式化程序注册的更多信息。
在 Spring MVC 中配置格式
https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-config-conversion
配置全局日期和时间格式
默认情况下,未使用 @DateTimeFormat
注释的日期和时间字段是使用 DateFormat.SHORT
样式从字符串转换的。 如果愿意,可以通过定义自己的全局格式来更改此设置。
为此,请确保 Spring 不注册默认格式器。 相反,可以借助以下方法手动注册格式化程序:
org.springframework.format.datetime.standard.DateTimeFormatterRegistrar
org.springframework.format.datetime.DateFormatterRegistrar
或 joda-Time 的org.springframework.format.datetime.joda.JodaTimeFormatterRegistrar
。
例如,以下 Java 配置注册全局 yyyyMMdd 格式:
1 |
|
如果您喜欢基于 XML 的配置,则可以使用 FormattingConversionServiceFactoryBean。 以下示例显示了如何执行此操作(这次使用 Joda Time):
1 |
|
请注意,在 Web 应用程序中配置日期和时间格式时,还有其他注意事项。请参阅 WebMVC 转换和格式或 WebFlux 转换和格式。
Java Bean 验证
Spring 框架提供了对 Java Bean 验证 API 的支持。
Bean 验证概述
Bean 验证为 Java 应用程序提供了通过约束声明和元数据进行验证的通用方法。要使用它,您需要使用声明性验证约束对域模型属性进行注释,然后由运行时强制实施。有内置的约束,您也可以定义自己的自定义约束。
考虑以下示例,该示例显示了具有两个属性的简单 PersonForm
模型:
1 | public class PersonForm { |
Bean 验证使您可以声明约束,如以下示例所示:
1 | public class PersonForm { |
然后,Bean 验证验证器根据声明的约束来验证此类的实例。 有关该 API 的一般信息,请参见 Bean 验证。 有关特定限制,请参见 Hibernate Validator 文档。 要学习如何将 bean 验证提供程序设置为 Spring bean,请继续阅读。
配置 Bean 验证提供程序
Spring 提供了对 Bean 验证 API 的全面支持,包括将 Bean 验证提供程序作为 Spring Bean 进行引导。 这使您可以在应用程序中需要验证的任何地方注入 javax.validation.ValidatorFactory
或 javax.validation.Validator
。
您可以使用 LocalValidatorFactoryBean
将默认的 Validator
配置为 Spring Bean,如以下示例所示:
1 | import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; |
前面示例中的基本配置触发 Bean 验证以使用其默认引导机制进行初始化。 Bean 验证提供程序,例如 Hibernate Validator,应该存在于类路径中并被自动检测到。
注入验证器
LocalValidatorFactoryBean
同时实现 javax.validation.ValidatorFactory
和 javax.validation.Validator
以及 Spring 的 org.springframework.validation.Validator
。 您可以将对这些接口之一的引用注入需要调用验证逻辑的 bean 中。
如果您希望直接使用 Bean Validation API,则可以注入对 javax.validation.Validator
的引用,如以下示例所示:
1 | import javax.validation.Validator; |
如果您的 bean 需要使用 Spring Validation API,则可以注入对 org.springframework.validation.Validator
的引用,如以下示例所示:
1 | import org.springframework.validation.Validator; |
配置自定义约束
每个 bean 验证约束都包括两个部分:
@Constraint
批注,用于声明约束及其可配置属性。
javax.validation.ConstraintValidator
接口的实现,用于实现约束的行为。
要将声明与实现相关联,每个 @Constraint
批注都引用一个对应的 ConstraintValidator
实现类。 在运行时,当在域模型中遇到约束注释时,ConstraintValidatorFactory
实例化引用的实现。
默认情况下,LocalValidatorFactoryBean
配置一个 SpringConstraintValidatorFactory
,该工厂使用 Spring 创建 ConstraintValidator
实例。 这使您的自定义 ConstraintValidators
像其他任何 Spring bean 一样受益于依赖项注入。
以下示例显示了一个自定义 @Constraint
声明,后跟一个关联的 ConstraintValidator
实现,该实现使用 Spring 进行依赖项注入:
1 | ({ElementType.METHOD, ElementType.FIELD}) |
1 | import javax.validation.ConstraintValidator; |
如前面的示例所示,ConstraintValidator
实现可以像其他任何 Spring bean 一样具有其 @Autowired
依赖项。
Spring 驱动方法验证
您可以通过 MethodValidationPostProcessor
bean 定义将 Bean Validation 1.1(以及作为自定义扩展,还包括 Hibernate Validator 4.3)支持的方法验证功能集成到 Spring 上下文中:
1 | import org.springframework.validation.beanvalidation.MethodValidationPostProcessor; |
为了有资格进行 Spring 驱动的方法验证,所有目标类都必须使用 Spring 的 @Validated
注释进行注释,该注释也可以选择声明要使用的验证组。 有关使用 Hibernate Validator 和 Bean Validation 1.1 提供程序的设置详细信息,请参见 MethodValidationPostProcessor
。
方法验证依赖于目标类周围的 AOP 代理,即接口上方法的 JDK 动态代理或 CGLIB 代理。 代理的使用存在某些限制,《了解 AOP 代理》 中介绍了其中的一些限制。 另外,请记住在代理类上始终使用方法和访问器;直接现场字段将不起作用。
其他配置选项
在大多数情况下,默认 LocalValidatorFactoryBean
配置就足够了。 从消息插值到遍历解析,有许多用于各种 Bean 验证构造的配置选项。 有关这些选项的更多信息,请参见 LocalValidatorFactoryBean
Javadoc。
注册一个 DataBinder
从 Spring 3 开始,您可以使用 Validator 配置 DataBinder
实例。 配置完成后,您可以通过调用 binder.validate()
来调用 Validator
。任何验证 Errors
都会自动添加到活页夹的 BindingResult
中。
下面的示例演示如何在绑定到目标对象后,以编程方式使用 DataBinder
来调用验证逻辑:
1 | Foo target = new Foo(); |
您还可以通过 dataBinder.addValidators
和 dataBinder.replaceValidators
配置具有多个 Validator
实例的 DataBinder
。当将全局配置的 bean 验证与在 DataBinder
实例上本地配置的 Spring Validator 结合使用时,这很有用。 请参阅 Spring MVC 验证配置。
Spring MVC 3 Validation
参见 Spring MVC 中的验证。