Appearance
Spring 核心:验证、数据绑定和类型转换
Tip:基于 Spring Core 5.3.30 版本。
1. 使用 Spring 的 Validator 接口进行验证
Spring 提供了一个 Validator 接口,你可以使用它来验证对象。Validator 接口通过使用一个 Errors 对象来工作,这样,在验证过程中,验证器可以向 Errors 对象报告验证失败。
考虑以下一个小型数据对象的示例:
Java
public class Person {
private String name;
private int age;
// the usual getters and setters...
}1
2
3
4
5
6
2
3
4
5
6
下一个示例通过实现 org.springframework.validation.Validator 接口的以下两个方法,为 Person 类提供验证行为:
supports(Class):这个Validator能否验证提供的类的实例?validate(Object, org.springframework.validation.Errors):验证给定的对象,并在出现验证错误的情况下,将这些错误注册到给定的Errors对象中。
实现一个 Validator 是相当直接的,特别是当你知道 Spring 框架还提供了 ValidationUtils 辅助类。以下示例为 Person 实例实现了 Validator:
Java
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");
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ValidationUtils 类上的静态方法 rejectIfEmpty(..) 用于在 name 属性为 null 或空字符串时拒绝它。请查看 ValidationUtils 的 JavaDoc,看看除了前面显示的示例之外,它还提供了什么功能。
虽然完全可以实现一个单一的 Validator 类来验证一个丰富对象中的每一个嵌套对象,但是将每个嵌套类对象的验证逻辑封装在其自己的 Validator 实现中可能会更好。一个简单的 “丰富” 对象的例子就是 Customer,它由两个 String 属性(名和姓)和一个复杂的 Address 对象组成。Address 对象可能独立于 Customer 对象使用,因此已经实现了一个独立的 AddressValidator。如果你希望你的 CustomerValidator 重用 AddressValidator 类中的逻辑,而不是采取复制粘贴的方式,你可以在你的 CustomerValidator 中依赖注入或实例化一个 AddressValidator,如下面的示例所示:
Java
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();
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
验证错误会报告给传递给验证器的 Errors 对象。在 Spring Web MVC 的情况下,你可以使用 <spring:bind/> 标签来检查错误消息,但你也可以自己检查 Errors 对象。关于它提供的方法的更多信息可以在 JavaDoc 中找到。
2. 将代码解析为错误消息
我们已经介绍了数据绑定和验证。这一部分将介绍如何输出与验证错误对应的消息。在前面的部分中,我们拒绝了 name 和 age 字段。如果我们想要使用 MessageSource 输出错误消息,我们可以使用我们在拒绝字段时提供的错误代码(在这种情况下是 name 和 age)。当你调用(直接或间接,例如,使用 ValidationUtils 类)rejectValue 或 Errors 接口的其他拒绝方法时,底层实现不仅注册了你传入的代码,还注册了一些额外的错误代码。MessageCodesResolver 确定 Errors 接口注册哪些错误代码。默认情况下,使用 DefaultMessageCodesResolver,它(例如)不仅注册了你给出的代码的消息,还注册了包含你传递给 reject 方法的字段名的消息。所以,如果你使用 rejectValue("age", "too.darn.old") 拒绝一个字段,除了 too.darn.old 代码,Spring 还注册了 too.darn.old.age 和 too.darn.old.age.int(第一个包含字段名,第二个包含字段的类型)。这样做是为了方便开发人员定位错误消息。
关于 MessageCodesResolver 和默认策略的更多信息可以在 MessageCodesResolver 和 DefaultMessageCodesResolver 的 JavaDoc 中找到。
3. Bean 操作和 BeanWrapper
org.springframework.beans 包遵循 JavaBeans 标准。JavaBean 是一个具有默认无参数构造函数的类,该类遵循一种命名约定,例如,一个名为 bingoMadness 的属性将有一个 setBingoMadness(..) 的 Setter 方法和一个 getBingoMadness() 的 Getter 方法。有关 JavaBeans 和规范的更多信息,请参见 JavaBeans。
在 beans 包中,一个非常重要的类是 BeanWrapper 接口及其对应的实现(BeanWrapperImpl)。如 JavaDoc 所述,BeanWrapper 提供了设置和获取属性值(单个或批量)、获取属性描述符以及查询属性以确定它们是否可读或可写的功能。此外,BeanWrapper 还支持嵌套属性,使得可以在无限深度的子属性上设置属性。BeanWrapper 还支持添加标准的 JavaBeans PropertyChangeListeners 和 VetoableChangeListeners,而无需在目标类中支持代码。最后但同样重要的是,BeanWrapper 提供了设置索引属性的支持。BeanWrapper 通常不直接由应用程序代码使用,而是由 DataBinder 和 BeanFactory 使用。
BeanWrapper 的工作方式部分地由其名称所示:它包装一个 Bean 以对该 Bean 执行操作,例如设置和检索属性。
3.1. 设置和获取基本和嵌套属性
通过 BeanWrapper 的 setPropertyValue 和 getPropertyValue 重载方法变体来设置和获取属性。有关详细信息,请参见它们的 JavaDoc。下表显示了这些约定的一些示例:
| 表达式 | 释义 |
|---|---|
name | 表示与 getName() 或 isName() 和 setName(..) 方法对应的属性名 |
account.name | 表示例如与 getAccount().setName() 或 getAccount().getName() 方法对应的属性 account 的嵌套属性 name |
account[2] | 表示索引属性 account 的第三个元素。索引属性可以是数组、列表或其他自然有序的集合类型 |
account[COMPANYNAME] | 表示由 account Map 属性的 COMPANYNAME 键索引的 Map 条目的值 |
Note:如果你不打算直接使用
BeanWrapper,那么下一节的内容对你来说并不重要。如果你只使用DataBinder和BeanFactory及其默认实现,你应该直接跳到关于PropertyEditors的部分。
以下代码片段展示了如何检索和操作已实例化的 Company(s) 和 Employee(s) 的一些属性的示例:
Java
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;
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Java
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;
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Java
BeanWrapper company = new BeanWrapperImpl(new Company());
// setting the company name..
company.setPropertyValue("name", "Some Company Inc.");
// ... can also be done like this:
PropertyValue value = new PropertyValue("name", "Some Company Inc.");
company.setPropertyValue(value);
// ok, let's create the director and tie it to the company:
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");1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
3.2. 内置的 PropertyEditor 实现
Spring 使用 PropertyEditor 的概念来实现 Object 和 String 之间的转换。这种方式可以方便地以与对象本身不同的方式表示属性。例如,日期可以以人类可读的方式表示(如字符串:2007-14-09),同时我们仍然可以将人类可读的形式转换回原始日期(或者,更好的是,将任何以人类可读形式输入的日期转换回 Date 对象)。这种行为可以通过注册类型为 java.beans.PropertyEditor 的自定义编辑器来实现。在 BeanWrapper 上注册自定义编辑器,或者在特定的 IoC 容器中注册(如前一章所述),可以使其知道如何将属性转换为所需的类型。关于 PropertyEditor 的更多信息,请参阅 Oracle 的 java.beans 包的 JavaDoc。
以下是在 Spring 中使用属性编辑的一些示例:
在 Bean 上设置属性是通过使用
PropertyEditor实现来完成的。当你在 XML 文件中使用String声明某个 Bean 的属性值时,Spring(如果相应属性的 Setter 有一个Class参数)使用ClassEditor尝试将参数解析为Class对象。在 Spring 的 MVC 框架中解析 HTTP 请求参数是通过使用各种
PropertyEditor实现来完成的,你可以在CommandController的所有子类中手动绑定。
Spring 有许多内置的 PropertyEditor 实现来简化生活。它们都位于 org.springframework.beans.propertyeditors 包中。大多数(但并非所有,如下表所示)默认由 BeanWrapperImpl 注册。如果属性编辑器在某种方式上是可配置的,你仍然可以注册你自己的变体来覆盖默认的。以下表格描述了 Spring 提供的各种 PropertyEditor 实现:
| 类 | 释义 | 默认注册 |
|---|---|---|
ByteArrayPropertyEditor | 字节数组的编辑器。将字符串转换为其对应的字节表示 | √ |
ClassEditor | 将代表类的字符串解析为实际的类,反之亦然。当找不到类时,会抛出 IllegalArgumentException | √ |
CustomBooleanEditor | 可定制的 Boolean 属性编辑器 | √ |
CustomCollectionEditor | 集合的属性编辑器,将任何源 Collection 转换为给定的目标 Collection 类型 | √ |
CustomDateEditor | 可定制的 java.util.Date 属性编辑器,支持自定义的 DateFormat | × |
CustomNumberEditor | 可定制的任何 Number 子类的属性编辑器,如 Integer、Long、Float 或 Double | √ |
FileEditor | 将字符串解析为 java.io.File 对象 | √ |
InputStreamEditor | 单向属性编辑器,可以接受一个字符串并通过一个中间的 ResourceEditor 和 Resource 生成一个 InputStream,以便可以直接将 InputStream 属性设置为字符串。请注意,默认的使用方式不会为你关闭 InputStream | √ |
LocaleEditor | 可以将字符串解析为 Locale 对象,反之亦然(字符串格式为 [language]_[country]_[variant],与 Locale 的 toString() 方法相同)。也接受空格作为分隔符,作为下划线的替代 | √ |
PatternEditor | 可以将字符串解析为 java.util.regex.Pattern 对象,反之亦然 | √ |
PropertiesEditor | 可以将字符串(按照 java.util.Properties 类的 JavaDoc 中定义的格式格式化)转换为 Properties 对象 | √ |
StringTrimmerEditor | 修剪字符串的属性编辑器。可选地允许将空字符串转换为 null 值 | × |
URLEditor | 可以将 URL 的字符串表示解析为实际的 URL 对象 | √ |
PropertyEditor 实现Spring 使用 java.beans.PropertyEditorManager 来设置可能需要的属性编辑器的搜索路径。搜索路径还包括 sun.bean.editors,其中包括用于如 Font、Color 以及大多数原始类型的 PropertyEditor 实现。还要注意,标准的 JavaBeans 基础设施会自动发现 PropertyEditor 类(无需你显式注册),如果它们与它们处理的类在同一个包中,并且具有与该类名后附加了 Editor 的名称相同。例如,可以有以下的类和包结构,这将足以让 SomethingEditor 类被识别并用作 Something 类型属性的 PropertyEditor。
Text
com
chank
pop
Something
SomethingEditor // the PropertyEditor for the Something class1
2
3
4
5
2
3
4
5
请注意,你也可以在这里使用标准的 BeanInfo JavaBeans 机制(在这里有一定程度的描述)。以下示例使用 BeanInfo 机制显式地将一个或多个 PropertyEditor 实例注册到关联类的属性中:
Text
com
chank
pop
Something
SomethingBeanInfo // the BeanInfo for the Something class1
2
3
4
5
2
3
4
5
以下是引用的 SomethingBeanInfo 类的 Java 源代码,它将 CustomNumberEditor 与 Something 类的 age 属性关联起来:
Java
public class SomethingBeanInfo extends SimpleBeanInfo {
public PropertyDescriptor[] getPropertyDescriptors() {
try {
final PropertyEditor numberPE = new CustomNumberEditor(Integer.class, true);
PropertyDescriptor ageDescriptor = new PropertyDescriptor("age", Something.class) {
@Override
public PropertyEditor createPropertyEditor(Object bean) {
return numberPE;
}
};
return new PropertyDescriptor[] { ageDescriptor };
}
catch (IntrospectionException ex) {
throw new Error(ex.toString());
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
3.2.1. 注册额外的自定义 PropertyEditor 实现
当将 Bean 属性设置为字符串值时,Spring IoC 容器最终使用标准的 JavaBeans PropertyEditor 实现来将这些字符串转换为属性的复杂类型。Spring 预注册了一些自定义 PropertyEditor 实现(例如,将以字符串表示的类名转换为 Class 对象)。此外,Java 的标准 JavaBeans PropertyEditor 查找机制允许一个类的 PropertyEditor 被适当地命名并放置在与其提供支持的类相同的包中,以便可以自动找到。
如果需要注册其他自定义 PropertyEditors,有几种可用的机制。最手动的方法(通常不方便,也不推荐)是使用 ConfigurableBeanFactory 接口的 registerCustomEditor() 方法,假设你有一个 BeanFactory 引用。另一个(稍微更方便的)机制是使用一个特殊的 Bean 工厂后处理器,称为 CustomEditorConfigurer。虽然你可以在 BeanFactory 实现中使用 Bean 工厂后处理器,但 CustomEditorConfigurer 有一个嵌套的属性设置,所以我们强烈建议你在 ApplicationContext 中使用它,你可以像部署任何其他 Bean 一样部署它,而且它可以被自动检测和应用。
请注意,所有的 Bean 工厂和应用上下文都通过使用 BeanWrapper 来处理属性转换,自动使用了一些内置的属性编辑器。BeanWrapper 注册的标准属性编辑器在前一节中列出。此外,ApplicationContexts 还会覆盖或添加额外的编辑器,以适合特定的应用上下文类型处理资源查找。
标准的 JavaBeans PropertyEditor 实例用于将以字符串表示的属性值转换为属性的实际复杂类型。你可以使用 CustomEditorConfigurer,一个 Bean 工厂后处理器,方便地向 ApplicationContext 添加对额外 PropertyEditor 实例的支持。
考虑以下示例,它定义了一个用户类 ExoticType 和另一个类 DependsOnExoticType,需要将 ExoticType 设置为属性:
Java
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;
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
当事情正确设置后,我们希望能够将 type 属性作为一个字符串赋值,然后 PropertyEditor 将其转换为一个实际的 ExoticType 实例。以下的 Bean 定义展示了如何建立这种关系:
XML
<bean id="sample" class="example.DependsOnExoticType">
<property name="type" value="aNameForExoticType"/>
</bean>1
2
3
2
3
PropertyEditor 的实现可能类似于以下内容:
Java
// converts string representation to ExoticType object
package example;
public class ExoticTypeEditor extends PropertyEditorSupport {
public void setAsText(String text) {
setValue(new ExoticType(text.toUpperCase()));
}
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
最后,以下示例展示了如何使用 CustomEditorConfigurer 将新的 PropertyEditor 注册到 ApplicationContext 中,然后可以根据需要使用它:
XML
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="example.ExoticType" value="example.ExoticTypeEditor"/>
</map>
</property>
</bean>1
2
3
4
5
6
7
2
3
4
5
6
7
3.2.1.1. 使用 PropertyEditorRegistrar
在 Spring 容器中注册属性编辑器的另一种机制是创建并使用 PropertyEditorRegistrar。当你需要在几种不同的情况下使用相同的属性编辑器集时,这个接口特别有用。你可以编写一个相应的注册器并在每个情况中重用它。PropertyEditorRegistrar 实例与一个名为 PropertyEditorRegistry 的接口一起工作,这个接口由 Spring 的 BeanWrapper(和 DataBinder)实现。当与 CustomEditorConfigurer 一起使用时,PropertyEditorRegistrar 实例特别方便,它暴露了一个名为 setPropertyEditorRegistrars(..) 的属性。以这种方式添加到 CustomEditorConfigurer 的 PropertyEditorRegistrar 实例可以轻松地与 DataBinder 和 Spring MVC 控制器共享。此外,它避免了在自定义编辑器上的同步需求:预期 PropertyEditorRegistrar 会为每次 Bean 创建尝试创建新的 PropertyEditor 实例。
以下示例展示了如何创建你自己的 PropertyEditorRegistrar 实现:
Java
package com.foo.editors.spring;
public final class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {
public void registerCustomEditors(PropertyEditorRegistry registry) {
// it is expected that new PropertyEditor instances are created
registry.registerCustomEditor(ExoticType.class, new ExoticTypeEditor());
// you could register as many custom property editors as are required here...
}
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
也可以参考 org.springframework.beans.support.ResourceEditorRegistrar,这是一个 PropertyEditorRegistrar 实现的示例。注意在其 registerCustomEditors(..) 方法的实现中,它创建了每个属性编辑器的新实例。
下一个示例展示了如何配置 CustomEditorConfigurer 并将我们的 CustomPropertyEditorRegistrar 实例注入其中:
XML
<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"/>1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
最后(这有点偏离本章的重点),对于那些使用 Spring 的 MVC Web 框架的人来说,将 PropertyEditorRegistrar 与数据绑定 Web 控制器结合使用可能非常方便。以下示例在 @InitBinder 方法的实现中使用了 PropertyEditorRegistrar:
Java
@Controller
public class RegisterUserController {
private final PropertyEditorRegistrar customPropertyEditorRegistrar;
RegisterUserController(PropertyEditorRegistrar propertyEditorRegistrar) {
this.customPropertyEditorRegistrar = propertyEditorRegistrar;
}
@InitBinder
void initBinder(WebDataBinder binder) {
this.customPropertyEditorRegistrar.registerCustomEditors(binder);
}
// other methods related to registering a User
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
这种 PropertyEditor 注册的方式可以使代码更简洁(@InitBinder 方法的实现只有一行长),并且可以将常见的 PropertyEditor 注册代码封装在一个类中,然后在需要的控制器之间共享。
4. Spring 类型转换
Spring 3 引入了一个 core.convert 包,该包提供了一个通用的类型转换系统。该系统定义了一个 SPI 来实现类型转换逻辑,以及一个在运行时执行类型转换的 API。在 Spring 容器中,你可以使用这个系统作为 PropertyEditor 实现的替代品,将外部化的 Bean 属性值字符串转换为所需的属性类型。你也可以在应用程序中需要类型转换的任何地方使用公共 API。
4.1. 转换器 SPI
实现类型转换逻辑的 SPI 是简单且强类型的,如下面的接口定义所示:
Java
package org.springframework.core.convert.converter;
public interface Converter<S, T> {
T convert(S source);
}1
2
3
4
5
2
3
4
5
要创建你自己的转换器,实现 Converter 接口,并将 S 参数化为你要转换的类型,将 T 参数化为你要转换到的类型。如果需要将 S 的集合或数组转换为 T 的数组或集合,你也可以透明地应用这样的转换器,前提是已经注册了一个委托数组或集合转换器(DefaultConversionService 默认就是这样做的)。
对于每次调用 convert(S),源参数保证不为 null。如果转换失败,你的 Converter 可能会抛出任何未检查的异常。特别地,它应该抛出 IllegalArgumentException 来报告无效的源值。请确保你的 Converter 实现是线程安全的。
在 core.convert.support 包中提供了几个转换器实现,以方便使用。这些包括从字符串到数字和其他常见类型的转换器。以下列表显示了 StringToInteger 类,这是一个典型的 Converter 实现:
Java
package org.springframework.core.convert.support;
final class StringToInteger implements Converter<String, Integer> {
public Integer convert(String source) {
return Integer.valueOf(source);
}
}1
2
3
4
5
6
7
2
3
4
5
6
7
4.2. 使用 ConverterFactory
当你需要集中处理整个类层次结构的转换逻辑时(例如,从 String 转换到 Enum 对象),你可以实现 ConverterFactory,如下例所示:
Java
package org.springframework.core.convert.converter;
public interface ConverterFactory<S, R> {
<T extends R> Converter<S, T> getConverter(Class<T> targetType);
}1
2
3
4
5
2
3
4
5
将 S 参数化为你要转换的类型,将 R 参数化为你可以转换到的类的基类型。然后实现 getConverter(Class<T>),其中 T 是 R 的子类。
以 StringToEnumConverterFactory 为例:
Java
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());
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
4.3. 使用 GenericConverter
当你需要一个复杂的 Converter 实现时,可以考虑使用 GenericConverter 接口。与 Converter 相比,GenericConverter 具有更灵活但类型签名较弱的特性,支持在多个源类型和目标类型之间进行转换。此外,GenericConverter 提供了源字段和目标字段的上下文,你可以在实现转换逻辑时使用这些上下文。这样的上下文允许类型转换由字段注解或字段签名上声明的泛型信息驱动。以下列表显示了 GenericConverter 的接口定义:
Java
package org.springframework.core.convert.converter;
public interface GenericConverter {
public Set<ConvertiblePair> getConvertibleTypes();
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}1
2
3
4
5
6
7
2
3
4
5
6
7
要实现 GenericConverter,需要让 getConvertibleTypes() 返回支持的源目标类型对。然后实现 convert(Object, TypeDescriptor, TypeDescriptor) 来包含你的转换逻辑。源 TypeDescriptor 提供了对保存正在转换的值的源字段的访问。目标 TypeDescriptor 提供了对将设置转换值的目标字段的访问。
GenericConverter 的一个好例子是一个在 Java 数组和集合之间进行转换的转换器。这样的 ArrayToCollectionConverter 会内省声明目标集合类型的字段以解析集合的元素类型。这让源数组中的每个元素在集合设置到目标字段之前都可以转换为集合元素类型。
Note:因为
GenericConverter是一个更复杂的 SPI 接口,你应该只在需要时使用它。对于基本的类型转换需求,应优先使用Converter或ConverterFactory。
4.3.1. 使用 ConditionalGenericConverter
有时,你可能希望只有在满足特定条件时才运行 Converter。例如,你可能只想在目标字段上存在特定注解时运行 Converter,或者你可能只想在目标类上定义了特定方法(如静态的 valueOf 方法)时运行 Converter。ConditionalGenericConverter 是 GenericConverter 和 ConditionalConverter 接口的联合,让你可以定义这样的自定义匹配条件:
Java
public interface ConditionalConverter {
boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}
public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
}1
2
3
4
5
6
2
3
4
5
6
ConditionalGenericConverter 的一个好例子是 IdToEntityConverter,它在持久实体标识符和实体引用之间进行转换。这样的 IdToEntityConverter 可能只在目标实体类型声明了静态查找方法(例如,findAccount(Long))时匹配。你可能会在 matches(TypeDescriptor, TypeDescriptor) 的实现中执行这样的查找方法检查。
4.4. ConversionService API
ConversionService 定义了一个统一的 API,用于在运行时执行类型转换逻辑。转换器通常在以下外观接口后面运行:
Java
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);
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
大多数 ConversionService 实现也实现了 ConverterRegistry,它提供了一个 SPI 用于注册转换器。在内部,ConversionService 的实现会委托给其注册的转换器来执行类型转换逻辑。
在 core.convert.support 包中提供了一个健壮的 ConversionService 实现。GenericConversionService 是适用于大多数环境的通用实现。ConversionServiceFactory 提供了一个方便的工厂用于创建常见的 ConversionService 配置。
4.5. 配置 ConversionService
ConversionService 是一个无状态的对象,设计为在应用程序启动时实例化,然后在多个线程之间共享。在 Spring 应用程序中,你通常为每个 Spring 容器(或 ApplicationContext)配置一个 ConversionService 实例。Spring 会获取该 ConversionService 并在框架需要执行类型转换时使用它。你也可以将这个 ConversionService 注入到你的任何 Bean 中并直接调用它。
Tip:如果没有在 Spring 中注册
ConversionService,那么将使用原始的基于PropertyEditor的系统。
要在 Spring 中注册默认的 ConversionService,请添加以下带有 conversionService id 的 Bean 定义:
XML
<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean"/>1
2
2
ConversionServiceFactoryBean 会创建一个 DefaultConversionService 实例,该 ConversionService 可以在字符串、数字、枚举、集合、映射以及其他常见类型之间进行转换。要使用你自己的自定义转换器来补充或覆盖默认的转换器,可以设置 converters 属性。属性值可以实现 Converter、ConverterFactory 或 GenericConverter 接口中的任何一个。
XML
<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="example.MyCustomConverter"/>
</set>
</property>
</bean>1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
在 Spring MVC 应用程序中使用 ConversionService 也是很常见的。请参阅 Spring MVC 章节中的转换和格式化。
在某些情况下,你可能希望在转换过程中应用格式化。有关使用 FormattingConversionServiceFactoryBean 的详细信息,请参阅 FormatterRegistry SPI。
4.6. 以编程方式使用 ConversionService
要以编程方式使用 ConversionService 实例,你可以像对待其他任何 Bean 一样注入对它的引用。以下示例展示了如何做到这一点:
Java
@Service
public class MyService {
public MyService(ConversionService conversionService) {
this.conversionService = conversionService;
}
public void doIt() {
this.conversionService.convert(...)
}
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
对于大多数使用场景,你可以使用指定 targetType 的 convert 方法,但是它不能处理更复杂的类型,例如参数化元素的集合。例如,如果你想以编程方式将 Integer 的 List 转换为 String 的 List,你需要提供源类型和目标类型的正式定义。
幸运的是,TypeDescriptor 提供了各种选项,使得这样做变得直接明了,如下例所示:
Java
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)));1
2
3
4
5
6
2
3
4
5
6
请注意,DefaultConversionService 会自动注册适用于大多数环境的转换器。这包括集合转换器、标量转换器以及基本的 Object-to-String 转换器。你可以使用 DefaultConversionService 类上的静态方法 addDefaultConverters 在任何 ConverterRegistry 中注册相同的转换器。
对于值类型的转换器会被数组和集合复用,所以没有必要创建一个特定的转换器来从 S 的 Collection 转换为 T 的 Collection,假设标准的集合处理是适当的。
5. Spring 字段格式化
如前一节所述,core.convert 是一个通用的类型转换系统。它提供了一个统一的 ConversionService API,以及一个强类型的 Converter SPI,用于实现从一种类型到另一种类型的转换逻辑。Spring 容器使用这个系统来绑定 Bean 属性值。此外,Spring 表达式语言(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 实现的替代方案。
总的来说,当你需要实现通用的类型转换逻辑时,你可以使用 Converter SPI,例如,用于在 java.util.Date 和 Long 之间进行转换。当你在客户端环境(如 Web 应用程序)工作并需要解析和打印本地化的字段值时,你可以使用 Formatter SPI。ConversionService 为这两个 SPI 提供了统一的类型转换 API。
5.1. 格式化器 SPI
实现字段格式化逻辑的 Formatter SPI 是简单且强类型的。以下列表显示了 Formatter 接口定义:
Java
package org.springframework.format;
public interface Formatter<T> extends Printer<T>, Parser<T> {
}1
2
3
4
2
3
4
Formatter 是从 Printer 和 Parser 这两个构建块接口扩展出来的。以下列表显示了这两个接口的定义:
Java
public interface Printer<T> {
String print(T fieldValue, Locale locale);
}1
2
3
2
3
Java
import java.text.ParseException;
public interface Parser<T> {
T parse(String clientValue, Locale locale) throws ParseException;
}1
2
3
4
5
2
3
4
5
要创建你自己的 Formatter,请实现前面显示的 Formatter 接口。将 T 参数化为你希望格式化的对象类型,例如 java.util.Date。实现 print() 操作以打印 T 的实例,以便在客户端区域设置中显示。实现 parse() 操作,从客户端区域设置返回的格式化表示中解析 T 的实例。如果解析尝试失败,你的 Formatter 应该抛出 ParseException 或 IllegalArgumentException。请确保你的 Formatter 实现是线程安全的。
format 子包提供了几个 Formatter 实现以方便使用。number 包提供了 NumberStyleFormatter、CurrencyStyleFormatter 和 PercentStyleFormatter 来格式化使用 java.text.NumberFormat 的 Number 对象。datetime 包提供了一个 DateFormatter 来格式化 java.util.Date 对象,使用 java.text.DateFormat。
以下的 DateFormatter 是一个 Formatter 实现的示例:
Java
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;
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Spring 团队欢迎社区驱动的 Formatter 贡献。请查看 GitHub Issues 进行贡献。
5.2. 注解驱动的格式化
字段格式化可以通过字段类型或注解进行配置。要将注解绑定到 Formatter,请实现 AnnotationFormatterFactory。以下列表显示了 AnnotationFormatterFactory 接口的定义:
Java
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);
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
要创建一个实现:
将
A参数化为你希望与格式化逻辑关联的字段注解类型,例如org.springframework.format.annotation.DateTimeFormat。让
getFieldTypes()返回可以使用注解的字段类型。让
getPrinter()返回一个Printer来打印带注解字段的值。让
getParser()返回一个Parser来解析带注解字段的clientValue。
以下示例 AnnotationFormatterFactory 实现将 @NumberFormat 注解绑定到一个格式化器,以便指定数字样式或模式:
Java
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();
}
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
要触发格式化,你可以使用 @NumberFormat 注解字段,如下例所示:
Java
public class MyModel {
@NumberFormat(style=Style.CURRENCY)
private BigDecimal decimal;
}1
2
3
4
2
3
4
5.2.1. 格式注解 API
在 org.springframework.format.annotation 包中存在一个便携式的格式注解 API。你可以使用 @NumberFormat 来格式化 Number 字段,如 Double 和 Long,并使用 @DateTimeFormat 来格式化 java.util.Date、java.util.Calendar、Long(用于毫秒时间戳)以及 JSR-310 java.time。
以下示例使用 @DateTimeFormat 将 java.util.Date 格式化为 ISO 日期(yyyy-MM-dd):
Java
public class MyModel {
@DateTimeFormat(iso=ISO.DATE)
private Date date;
}1
2
3
4
2
3
4
5.3. FormatterRegistry SPI
FormatterRegistry 是一个用于注册格式化器和转换器的 SPI。FormattingConversionService 是适合大多数环境的 FormatterRegistry 实现。你可以以编程方式或声明方式配置这个变体作为 Spring Bean,例如,通过使用 FormattingConversionServiceFactoryBean。因为这个实现也实现了 ConversionService,你可以直接配置它以供 Spring 的 DataBinder 和 Spring 表达式语言(SpEL)使用。
以下列表显示了 FormatterRegistry SPI:
Java
package org.springframework.format;
public interface FormatterRegistry extends ConverterRegistry {
void addPrinter(Printer<?> printer);
void addParser(Parser<?> parser);
void addFormatter(Formatter<?> formatter);
void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter);
void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser);
void addFormatterForFieldAnnotation(AnnotationFormatterFactory<? extends Annotation> annotationFormatterFactory);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
如前面的列表所示,你可以按字段类型或注解注册格式化器。
FormatterRegistry SPI 允许你集中配置格式化规则,而不是在你的控制器中复制这样的配置。例如,你可能希望强制所有日期字段以某种方式格式化,或者带有特定注解的字段以某种方式格式化。有了共享的 FormatterRegistry,你只需定义一次这些规则,每当需要格式化时,它们就会被应用。
5.4. FormatterRegistrar SPI
FormatterRegistrar 是一个通过 FormatterRegistry 注册格式化器和转换器的 SPI。以下列表显示了它的接口定义:
Java
package org.springframework.format;
public interface FormatterRegistrar {
void registerFormatters(FormatterRegistry registry);
}1
2
3
4
5
2
3
4
5
FormatterRegistrar 在为给定的格式化类别注册多个相关的转换器和格式化器时非常有用,例如日期格式化。在声明式注册不足的地方,它也可能很有用,例如,当一个格式化器需要在与其自身 <T> 不同的特定字段类型下进行索引,或者在注册 Printer/Parser 对时。下一节将提供更多关于转换器和格式化器注册的信息。
5.5. 在 Spring MVC 中配置格式化
请参阅 Spring MVC 章节中的转换和格式化。
6. 配置全局日期和时间格式
默认情况下,未使用 @DateTimeFormat 注解的日期和时间字段会使用 DateFormat.SHORT 样式从字符串转换。如果你愿意,你可以通过定义你自己的全局格式来改变这一点。
要做到这一点,确保 Spring 不注册默认的格式化器。相反,借助以下工具手动注册格式化器:
org.springframework.format.datetime.standard.DateTimeFormatterRegistrarorg.springframework.format.datetime.DateFormatterRegistrar
例如,以下 Java 配置注册了一个全局的 yyyyMMdd 格式:
Java
@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 JSR-310 date conversion with a specific global format
DateTimeFormatterRegistrar dateTimeRegistrar = new DateTimeFormatterRegistrar();
dateTimeRegistrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"));
dateTimeRegistrar.registerFormatters(conversionService);
// Register date conversion with a specific global format
DateFormatterRegistrar dateRegistrar = new DateFormatterRegistrar();
dateRegistrar.setFormatter(new DateFormatter("yyyyMMdd"));
dateRegistrar.registerFormatters(conversionService);
return conversionService;
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
如果你更喜欢基于 XML 的配置,你可以使用 FormattingConversionServiceFactoryBean。以下示例展示了如何做到这一点:
XML
<?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
https://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.standard.DateTimeFormatterRegistrar">
<property name="dateFormatter">
<bean class="org.springframework.format.datetime.standard.DateTimeFormatterFactoryBean">
<property name="pattern" value="yyyyMMdd"/>
</bean>
</property>
</bean>
</set>
</property>
</bean>
</beans>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
请注意,在 Web 应用程序中配置日期和时间格式时,需要考虑额外的因素。请参阅 WebMVC 转换和格式化或 WebFlux 转换和格式化。
7. Java Bean Validation
7.1. Bean Validation 概述
Bean Validation 为 Java 应用程序提供了一种通过约束声明和元数据进行验证的通用方式。要使用它,你需要使用声明性验证约束来注解领域模型的属性,然后这些约束将由运行时进行强制执行。有内置的约束,你也可以定义自己的自定义约束。
考虑以下示例,它展示了一个简单的 PersonForm 模型,该模型有两个属性:
Java
public class PersonForm {
private String name;
private int age;
}1
2
3
4
2
3
4
Bean Validation 允许你声明约束,如下面的示例所示:
Java
public class PersonForm {
@NotNull
@Size(max=64)
private String name;
@Min(0)
private int age;
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
然后,Bean Validation 验证器会根据声明的约束验证此类的实例。有关 API 的一般信息,请参阅 Bean Validation。有关特定约束,请参阅 Hibernate Validator 文档。要了解如何将 Bean 验证提供者设置为 Spring Bean,请继续阅读。
7.2. 配置 Bean Validation 提供程序
Spring 为 Bean Validation API 提供了全面的支持,包括将 Bean Validation 提供者作为 Spring Bean 的引导(Bootstrapping)。这使你可以在应用程序中需要验证的任何地方注入 javax.validation.ValidatorFactory 或 javax.validation.Validator。
你可以使用 LocalValidatorFactoryBean 来配置一个默认的 Validator 作为 Spring Bean,如下面的示例所示:
Java
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
@Configuration
public class AppConfig {
@Bean
public LocalValidatorFactoryBean validator() {
return new LocalValidatorFactoryBean();
}
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
前面示例中的基本配置通过使用其默认的引导机制触发 Bean 验证的初始化。预期 Bean Validation 提供者(如 Hibernate 验证器)会出现在类路径中,并会被自动检测到。
7.2.1. 注入 Validator
LocalValidatorFactoryBean 实现了 javax.validation.ValidatorFactory 和 javax.validation.Validator,以及 Spring 的 org.springframework.validation.Validator。你可以将这些接口的任何一个引用注入到需要调用验证逻辑的 Bean 中。
如果你更喜欢直接使用 Bean Validation API,你可以注入一个 javax.validation.Validator 的引用,如下面的示例所示:
Java
import javax.validation.Validator;
@Service
public class MyService {
@Autowired
private Validator validator;
}1
2
3
4
5
6
7
2
3
4
5
6
7
如果你的 Bean 需要 Spring 验证 API,你可以注入一个 org.springframework.validation.Validator 的引用,如下面的示例所示:
Java
import org.springframework.validation.Validator;
@Service
public class MyService {
@Autowired
private Validator validator;
}1
2
3
4
5
6
7
2
3
4
5
6
7
7.2.2. 配置自定义约束
每个 Bean 验证约束都包含两部分:
一个
@Constraint注解,它声明了约束及其可配置的属性。一个实现了
javax.validation.ConstraintValidator接口的实现,它实现了约束的行为。
为了将声明与实现关联起来,每个 @Constraint 注解都引用了一个对应的 ConstraintValidator 实现类。在运行时,当在你的领域模型中遇到约束注解时,ConstraintValidatorFactory 会实例化引用的实现。
默认情况下,LocalValidatorFactoryBean 配置了一个 SpringConstraintValidatorFactory,它使用 Spring 来创建 ConstraintValidator 实例。这使你的自定义 ConstraintValidator 可以像任何其他 Spring Bean 一样从依赖注入中受益。
以下示例显示了一个自定义的 @Constraint 声明,后面跟着一个使用 Spring 进行依赖注入的关联 ConstraintValidator 实现:
Java
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy=MyConstraintValidator.class)
public @interface MyConstraint {
}1
2
3
4
5
2
3
4
5
Java
import javax.validation.ConstraintValidator;
public class MyConstraintValidator implements ConstraintValidator {
@Autowired;
private Foo aDependency;
// ...
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
如前面的示例所示,ConstraintValidator 的实现可以像任何其他 Spring Bean 一样,将其依赖项通过 @Autowired 注入。
7.2.3. Spring 驱动的方法验证
你可以通过 MethodValidationPostProcessor Bean 的定义,将 Bean Validation 1.1(以及作为自定义扩展,也由 Hibernate Validator 4.3)支持的方法验证功能集成到 Spring 上下文中:
Java
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
@Configuration
public class AppConfig {
@Bean
public MethodValidationPostProcessor validationPostProcessor() {
return new MethodValidationPostProcessor();
}
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
要想有资格进行 Spring 驱动的方法验证,所有目标类都需要用 Spring 的 @Validated 注解进行注解,该注解也可以选择性地声明要使用的验证组。有关使用 Hibernate 验证器和 Bean Validation 1.1 提供者进行设置的详细信息,请参阅 MethodValidationPostProcessor。
Note:方法验证依赖于目标类周围的 AOP 代理,可以是接口上的方法的 JDK 动态代理,也可以是 CGLIB 代理。使用代理有一些限制,其中一些在理解 AOP 代理中有描述。此外,记住始终在被代理的类上使用方法和访问器,直接的字段访问将不会工作。
7.2.4. 其他配置选项
对于大多数情况,LocalValidatorFactoryBean 的默认配置就足够了。对于各种 Bean Validation 构造,有许多配置选项,从消息插值到遍历解析。有关这些选项的更多信息,请参阅 LocalValidatorFactoryBean 的 JavaDoc。
7.3. 配置 DataBinder
自 Spring 3 以来,你可以使用 Validator 配置一个 DataBinder 实例。一旦配置好,你可以通过调用 binder.validate() 来调用 Validator。任何验证 Errors 都会自动添加到 binder 的 BindingResult 中。
以下示例展示了如何在绑定到目标对象后,以编程方式使用 DataBinder 调用验证逻辑:
Java
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();1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
你也可以通过 dataBinder.addValidators 和 dataBinder.replaceValidators 用多个 Validator 实例配置 DataBinder。当将全局配置的 Bean 验证与在 DataBinder 实例上本地配置的 Spring Validator 结合使用时,这是非常有用的。请参阅 Spring MVC 验证配置。
7.4. Spring MVC 3 验证
请参阅 Spring MVC 章节中的验证部分。