本篇太乱,请移步:
Spring Framework 官方文档学习(四)之Validation、Data Binding、Type Conversion(一)
写了删删了写,反复几次,对自己的描述很不满意,表达能力还是不行啊。另外,格式太花了,很不喜欢。
前提
1、什么是JavaBean?
简单类,无参构造,有SETTER/GETTER,其对应的字段称为属性。–其实还有isXxx,用于布尔类型的属性,略。
详见 https://en.wikipedia.org/wiki/JavaBeans
注意,这里的JavaBean不是Spring的bean。
好了,现在已经有了属性(property)的概念,下面所有的东西都是围绕它来进行的。
2、需求
我们应该明白一点,所有的技术都是源自需求而出现的(不严谨)。搞明白需求就搞明白了它们的应用场景。
假定我们有一个桌面应用(下文用 app 代替 桌面应用),需要你输入生日,然后该app会输出你的年龄。看起来很简单吧,app的后台逻辑只要用当前日期减去你的生日即可,但是,现实不是这样子的。
问题一,我们输入的永远是字符串,字符串需要转成日期格式才能被我们的app用使用。–对应 类型转换
问题二,我们输入的字符串转成的日期怎么给app后台逻辑使用? –对应 数据绑定
问题三,人的年龄是有限制的,不能为负数,不能太大(例如超过了200)。 –对应 校验
看到这里,你应该已经搞明白Validation、Data Binding、Type Conversion三者之间的关系了。
同样的问题也出现在浏览器与服务器的交互之中,因为请求与响应,大都是被解析成字符串。
简介
1、什么是Validation?
Validation 就是对属性的值进行校验。–【谁的属性?JavaBean的!】
例如,User的属性age,我的预期是[0, 100],其他所有值都是非法的,那就校验一下好了。
Spring提供了Validator
接口,能在应用的任意layer使用,来完成Validation的工作。
2、什么是数据绑定?
数据绑定就是将数据绑定到属性上!–【谁的属性?JavaBean的!】
两种方法,通过对象调用SETTER,或者,通过反射调用对象的SETTER,二者都是给属性设值。–谁的对象?JavaBean的!
例如,user.setAge(20)。或者通过反射 — 在各种框架中常用。
Spring提供了DataBinder
来做具体的Data binding工作。
注意:Validator
和DataBinder
构成了validation
包。
3、什么是类型转换?
将一个类型的对象转成另一个类型的对象,例如String和Date互转等。 –【谁的类型?属性的!】
Spring提供了PropertyEditor
以及core.convert 包和format 包。后两者是Spring 3 引入的,可以看作PropertyEditor
的替代品,更简单。
注意:PropertyEditor
概念是 JavaBeans specification 的一部分。
注意上面【】中的内容,这三者其实都是在操作JavaBean的属性。那么,问题又来了,Spring用什么来操作JavaBean的属性?答案是BeanWrapper
,这是一个接口,Spring还提供了一个实现BeanWrapperImpl
。但是,这个东西是很底层的概念,用户一般不必直接使用它,了解即可。Spring的 DataBinder
和底层 BeanWrapper
都是使用 PropertyEditor
s 来解析和格式化属性值。Spring 3 引入了 core.convert 包和format 包,可以看作是 PropertyEditor
的替代品,更简单。稍后谈。 至此,我们已经对Spring的Validation、Data Binding、Type Conversion有了大致的了解,现在可以进入正文了。来看点具体的东西。
正文
1、使用Validator接口进行Validation
使用Spring的Validator
接口进行校验,该接口只有两个方法,support(..)用于判断是否支持某类型,validate(..)则进行校验。Validator
接口形参是一个Errors
对象,当校验时,可以将失败结果报告给该对象。用户可以通过 implement 该接口来自定义Validator。另外,还有一个ValidationUtils的工具类。 下面通过一个例子来学习一下如何自定义Validator:
public class Person { private String name;
private int age; // the usual getters and setters...略
}
public class PersonValidator implements Validator { public boolean supports(Class clazz) {
return Person.class.equals(clazz); // 仅支持Person类
} public void validate(Object obj, Errors e) {
ValidationUtils.rejectIfEmpty(e, "name", "name.empty");
Person p = (Person) obj;
if (p.getAge() < 0) { // 年龄不能小于0
e.rejectValue("age", "negativevalue");
} else if (p.getAge() > 110) { // 年龄不能大于110
e.rejectValue("age", "too.darn.old");
}
}
}
上面的代码一目了然,实现Validator
接口,完成两个方法即可。
如果是复合类(包含其他类字段),还可以注入相应的Validator — 复用,高效。
2、错误码与错误消息(暂略)
上面提到了数据绑定和校验,还有一件事需要注意,这就是校验的错误信息的输出。 使用MessageSource来输出error msg?(没看懂,暂略)http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#validation-conversion MessageCodesResolver
DefaultMessageCodesResolver
3、Bean操作 和 BeanWrapper
org.springframework.beans 包遵守Oracle提供的JavaBean标准。 beans包中一个非常重要的类是BeanWrapper
接口和其实现 BeanWrapperImpl
。顾名思义,BeanWrapper
就是封装一个bean,对其进行操作,例如set/get property。支持嵌套属性等特点,详见 table 9.1 。BeanWrapper
支持添加标准JavaBean PropertyChangeListeners
and VetoableChangeListeners
的能力,无须在目标类中编码。(监听器)但是,一般不直接用在应用的代码中,而是用在 the DataBinder
and the BeanFactory
中。
3.1、set/get属性
示例如下:
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");
3.2、内建的PropertyEditor实现
Spring使用PropertyEditor来实现 an Object
and a String
之间的转换。– 注意,是对象和字符串之间的转换,不是任意类型间的转换。有时候,以不同形式描述对象中的properties很有用。例如,一个Date
可以用人类可读的方式来描述。这可以通过注册自定义的java.beans.PropertyEditor类型的editor来实现(–卧槽,刚意识到这不是Spring的东西),嗯,注册在BeanWrapper
上或者IoC容器中。详见Javadoc。 两个使用PropertyEditor
的例子:① 你在xml中定义的bean,其class属性实际上是通过 ClassEditor
来转换成相应的类。② Spring MVC中解析各种HTTP请求的参数,也是通过各种PropertyEditors
来完成的。 Spring提供了大量的PropertyEditors
,它们都在org.springframework.beans.propertyeditors 包中。它们的多数,默认已经被BeanWrapperImpl注册了。当然,你也可以注册自己的变体来覆盖默认的:
Table 9.2. Built-in PropertyEditors
Class | Explanation |
---|---|
|
Editor for byte arrays. Strings will simply be converted to their corresponding byte representations. Registered by default by |
|
Parses Strings representing classes to actual classes and the other way around. When a class is not found, an |
|
Customizable property editor for |
|
Property editor for Collections, converting any source |
|
Customizable property editor for java.util.Date, supporting a custom DateFormat. NOT registered by default. Must be user registered as needed with appropriate format.默认没注册! |
|
Customizable property editor for any Number subclass like |
|
Capable of resolving Strings to |
|
One-way property editor, capable of taking a text string and producing (via an intermediate |
|
Capable of resolving Strings to |
|
Capable of resolving Strings to |
|
Capable of converting Strings (formatted using the format as defined in the javadocs of the |
|
Property editor that trims Strings. Optionally allows transforming an empty string into a |
|
Capable of resolving a String representation of a URL to an actual |
Spring 使用 java.beans.PropertyEditorManager 来设置搜索路径,以查找需要的property editors。该路径也包含了sun.bean.editors,– 这里有针对Font
、Color
以及大多数基本类型的PropertyEditor
的实现。注意,① 标准JavaBean设施会自动发现PropertyEditor
类(不需要显式的注册它们)–如果它们和它们处理的类在相同包下,且其名字以被其处理类的名字加上“Editor”的话。例如:
com
chank
pop
Foo
FooEditor // the PropertyEditor for the Foo class
注意,② 还可以使用标准BeanInfo
JavaBean机制。下例就使用了该机制来显式注册相关类属性的一个或多个PropertyEditor
实例。(使用已有的editor)
com
chank
pop
Foo
FooBeanInfo // the BeanInfo for the Foo class
public class FooBeanInfo extends SimpleBeanInfo { public PropertyDescriptor[] getPropertyDescriptors() {
try {
final PropertyEditor numberPE = new CustomNumberEditor(Integer.class, true);
PropertyDescriptor ageDescriptor = new PropertyDescriptor("age", Foo.class) {
public PropertyEditor createPropertyEditor(Object bean) {
return numberPE;
};
};
return new PropertyDescriptor[] { ageDescriptor };
}
catch (IntrospectionException ex) {
throw new Error(ex.toString());
}
}
}
3.2.1、注册额外定制的PropertyEditor
① 最笨且最不推荐的方法:使用 ConfigurableBeanFactory 接口的 registerCustomEditor() 方法,前提是拥有BeanFactory引用。② 稍微方便点的方法:使用一个特殊的bean factory post-processor — CustomEditorConfigurer。虽然可以在 BeanFactory
实现中使用,但更建议在ApplicationContext中使用。注意,所有 bean factories和application contexts都会自动应用大量的内建property editors — 通过BeanWrapper
来处理property conversions。(前面有提到,BeanWrapperImpl
会自动注册一些内建的editors)。此外,具体的ApplicationContexts
还会覆盖或者添加额外的editors。(这句很重要) 例子,假定有两个类:
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;
}
}
下面就会调用幕后的PropertyEditor
–注意,这里的value是String,后台editor会将其转成 ExoticType类型。
<bean id="sample" class="example.DependsOnExoticType">
<property name="type" value="aNameForExoticType"/>
</bean>
该editor实现大概类似这样:
// 将String转成ExoticType对象
package example; public class ExoticTypeEditor extends PropertyEditorSupport { public void setAsText(String text) {
setValue(new ExoticType(text.toUpperCase()));
}
}
然后,就到了最后一步,也是这里的主题,使用CustomEditorConfigurer
将新的PropertyEditor
注册到ApplicationContext。
如下:
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="example.ExoticType" value="example.ExoticTypeEditor"/>
</map>
</property>
</bean>
③ 使用PropertyEditorRegistrars,需要手动创建它。在复用时很有用,因为它打包了一组editor,拿来即用。(听起来,是类似map或者set之类的集合??)
提醒,下面这段话可能比较绕,建议略过,直接看代码,简洁明了。
PropertyEditorRegistrars
配合接口 PropertyEditorRegistry 使用。这个接口被BeanWrapper(还有DataBinder)实现了。PropertyEditorRegistrars
配合CustomEditorConfigurer
使用时特别方便,后者有一个方法setPropertyEditorRegistrars(..),以这种方式添加到CustomEditorConfigurer
中的PropertyEditorRegistrars
可以轻松的共享给DataBinder
和Spring MVC Controllers。 Furthermore, it avoids the need for synchronization on custom editors:每个bean创建时,PropertyEditorRegistrar
都会创建新的PropertyEditor
实例。 通过示例来学习最简单。首先,创建你的PropertyEditorRegistrar
实现:
package com.foo.editors.spring; public final class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar { public void registerCustomEditors(PropertyEditorRegistry registry) { // 需要PropertyEditor实例
registry.registerCustomEditor(ExoticType.class, new ExoticTypeEditor()); // 可以注册任意多的PropertyEditor...
}
}
插一句,org.springframework.beans.support.ResourceEditorRegistrar 也是一个实现,可以参考下。
其次,配置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"/>
最后,在使用Spring MVC框架时,使用PropertyEditorRegistrars
配合data-binding Controllers(如SimpleFormController)会是非常方便的(–暂时不明白,以后再来看吧)。见下例:
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
注册能简洁代码(the implementation of initBinder(..)
is just one line long!),且允许通用的PropertyEditor
注册代码包含在一个类中–然后由所有需要的Controllers
共享。
4、Spring 类型转换
http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#core-convertSpring 3 引入了core.convert 包,提供了通用的类型转换系统。该系统定义了一个SPI — 实现了类型转换逻辑,还定义了一个API — 用于在runtime执行类型转换。在Spring容器内,该系统可以用作PropertyEditors 的替代品来转换外部bean property value strings 到需要的property 类型。插一句,SPI 是 Service Provider Interface,与API的区别见 difference-between-spi-and-api 。
4.1、Converter SPI
很简单:
package org.springframework.core.convert.converter; public interface Converter<S, T> { T convert(S source); }
想要创建自己的converter,实现这个接口即可。
该converter还能用于将一个S类型的集合(或数组)转成T类型的集合(或数组),前提是已经注册了一个代理数组/集合converter(DefaultConversionService
默认已注册了)。 在core.convert.support 包中以提供了几个converter实现。它们包括从String到Number和其他常见类型的转换。 来看一下StringToInteger
:
package org.springframework.core.convert.support; final class StringToInteger implements Converter<String, Integer> { public Integer convert(String source) {
return Integer.valueOf(source);
} }
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); }
例子:
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());
}
}
}
4.3、GenericConverter(略)
4.4、ConditionalGenericConverter(略)
4.5、ConversionService API 转换服务API
ConversionService定义了一个统一的API以执行类型转换逻辑,是一个facade(门面)接口。
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); }
从上面的代码可以推理,该接口的实现必定是先判断能否转换,如能转换再调用相应的converter进行转换。–问题,从哪调用converter?ConverterRegistry!
所以多数 ConversionService的实现 也实现了 ConverterRegistry,它提供了一个 SPI 以注册 converters 。在内部,一个ConversionService的实现委托注册的converters来执行类型转换逻辑。
在core.convert.support 包中提供了一个健全的实现。GenericConversionService
是一个通用的实现,适用于多数环境。ConversionServiceFactory
提供了一个便捷的工厂以创建常见的ConversionService配置。
4.6、Configuring a ConversionService 配置
一个ConversionService是一个无状态的对象,被设计成在应用启动时被实例化,然后在多个线程之间共享。在一个Spring应用中,可以给每个容器配置一个ConversionService。Spring会加载并调用它。你也可以注入需要的地方,直接调用它。注意:如果没有ConversionService被注册,会使用原始的基于PropertyEditor的系统。 注册一个默认的ConversionService(id不可修改):
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"/>
注意,ConversionServiceFactoryBean的Javadoc说返回的conversionService是DefaultConversionService 实例。
默认的ConversionService可以在strings、numbers、enums、collections、maps以及其他常用类型之间进行转换。
要补充或者覆盖默认的转换器,需要设置converters
属性。其值可以是Converter、ConverterFactory或者GenericConverter的实现。
<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="example.MyCustomConverter"/>
</set>
</property>
</bean>
个人心得:
①、非web项目的Spring应用,不会自动注册ConversionService bean。(web项目暂时没测)
②、@Configuration class 中进行ConversionService bean定义时,有几个选择:使用其实现,如GenericConversionService、DefaultConversionService 或者其他;使用ConversionServiceFactoryBean。区别在于,GenericConversionService 没有注册converters,DefaultConversionService 注册了很多converters,ConversionServiceFactoryBean 则提供了DefaultConversionService。– 所以,一般情况下,直接使用ConversionServiceFactoryBean 即可。如下:
/**
* This implementation creates a DefaultConversionService.
* Subclasses may override createConversionService() in order to return a GenericConversionService instance of their choosing.
*
* 这个可以取代上面的DefaultConversionService,基本一致。
*
* @return
*/
@Bean
public ConversionServiceFactoryBean conversionService(){
return new ConversionServiceFactoryBean();
}
另,
①通常也可以在Spring MVC应用中使用ConversionService。待后续。See Section 22.16.3, “Conversion and Formatting” in the Spring MVC chapter.②在特定环境下,可能希望在转换时应用格式。See Section 9.6.3, “FormatterRegistry SPI” for details on using FormattingConversionServiceFactoryBean
.
4.7、编码使用ConversionService
注入使用即可。
@Service
public class MyService { @Autowired
public MyService(ConversionService conversionService) {
this.conversionService = conversionService;
} public void doIt() {
this.conversionService.convert(...)
}
}
多数情况下不能用于复合类型,如集合。例如,如果需要编码转换List<Integer>到List<String>,需要提供源类型和目标类型的正式定义。幸运的是,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
自动注册的converters,可以适用于大多数环境。其中包含了collection converters、scalar converters,以及基本的Object到String的converter。同样的converters也可以使用DefaultConversionService
的static addDefaultConverters
注册到任意ConverterRegistry
中。 值类型的转换器,会被自动复用到值类型的array和collection中,不必再去创建。 — 值类型这个概念,应该就是基本类型吧。
5、Spring Field Formatting (Spring字段格式化)
上面有提到, core.convert
是一个通用目的的类型转换系统。提供了统一的ConversionService API和强类型的Converter SPI,以实现转换逻辑。Spring容器使用该系统来绑定bean property values。此外,SpEL 和 DataBinder 使用该系统绑定 field values。例如,当SpEL需要强制 a Short
to a Long
来完成expression.setValue(Object bean, Object value)尝试时,core.convert系统负责执行该强制。———————————回顾结束,开始新内容——————————– 现在来考虑下典型客户环境的类型转换需求,例如web或desktop应用。在该环境下,一般是String和其他互转。此外,你还经常需要本地化String values。但通用的core.convert Converter SPI不能直接完成格式化需求。为了直接完成格式化需求,Spring 3 引入了便捷的 Formatter SPI,其提供了PropertyEditors的简单直接的替换 — 用于client环境。 总的来说,通用目的的类型转换逻辑时,使用Converter SPI;在client环境下工作,且需要解析和输出本地化 field values 时,使用Formatter SPI。 — ConversionService 为二者都提供了统一的类型转换 API。个人体会:一个是粗粒度的,一个是细粒度的。
5.1、Formatter SPI
package org.springframework.format; public interface Formatter<T> extends Printer<T>, Parser<T> {
}
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接口即可。注意异常和线程安全。format
包的子包中提供了几个Formatter的实现。number,datetime,datetime.joda。参考DateFormatter
。
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;
} }
5.2、注解驱动的Formatting
绑定一个注解到一个formatter,实现 AnnotationFormatterFactory 即可。详见这里。略。
5.2.1、Format Annotation API
便携的格式化注解API存在于org.springframework.format.annotation包中。Use @NumberFormat to format java.lang.Number fields. Use @DateTimeFormat to format java.util.Date, java.util.Calendar, java.util.Long, or Joda Time fields.例子:
public class MyModel { @DateTimeFormat(iso=ISO.DATE)
private Date date; }
5.3、FormatterRegistry SPI
这是一个SPI,用于注册formatters和converters。其实现FormattingConversionService
适用于多数环境。该实现可以编码式配置或者声明式配置,通过FormattingConversionServiceFactoryBean。另外,其还实现了ConversionService,所以可以配置和DataBinder、SpEL一起使用。http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#format-FormatterRegistry-SPI
5.4、FormatterRegistrar SPI
通过FormatterRegistry注册formatters和converters。 — 蛋疼,两个词分不清。http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#format-FormatterRegistrar-SPI
6、配置全局 date & time 格式
http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#format-configuring-formatting-globaldatetimeformat默认,如果没有使用注解@DateTimeFormat,会使用 DateFormat.SHORT 风格。当然,你可以定义自己的全局格式。需要确保Spring没有注册默认的formatters,然后手动注册所有的formatters。使用 DateFormat.SHORT 或 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;
}
}
注意,如果使用Spring MVC,记住,需要显式的配置使用的conversion service。对于基于Java的@Configuration来说,这意味着继承WebMvcConfigurationSupport
并重写mvcConversionService()。对于XML来说,应该使用<mvc:annotation-driven>元素的’conversion-service’属性。See Section 22.16.3, “Conversion and Formatting” for details.
7、Spring Validation
http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#validation-beanvalidationSpring 3 引入了validation support的几个增强。第一,完整的支持JSR-303 Bean Validation API。第二,编码式使用时,Spring的DataBinder现在可以validate objects,也可以bind to them。第三,Spring MVC现在支持声明式校验@Controller的输入。
7.1、JSR-303 Bean Validation API概览
允许定义声明式校验限制。如:
public class PersonForm { @NotNull
@Size(max=64)
private String name; @Min(0)
private int age; }
For general information on JSR-303/JSR-349, see the Bean Validation website. For information on the specific capabilities of the default reference implementation, see the Hibernate Validator documentation.
7.2、配置一个Bean Validation Provider
Spring提供了对Bean Validation API的完整的支持。包括启动其实现(provider),并将其作为Spring的bean。所以,需要validation时,可以注入a javax.validation.ValidatorFactory
or javax.validation.Validator
。使用LocalValidatorFactoryBean
来配置默认的Validator:
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
上面,会引动Bean Validation使用其自身的启动机制来初始化。如果在classpath中有JSR-303/JSR-349 provider,如Hibernate Validator,会被自动探测到。
7.2.1、注入一个Validator
LocalValidatorFactoryBean
implements both javax.validation.ValidatorFactory
and javax.validation.Validator
, as well as Spring’sorg.springframework.validation.Validator
. 可以注入这几个接口的任意一个引用来使用。①如果倾向于使用Bean Validation API,直接注入javax.validation.Validator
:
import javax.validation.Validator; @Service
public class MyService { @Autowired
private Validator validator; }
②如果倾向于使用Spring Validation API,可以注入org.springframework.validation.Validator:
import org.springframework.validation.Validator; @Service
public class MyService { @Autowired
private Validator validator; }
7.2.2、配置自定义限制(Constraints)
每个Bean Validation constraint都由两部分组成。首先,一个@Constraint注解,声明了the constraint和其可配置的properties。其次,javax.validation.ConstraintValidator接口的实现,实现了the constraint’s behavior。为了将声明与实现关联起来,每个@Constraint注解引用了一个相关的ValidationConstraint实现类。在运行时,当一个ConstraintValidatorFactory在你的domain model中遇到constraint 注解时会实例化被引用的实现。 默认的,LocalValidatorFactoryBean
配置了一个SpringConstraintValidatorFactory
,其使用Spring来创建ConstraintValidator实例。下面是一个例子,一个定制的 @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; ...
}
As you can see, a ConstraintValidator implementation may have its dependencies @Autowired like any other Spring bean.
7.2.3、Spring-driven Method Validation
方法验证,由Bean Validation 1.1 支持,作为一个自定义的扩展同样也被Hibernate Validator 4.3支持,通过一个 MethodValidationPostProcessor
bean定义,它可以被集成到Spring context中。
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>
为了支持Spring-driven method validation,所有的目标类都需要使用Spring的@Validated注解,可以选择声明校验组。详见MethodValidationPostProcessor的javadoc。
7.2.4、更多配置选项(略)
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也可以配置多个Validator实例 — 通过dataBinder.addValidators和dataBinder.replaceValidators。