Spring Framework 官方文档学习(四)之Validation、Data Binding、Type Conversion
本篇太乱,请移步:
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 的一部分。
BeanWrapper
,这是一个接口,Spring还提供了一个实现BeanWrapperImpl
。但是,这个东西是很底层的概念,用户一般不必直接使用它,了解即可。DataBinder
和底层 BeanWrapper
都是使用 PropertyEditor
s 来解析和格式化属性值。PropertyEditor
的替代品,更简单。稍后谈。 正文
1、使用Validator接口进行Validation
Validator
接口进行校验,该接口只有两个方法,support(..)用于判断是否支持某类型,validate(..)则进行校验。Validator
接口形参是一个Errors
对象,当校验时,可以将失败结果报告给该对象。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、错误码与错误消息(暂略)
MessageCodesResolver
DefaultMessageCodesResolver
3、Bean操作 和 BeanWrapper
BeanWrapper
接口和其实现 BeanWrapperImpl
。BeanWrapper
支持添加标准JavaBean PropertyChangeListeners
and VetoableChangeListeners
的能力,无须在目标类中编码。(监听器)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实现
Object
and a String
之间的转换。-- 注意,是对象和字符串之间的转换,不是任意类型间的转换。Date
可以用人类可读的方式来描述。这可以通过注册自定义的java.beans.PropertyEditor类型的editor来实现(--卧槽,刚意识到这不是Spring的东西),嗯,注册在BeanWrapper
上或者IoC容器中。详见Javadoc。PropertyEditor
的例子:ClassEditor
来转换成相应的类。PropertyEditors
来完成的。 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 |
Font
、Color
以及大多数基本类型的PropertyEditor
的实现。PropertyEditor
类(不需要显式的注册它们)--如果它们和它们处理的类在相同包下,且其名字以被其处理类的名字加上“Editor”的话。1 com 2 chank 3 pop 4 Foo 5 FooEditor // the PropertyEditor for the Foo class
注意,② 还可以使用标准BeanInfo
JavaBean机制。下例就使用了该机制来显式注册相关类属性的一个或多个PropertyEditor
实例。(使用已有的editor)
1 com 2 chank 3 pop 4 Foo 5 FooBeanInfo // the BeanInfo for the Foo class
1 public class FooBeanInfo extends SimpleBeanInfo { 2 3 public PropertyDescriptor[] getPropertyDescriptors() { 4 try { 5 final PropertyEditor numberPE = new CustomNumberEditor(Integer.class, true); 6 PropertyDescriptor ageDescriptor = new PropertyDescriptor("age", Foo.class) { 7 public PropertyEditor createPropertyEditor(Object bean) { 8 return numberPE; 9 }; 10 }; 11 return new PropertyDescriptor[] { ageDescriptor }; 12 } 13 catch (IntrospectionException ex) { 14 throw new Error(ex.toString()); 15 } 16 } 17 }
3.2.1、注册额外定制的PropertyEditor
BeanFactory
实现中使用,但更建议在ApplicationContext中使用。BeanWrapper
来处理property conversions。(前面有提到,BeanWrapperImpl
会自动注册一些内建的editors)。此外,具体的ApplicationContexts
还会覆盖或者添加额外的editors。(这句很重要)1 package example; 2 3 public class ExoticType { 4 5 private String name; 6 7 public ExoticType(String name) { 8 this.name = name; 9 } 10 } 11 12 public class DependsOnExoticType { 13 14 private ExoticType type; 15 16 public void setType(ExoticType type) { 17 this.type = type; 18 } 19 }
下面就会调用幕后的PropertyEditor
--注意,这里的value是String,后台editor会将其转成 ExoticType类型。
<bean id="sample" class="example.DependsOnExoticType"> <property name="type" value="aNameForExoticType"/> </bean>
该editor实现大概类似这样:
1 // 将String转成ExoticType对象 2 package example; 3 4 public class ExoticTypeEditor extends PropertyEditorSupport { 5 6 public void setAsText(String text) { 7 setValue(new ExoticType(text.toUpperCase())); 8 } 9 }
然后,就到了最后一步,也是这里的主题,使用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
实现:1 package com.foo.editors.spring; 2 3 public final class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar { 4 5 public void registerCustomEditors(PropertyEditorRegistry registry) { 6 7 // 需要PropertyEditor实例 8 registry.registerCustomEditor(ExoticType.class, new ExoticTypeEditor()); 9 10 // 可以注册任意多的PropertyEditor... 11 } 12 }
插一句,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)会是非常方便的(--暂时不明白,以后再来看吧)。见下例:
1 public final class RegisterUserController extends SimpleFormController { 2 3 private final PropertyEditorRegistrar customPropertyEditorRegistrar; 4 5 public RegisterUserController(PropertyEditorRegistrar propertyEditorRegistrar) { 6 this.customPropertyEditorRegistrar = propertyEditorRegistrar; 7 } 8 9 protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception { 10 this.customPropertyEditorRegistrar.registerCustomEditors(binder); 11 } 12 13 // other methods to do with registering a User 14 }
这种风格的PropertyEditor
注册能简洁代码(the implementation of initBinder(..)
is just one line long!),且允许通用的PropertyEditor
注册代码包含在一个类中--然后由所有需要的Controllers
共享。
4、Spring 类型转换
4.1、Converter SPI
1 package org.springframework.core.convert.converter; 2 3 public interface Converter<S, T> { 4 5 T convert(S source); 6 7 }
想要创建自己的converter,实现这个接口即可。
DefaultConversionService
默认已注册了)。StringToInteger
:1 package org.springframework.core.convert.support; 2 3 final class StringToInteger implements Converter<String, Integer> { 4 5 public Integer convert(String source) { 6 return Integer.valueOf(source); 7 } 8 9 }
4.2、ConverterFactory
1 package org.springframework.core.convert.converter; 2 3 public interface ConverterFactory<S, R> { 4 5 <T extends R> Converter<S, T> getConverter(Class<T> targetType); 6 7 }
例子:
1 package org.springframework.core.convert.support; 2 3 final class StringToEnumConverterFactory implements ConverterFactory<String, Enum> { 4 5 public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) { 6 return new StringToEnumConverter(targetType); 7 } 8 9 private final class StringToEnumConverter<T extends Enum> implements Converter<String, T> { 10 11 private Class<T> enumType; 12 13 public StringToEnumConverter(Class<T> enumType) { 14 this.enumType = enumType; 15 } 16 17 public T convert(String source) { 18 return (T) Enum.valueOf(this.enumType, source.trim()); 19 } 20 } 21 }
4.3、GenericConverter(略)
4.4、ConditionalGenericConverter(略)
4.5、ConversionService API 转换服务API
1 package org.springframework.core.convert; 2 3 public interface ConversionService { 4 // 判断,能否将源类型转成目标类型 5 boolean canConvert(Class<?> sourceType, Class<?> targetType); 6 // 转换 7 <T> T convert(Object source, Class<T> targetType); 8 // 判断 9 boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType); 10 // 转换 11 Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType); 12 13 }
从上面的代码可以推理,该接口的实现必定是先判断能否转换,如能转换再调用相应的converter进行转换。--问题,从哪调用converter?ConverterRegistry!
所以多数 ConversionService的实现 也实现了 ConverterRegistry,它提供了一个 SPI 以注册 converters 。在内部,一个ConversionService的实现委托注册的converters来执行类型转换逻辑。
GenericConversionService
是一个通用的实现,适用于多数环境。ConversionServiceFactory
提供了一个便捷的工厂以创建常见的ConversionService配置。4.6、Configuring a ConversionService 配置
1 <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"/>
注意,ConversionServiceFactoryBean的Javadoc说返回的conversionService是DefaultConversionService 实例。
默认的ConversionService可以在strings、numbers、enums、collections、maps以及其他常用类型之间进行转换。
converters
属性。其值可以是Converter、ConverterFactory或者GenericConverter的实现。1 <bean id="conversionService" 2 class="org.springframework.context.support.ConversionServiceFactoryBean"> 3 <property name="converters"> 4 <set> 5 <bean class="example.MyCustomConverter"/> 6 </set> 7 </property> 8 </bean>
个人心得:
①、非web项目的Spring应用,不会自动注册ConversionService bean。(web项目暂时没测)
②、@Configuration class 中进行ConversionService bean定义时,有几个选择:使用其实现,如GenericConversionService、DefaultConversionService 或者其他;使用ConversionServiceFactoryBean。区别在于,GenericConversionService 没有注册converters,DefaultConversionService 注册了很多converters,ConversionServiceFactoryBean 则提供了DefaultConversionService。-- 所以,一般情况下,直接使用ConversionServiceFactoryBean 即可。如下:
1 /** 2 * This implementation creates a DefaultConversionService. 3 * Subclasses may override createConversionService() in order to return a GenericConversionService instance of their choosing. 4 * 5 * 这个可以取代上面的DefaultConversionService,基本一致。 6 * 7 * @return 8 */ 9 @Bean 10 public ConversionServiceFactoryBean conversionService(){ 11 return new ConversionServiceFactoryBean(); 12 }
另,
FormattingConversionServiceFactoryBean
.4.7、编码使用ConversionService
1 @Service 2 public class MyService { 3 4 @Autowired 5 public MyService(ConversionService conversionService) { 6 this.conversionService = conversionService; 7 } 8 9 public void doIt() { 10 this.conversionService.convert(...) 11 } 12 }
TypeDescriptor
提供了多种选项,使其变得简单直接:1 DefaultConversionService cs = new DefaultConversionService(); 2 3 List<Integer> input = .... 4 cs.convert(input, 5 TypeDescriptor.forObject(input), // List<Integer> type descriptor 6 TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class)));
DefaultConversionService
自动注册的converters,可以适用于大多数环境。其中包含了collection converters、scalar converters,以及基本的Object到String的converter。同样的converters也可以使用DefaultConversionService
的static addDefaultConverters
注册到任意ConverterRegistry
中。5、Spring Field Formatting (Spring字段格式化)
core.convert
是一个通用目的的类型转换系统。提供了统一的ConversionService API和强类型的Converter SPI,以实现转换逻辑。Spring容器使用该系统来绑定bean property values。Short
to a Long
来完成expression.setValue(Object bean, Object value)尝试时,core.convert系统负责执行该强制。5.1、Formatter SPI
1 package org.springframework.format; 2 3 public interface Formatter<T> extends Printer<T>, Parser<T> { 4 }
1 public interface Printer<T> { 2 String print(T fieldValue, Locale locale); 3 }
1 import java.text.ParseException; 2 3 public interface Parser<T> { 4 T parse(String clientValue, Locale locale) throws ParseException; 5 }
format
包的子包中提供了几个Formatter的实现。number,datetime,datetime.joda。DateFormatter
。1 package org.springframework.format.datetime; 2 3 public final class DateFormatter implements Formatter<Date> { 4 5 private String pattern; 6 7 public DateFormatter(String pattern) { 8 this.pattern = pattern; 9 } 10 11 public String print(Date date, Locale locale) { 12 if (date == null) { 13 return ""; 14 } 15 return getDateFormat(locale).format(date); 16 } 17 18 public Date parse(String formatted, Locale locale) throws ParseException { 19 if (formatted.length() == 0) { 20 return null; 21 } 22 return getDateFormat(locale).parse(formatted); 23 } 24 25 protected DateFormat getDateFormat(Locale locale) { 26 DateFormat dateFormat = new SimpleDateFormat(this.pattern, locale); 27 dateFormat.setLenient(false); 28 return dateFormat; 29 } 30 31 }
5.2、注解驱动的Formatting
5.2.1、Format Annotation API
1 public class MyModel { 2 3 @DateTimeFormat(iso=ISO.DATE) 4 private Date date; 5 6 }
5.3、FormatterRegistry SPI
FormattingConversionService
适用于多数环境。5.4、FormatterRegistrar SPI
6、配置全局 date & time 格式
DateFormatterRegistrar
-- 这取决于你是否使用Joda Time 库。1 @Configuration 2 public class AppConfig { 3 4 @Bean 5 public FormattingConversionService conversionService() { 6 7 // Use the DefaultFormattingConversionService but do not register defaults 8 DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(false); 9 10 // Ensure @NumberFormat is still supported 11 conversionService.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory()); 12 13 // Register date conversion with a specific global format 14 DateFormatterRegistrar registrar = new DateFormatterRegistrar(); 15 registrar.setFormatter(new DateFormatter("yyyyMMdd")); 16 registrar.registerFormatters(conversionService); 17 18 return conversionService; 19 } 20 }
WebMvcConfigurationSupport
并重写mvcConversionService()。对于XML来说,应该使用<mvc:annotation-driven>元素的'conversion-service'属性。See Section 22.16.3, “Conversion and Formatting” for details.7、Spring Validation
7.1、JSR-303 Bean Validation API概览
1 public class PersonForm { 2 3 @NotNull 4 @Size(max=64) 5 private String name; 6 7 @Min(0) 8 private int age; 9 10 }
7.2、配置一个Bean Validation Provider
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
. javax.validation.Validator
:1 import javax.validation.Validator; 2 3 @Service 4 public class MyService { 5 6 @Autowired 7 private Validator validator; 8 9 }
②如果倾向于使用Spring Validation API,可以注入org.springframework.validation.Validator:
1 import org.springframework.validation.Validator; 2 3 @Service 4 public class MyService { 5 6 @Autowired 7 private Validator validator; 8 9 }
7.2.2、配置自定义限制(Constraints)
LocalValidatorFactoryBean
配置了一个SpringConstraintValidatorFactory
,其使用Spring来创建ConstraintValidator实例。@Constraint
声明,紧接着相关联的ConstraintValidator
实现--使用Spring来依赖注入:1 @Target({ElementType.METHOD, ElementType.FIELD}) 2 @Retention(RetentionPolicy.RUNTIME) 3 @Constraint(validatedBy=MyConstraintValidator.class) 4 public @interface MyConstraint { 5 }
1 import javax.validation.ConstraintValidator; 2 3 public class MyConstraintValidator implements ConstraintValidator { 4 5 @Autowired; 6 private Foo aDependency; 7 8 ... 9 }
7.2.3、Spring-driven Method Validation
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
1 Foo target = new Foo(); 2 DataBinder binder = new DataBinder(target); 3 binder.setValidator(new FooValidator()); 4 5 // bind to the target object 6 binder.bind(propertyValues); 7 8 // validate the target object 9 binder.validate(); 10 11 // get BindingResult that includes any validation errors 12 BindingResult results = binder.getBindingResult();
posted on 2016-10-10 10:18 LarryZeal 阅读(3044) 评论(1) 编辑 收藏 举报