将验证视为业务逻辑有其优缺点,Spring提供的验证(和数据绑定)设计不排除其中任何一种。具体来讲,验证不该与Web层绑定,而且应该易于本地化,而且应该能够插入任何可用的验证器。考虑到这些问题,Spring提供了一个Validator
契约,该契约既基本又能够在应用程序的每一个层中使用。html
数据绑定对于使用户输入动态绑定到应用程序的域模型(或用于处理用户输入的任何对象)很是有用。Spring提供了恰当地命名为DataBinder
的功能。Validator
和DataBinder
由validation
包组成,被主要的使用但不只限于web层。java
BeanWrapper
在Spring框架中是一个基本的概念而且在许多地方被使用到。然而,你大概不须要直接地使用BeanWrapper
。可是,因为这是参考文档,因此咱们认为可能须要一些解释。咱们将在本章中解释BeanWrapper
,由于若是你要使用它,那么在尝试将数据绑定到对象时最有可能使用它。react
Spring的DataBinder
和低级别BeanWrapper
二者使用PropertyEditorSupport
实现去解析和格式化属性值。PropertyEditor
和PropertyEditorSupport
类型是JavaBeans规范的一部分而且在这个章节进行解释。Spring 3开始引入了core.convert
包,该包提供了常规的类型转换工具,以及用于格式化UI字段值的高级“ format
”包。你能够将这些包用做PropertyEditorSupport
实现的更简单替代方案。这些也会在这个章节讨论。git
Spring经过安装基础设计和适配Spring的Validator
契约提供JavaBean校验。应用程序能够全局一次启用Bean验证,像在JavaBean校验中描述同样,而且仅将其用于全部验证需求。在Web层中,应用程序能够每一个DataBinder
进一步注册控制器本地的Spring Validator
实例,如配置DataBinder
中所述,这对于插入自定义验证逻辑颇有用。github
Spring提供一个Validator
接口,你可使用它校验对象。当校验的时候,Validator
接口经过使用Errors对象工做,所以校验器能够报告校验失败信息到Errors
对象。web
考虑下面小数据对象例子:spring
public class Person { private String name; private int age; // the usual getters and setters... }
下面例子经过实现下面org.springframework.validation.Validator
接口的两个方法为Person
类提供校验行为。express
supports(Class)
: Validator
校验接口是否支持Classvalidate(Object, org.springframework.validation.Errors)
: 验证给定的对象,并在发生验证错误的状况下,使用给定的Errors
对象注册这些对象。实现Validator
很是简单,特别地当你知道Spring框架提供的ValidationUtils
帮助类时。下面例子为Person
接口实现Validator
:编程
public class PersonValidator implements Validator { /** * This Validator validates only Person instances */ public boolean supports(Class clazz) { return Person.class.equals(clazz); } public void validate(Object obj, Errors e) { ValidationUtils.rejectIfEmpty(e, "name", "name.empty"); Person p = (Person) obj; if (p.getAge() < 0) { e.rejectValue("age", "negativevalue"); } else if (p.getAge() > 110) { e.rejectValue("age", "too.darn.old"); } } }
ValidationUtils
类上的静态rejectIfEmpty(...)
方法用于拒绝name
属性(若是该属性为null
或空字符串)。查看ValidationUtils
javadoc,看看它除了提供前面显示的示例外还提供什么功能。api
虽然能够实现单个验证器类来验证对象中的每一个嵌套对象,但更好的作法是将每一个嵌套对象类的验证逻辑封装到本身的验证器实现中。一个“丰富
”对象的简单示例是一个由两个String属性(第一个和第二个名字)和一个复杂的Address
对象组成的Customer
。Address
对象能够独立于Customer
对象使用,所以已经实现了独特的AddressValidator
。若是但愿CustomerValidator
重用AddressValidator
类中包含的逻辑而须要复制和粘贴,则能够在CustomerValidator
中依赖注入或实例化一个AddressValidator
,如如下示例所示:
public class CustomerValidator implements Validator { private final Validator addressValidator; public CustomerValidator(Validator addressValidator) { if (addressValidator == null) { throw new IllegalArgumentException("The supplied [Validator] is " + "required and must not be null."); } if (!addressValidator.supports(Address.class)) { throw new IllegalArgumentException("The supplied [Validator] must " + "support the validation of [Address] instances."); } this.addressValidator = addressValidator; } /** * This Validator validates Customer instances, and any subclasses of Customer too */ public boolean supports(Class clazz) { return Customer.class.isAssignableFrom(clazz); } public void validate(Object target, Errors errors) { ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "field.required"); ValidationUtils.rejectIfEmptyOrWhitespace(errors, "surname", "field.required"); Customer customer = (Customer) target; try { errors.pushNestedPath("address"); ValidationUtils.invokeValidator(this.addressValidator, customer.getAddress(), errors); } finally { errors.popNestedPath(); } } }
验证错误将报告给传递给验证器的Errors
对象。在Spring Web MVC场景中,你可使用<spring:bind/>
标签去检查错误信息,可是你也能够本身检查Errors
对象。更多关于提供的信息在Javadoc中。
参考代码:
com.liyong.ioccontainer.service.validator.ValidatorTest
咱们介绍了数据绑定和校验。本节介绍与验证错误对应的输出消息。在上一节显示的例子中,咱们拒绝name
和age
字段。若是咱们想使用MessageSource
去输出错误信息,咱们可使用提供的错误码,当拒绝字段时(在这个场景中name
和age
)。当你Errors
接口调用(直接地或间接地,经过使用ValidationUtils
类)rejectValue
或其余reject
方法之一时,底层的实现不只注册你传递的码,并且还注册一些附加的错误码。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中找到。
这个org.springframework.beans
包遵循JavaBeans标准。JavaBean是具备默认无参数构造函数的类,而且遵循命名约定,在该命名约定下,例如:名为bingoMadness
的属性将具备setter
方法setBingoMadness(..)
和getter
方法getBingoMadness()
。更多关于JavaBean信息和规范,查看javaBeans。
在beans
包中一个很是重要的类是BeanWrapper
接口和它的对应实现(BeanWrapperImpl
)。就像从Javadoc引言的那样,BeanWrapper
提供了如下功能:设置和获取属性值(单独或批量),获取属性描述符以及查询属性以肯定它们是否可读或可写。此外,BeanWrapper
还支持嵌套属性,从而能够将子属性上的属性设置为无限深度。BeanWrapper
还支持添加标准JavaBeans 的PropertyChangeListeners
和VetoableChangeListeners
的功能,而无需在目标类中支持代码。最后但并不是不重要的一点是,BeanWrapper
支持设置索引属性。BeanWrapper
一般不直接由应用程序代码使用,而是由DataBinder
和BeanFactory
使用。
BeanWrapper
的工做方式部分由其名称表示:它包装一个Bean,以对该Bean执行操做,例如设置和检索属性。
设置和获取属性是经过BeanWrapper
的重载方法setPropertyValue
和getPropertyValue
的变体。查看它们的详细文档。下面的表格显示这些约定:
Expression | Explanation |
---|---|
name |
表示属性name 对应的getName() 或 isName() 和 setName(..) 方法。 |
account.name |
表示嵌入account 属性的name 属性对应的getAccount().setName() 或getAccount().getName() 方法 |
account[2] |
表示2个索引元素属性account 。索引属性能够是类型array 、list 或其余天然顺序集合。 |
account[COMPANYNAME] |
表示map 实体的值经过account Map属性的key COMPANYNAME 索引。 |
(若是你没打算直接使用BeanWrapper
,下面部分不是相当重要地。若是你仅仅使用DataBinder
、BeanFactory
和他的默认实现,你能够跳过PropertyEditors的部分)。
下面两个例子类使用BeanWrapper
去获取和设置属性:
public class Company { private String name; private Employee managingDirector; public String getName() { return this.name; } public void setName(String name) { this.name = name; } public Employee getManagingDirector() { return this.managingDirector; } public void setManagingDirector(Employee managingDirector) { this.managingDirector = managingDirector; } }
public class Employee { private String name; private float salary; public String getName() { return this.name; } public void setName(String name) { this.name = name; } public float getSalary() { return salary; } public void setSalary(float salary) { this.salary = salary; } }
如下代码段显示了一些有关如何检索和操纵实例化的Company
和Employee
的某些属性的示例:
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");
代码示例:
com.liyong.ioccontainer.service.beanwrapper.BeanWrapperTest
PropertyEditor
实现Spring使用PropertyEditor
概念去影响一个对象和字符串之间的转换。以不一样于对象自己的方式表示属性可能很方便。例如,日期能够用人类可读的方式表示(如字符串:'2007-14-09
'),而咱们仍然能够将人类可读的形式转换回原始日期(或者更好的是,转换任何日期以人类可读的形式输入到Date
对象)。经过注册类型为java.beans.PropertyEditor
的自定义编辑器,能够实现此行为。在BeanWrapper
上或在特定的IoC容器中注册自定义编辑器(如上一章所述),使它具备如何将属性转换为所需类型的能力。更多关于PropertyEditor
,请参阅Oracle的java.beans包的javadoc。
在Spring中使用属性编辑的两个示例:
PropertyEditor
实如今bean上设置属性。当使用String做为在XML文件中声明的某个bean的属性的值时,Spring(若是相应属性的setter
具备Class
参数)将使用ClassEditor
尝试将参数解析为Class
对象。PropertyEditor
实现来解析HTTP请求参数,你能够在CommandController
的全部子类中手动绑定这些实现。Spring有一个内建的PropertyEditor
实现。它们都位于org.springframework.beans.propertyeditors
包中。默认状况下,大多数(但不是所有,以下表所示)由BeanWrapperImpl
注册。若是能够经过某种方式配置属性编辑器,则仍能够注册本身的变体以覆盖默认变体。
下表描述了Spring提供的各类PropertyEditor
实现:
Class | Explanation |
---|---|
ByteArrayPropertyEditor |
字节数组的编辑器。将字符串转换为其相应的字节表示形式。默认 BeanWrapperImpl 注册。 |
ClassEditor |
将表明类的字符串解析为实际类,反之亦然。当类没有找到抛出IllegalArgumentException 。默认 BeanWrapperImpl 注册。 |
CustomBooleanEditor |
Boolean 属性的可定制属性编辑器。默认,经过BeanWrapperImpl 注册,可是能够经过将其自定义实例注册为自定义编辑器来覆盖它。 |
CustomCollectionEditor |
集合属性编辑器,转换任何源Collection 到给定Collection 类型。 |
CustomDateEditor |
java.util.Date 的可自定义属性编辑器,支持一个自定义DateFormat 。默认不会被注册。必须根据须要以适当的格式进行用户注册。 |
CustomNumberEditor |
任何Number 子类可自定义属性编辑器,例如Integer 、Long 、Float 或Double 。默认,经过BeanWrapperImpl 注册,可是能够经过将其自定义实例注册为自定义编辑器来覆盖它。 |
FileEditor |
解析字符串为java.io.File 对象。默认,经过BeanWrapperImpl 注册。 |
InputStreamEditor |
单向属性编辑器,它能够采用字符串并生成(经过中间的ResourceEditor 和Resource )一个InputStream ,以即可以将InputStream 属性直接设置为字符串。请注意,默认用法不会为你关闭InputStream 。默认状况下,由BeanWrapperImpl 注册 |
LocaleEditor |
能够将字符串解析为Locale 对象,反之亦然(字符串格式为[country] [variant] ,相似Locale 的toString()方法相同)。默认,经过BeanWrapperImpl 注册 |
PatternEditor |
可以解析字符串为java.util.regex.Pattern 对象,反之亦然。 |
PropertiesEditor |
能够将字符串(格式设置为java.util.Properties 类的javadoc中定义的格式)转换为Properties 对象 |
StringTrimmerEditor |
修剪字符串的属性编辑器。 (可选)容许将空字符串转换为空值。默认不被注册-必须被用户注册。 |
URLEditor |
可以转换一个字符串表明的URL为真实的URL对象。默认,经过BeanWrapperImpl 注册。 |
Spring使用java.beans.PropertyEditorManager
去设置属性编辑器可能须要的搜索路径。搜索路径也能够包含sun.bean.editors
,它包括例如Font
、Color
和大多数原始类型的PropertyEditor
实现。还要注意,若是标准JavaBeans基础结构与它们处理的类在同一包中而且与该类具备相同的名称,而且附加了Editor
,则标准JavaBeans基础结构会自动发现PropertyEditor
类(无需显式注册它们)。例如,可使用如下类和包结构,这就足以识别SomethingEditor
类并将其用做某种类型属性的PropertyEditor
。
com chank pop Something SomethingEditor // SomethingEditor用做Something类
注意,你也能够在此处使用标准的BeanInfo
JavaBeans机制(这里有所描述)。下面例子使用BeanInfo
机制去明确地注册一个或多个PropertyEditor
实例到关联类的属性:
com chank pop Something SomethingBeanInfo // BeanInfo用做Something类
下面是引用的SomethingBeanInfo
类的Java源代码,它将CustomNumberEditor
与Something
类的age
属性关联起来:
public class SomethingBeanInfo extends SimpleBeanInfo { public PropertyDescriptor[] getPropertyDescriptors() { try { final PropertyEditor numberPE = new CustomNumberEditor(Integer.class, true); PropertyDescriptor ageDescriptor = new PropertyDescriptor("age", Something.class) { public PropertyEditor createPropertyEditor(Object bean) { return numberPE; }; }; return new PropertyDescriptor[] { ageDescriptor }; } catch (IntrospectionException ex) { throw new Error(ex.toString()); } } }
参考代码:
com.liyong.ioccontainer.service.propertyeditor.PropertyEditorTest
注册附加的自定义PropertyEditor
实现
当设置bean属性为字符串值时,Spring IoC容器最终地使用标准JavaBean的PropertyEditor
实现去转换这些字符串为属性的复杂类型。Spring预注册了很是多的自定义PropertyEditor
实现(例如,将表示为字符串的类名称转换为Class对象)。此外,Java的标准JavaBeans PropertyEditor
查找机制容许适当地命名类的PropertyEditor
,并将其与提供支持的类放在同一包中,以即可以自动找到它。
若是须要注册其余自定义PropertyEditors
,则可使用几种机制。最手动的方法(一般不方便或不建议使用)是使用ConfigurableBeanFactory
接口的registerCustomEditor()
方法,假设你有BeanFactory
引用。另外一种(稍微方便些)的机制是使用称为CustomEditorConfigurer
的特殊bean工厂后处理器。尽管你能够将Bean工厂后处理器与BeanFactory
实现一块儿使用,但CustomEditorConfigurer
具备嵌套的属性设置,所以咱们强烈建议你将其与ApplicationContext
一块儿使用,在这里能够将其以与其余任何Bean类似的方式进行部署,而且能够在任何位置进行部署。自动检测并应用。
请注意,全部的bean工厂和应用程序上下文经过使用BeanWrapper
来处理属性转换,都会自动使用许多内置的属性编辑器。上一节列出了BeanWrapper
注册的标准属性编辑器。此外,ApplicationContext
还以适合特定应用程序上下文类型的方式重写或添加其余编辑器,以处理资源查找。
标准JavaBeans PropertyEditor
实例用于将表示为字符串的属性值转换为属性的实际复杂类型。你可使用bean工厂的后处理器CustomEditorConfigurer
来方便地将对其余PropertyEditor
实例的支持添加到ApplicationContext
中。
考虑如下示例,该示例定义了一个名为ExoticType
的用户类和另外一个名为DependsOnExoticType
的类,该类须要将ExoticType
设置为属性:
package example; public class ExoticType { private String name; public ExoticType(String name) { this.name = name; } } public class DependsOnExoticType { private ExoticType type; public void setType(ExoticType type) { this.type = type; } }
正确设置以后,咱们但愿可以将type
属性分配为字符串,PropertyEditor
会将其转换为实际的ExoticType
实例。如下bean定义显示了如何创建这种关系:
<bean id="sample" class="example.DependsOnExoticType"> <property name="type" value="aNameForExoticType"/> </bean>
PropertyEditor实现可能相似于如下内容:
// converts string representation to ExoticType object package example; public class ExoticTypeEditor extends PropertyEditorSupport { public void setAsText(String text) { setValue(new ExoticType(text.toUpperCase())); } }
最后,下面的示例演示如何使用CustomEditorConfigurer
向ApplicationContext
注册新的PropertyEditor
,而后能够根据须要使用它:
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer"> <property name="customEditors"> <map> <entry key="example.ExoticType" value="example.ExoticTypeEditor"/> </map> </property> </bean>
参考代码:
com.liyong.ioccontainer.starter.PropertyEditorIocContainer
使用PropertyEditorRegistrar
在Spring容器中注册属性编辑器的其余机制是建立和使用PropertyEditorRegistrar
。当须要在几种不一样状况下使用同一组属性编辑器时,此接口特别有用。你能够在每一种场景中写对应的注册和从新使用。PropertyEditorRegistrar
实例与一个名为PropertyEditorRegistry
的接口一块儿工做,该接口由Spring BeanWrapper
(和DataBinder
)实现。与CustomEditorConfigurer
(在此描述)结合使用时,PropertyEditorRegistrar
实例特别方便,该实例暴露了名为setPropertyEditorRegistrars(..)
的属性。以这种方式添加到CustomEditorConfigurer
中的PropertyEditorRegistrar
实例能够轻松地与DataBinder
和Spring MVC控制器共享。此外,它避免了在自定义编辑器上进行同步的需求:但愿PropertyEditorRegistrar
为每次建立bean的尝试建立新的PropertyEditor
实例。
如下示例说明如何建立本身的PropertyEditorRegistrar
实现:
package com.foo.editors.spring; public final class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar { public void registerCustomEditors(PropertyEditorRegistry registry) { // 指望建立一个新的PropertyEditor示例 registry.registerCustomEditor(ExoticType.class, new ExoticTypeEditor()); // you could register as many custom property editors as are required here... } }
另请参阅org.springframework.beans.support.ResourceEditorRegistrar
以获取示例PropertyEditorRegistrar
实现。注意,在实现registerCustomEditors(...)
方法时,它如何建立每一个属性编辑器的新实例。
下一个示例显示了如何配置CustomEditorConfigurer
并将其注入咱们的CustomPropertyEditorRegistrar
的实例:
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer"> <property name="propertyEditorRegistrars"> <list> <ref bean="customPropertyEditorRegistrar"/> </list> </property> </bean> <bean id="customPropertyEditorRegistrar" class="com.foo.editors.spring.CustomPropertyEditorRegistrar"/>
最后(对于使用Spring的MVC Web框架的读者来讲,与本章的重点有所偏离),使用PropertyEditorRegistrars
与数据绑定Controllers
(例如SimpleFormController
)结合使用会很是方便。下面的示例在initBinder(..)
方法的实现中使用PropertyEditorRegistrar
:
public final class RegisterUserController extends SimpleFormController { private final PropertyEditorRegistrar customPropertyEditorRegistrar; public RegisterUserController(PropertyEditorRegistrar propertyEditorRegistrar) { this.customPropertyEditorRegistrar = propertyEditorRegistrar; } protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception { this.customPropertyEditorRegistrar.registerCustomEditors(binder); } // other methods to do with registering a User }
这种PropertyEditor
注册样式可使代码简洁(initBinder(..)
的实现只有一行长),而且能够将通用的PropertyEditor
注册代码封装在一个类中,而后根据须要在许多Controller
之间共享。
Spring 3 已经引入一个core.convert
包,它提供了通常类型系统转换。系统定义了一个用于实现类型转换逻辑的SPI和一个用于在运行时执行类型转换的API。在Spring容器中,可使用此特性做为PropertyEditor
实现的替代方法,以将外部化的bean属性值字符串转换为所需的属性类型。你还能够在应用程序中须要类型转换的任何地方使用公共API。
如如下接口定义所示,用于实现类型转换逻辑的SPI很是简单且具备强类型:
package org.springframework.core.convert.converter; public interface Converter<S, T> { T convert(S source); }
要建立本身的转换器,请实现Converter
接口,并将S
设置为要被转换的类型,并将T
设置为要转换为的类型。若是须要将S
的集合或数组转换为T
的集合,而且已经注册了委托数组或集合转换器(默认状况下,DefaultConversionService
会这样作),那么你还能够透明地应用这样的转换器。
对于每次convert(S)
的调用,方法参数必须保证不能为null
。若是转换失败,你的Converter
可能抛出未检查异常。特别地,它可能抛出IllegalArgumentException
去报告无效参数值异常。当心的去确保Converter
实现是线程安全的。
为了方便起见,在core.convert.support
包中提供了几种转换器实现。这些包括从字符串到数字和其余常见类型的转换器。下面的清单显示了StringToInteger
类,它是一个典型的Converter
实现:
package org.springframework.core.convert.support; final class StringToInteger implements Converter<String, Integer> { public Integer convert(String source) { return Integer.valueOf(source); } }
ConverterFactory
当须要集中整个类层次结构的转换逻辑时(例如,从String
转换为Enum
对象时),能够实现ConverterFactory
,如如下示例所示:
package org.springframework.core.convert.converter; public interface ConverterFactory<S, R> { <T extends R> Converter<S, T> getConverter(Class<T> targetType); }
参数化S
为你要转换的类型,参数R
为基础类型,定义能够转换为的类的范围。而后实现getConverter(Class <T>)
,其中T
是R
的子类。
考虑StringToEnumConverterFactory
例子:
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()); } } }
GenericConverter
当你须要复杂的Converter
实现时,请考虑使用GenericConverter
接口。与Converter
相比,GenericConverter
具备比Converter
更灵活但类型不强的签名,支持多种源类型和目标类型之间进行转换。此外,GenericConverter
还提供了在实现转换逻辑时可使用的源和目标字段上下文。这样的上下文容许由字段注解或在字段签名上声明的泛型信息驱动类型转换。下面清单显示GenericConverter
接口定义:
package org.springframework.core.convert.converter; public interface GenericConverter { public Set<ConvertiblePair> getConvertibleTypes(); Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType); }
实现GenericConverter
,须要getConvertibleTypes()
返回支持的源→目标类型对。而后实现convert(Object, TypeDescriptor, TypeDescriptor)
去包含你的转换逻辑。源TypeDescriptor
提供对包含正在转换的值的源字段的访问。使用目标TypeDescriptor
,能够访问要设置转换值的目标字段。
GenericConverter
的一个很好的例子是在Java数组和集合之间进行转换的转换器。这样的ArrayToCollectionConverter
会检查声明目标集合类型的字段以解析集合的元素类型。这样就能够在将集合设置到目标字段上以前,将源数组中的每一个元素转换为集合元素类型。
因为GenericConverter
是一个更复杂的SPI接口,所以仅应在须要时使用它。支持Converter
或ConverterFactory
以知足基本的类型转换需求。参考代码:
com.liyong.ioccontainer.service.converter.GenericConverterTest
使用ConditionalGenericConverter
有时,你但愿Converter
仅在知足特定条件时才运行。例如,你可能只想在目标字段上存在特定注解时才运行Converter
,或者可能在目标类上定义了特定方法(例如静态valueOf
方法)时才运行Converter
。ConditionalGenericConverter
是GenericConverter
和ConditionalConverter
接口的联合,可以让你定义如下自定义匹配条件:
public interface ConditionalConverter { boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType); } public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter { }
ConditionalGenericConverter
的一个很好的例子是EntityConverter
,它在持久实体标识和实体引用之间转换。仅当目标实体类型声明静态查找器方法(例如findAccount(Long)
)时,此类EntityConverter
才可能匹配。你能够在matchs(TypeDescriptor,TypeDescriptor)
的实现中执行这种finder
方法检查。
参考代码:
com.liyong.ioccontainer.service.converter.ConditionalConverterTest
ConversionService
APIConversionService
定义了一个统一的API,用于在运行时执行类型转换逻辑。转换器一般在如下门面接口执行:
package org.springframework.core.convert; public interface ConversionService { boolean canConvert(Class<?> sourceType, Class<?> targetType); <T> T convert(Object source, Class<T> targetType); boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType); Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType); }
大多数ConversionService
实现也都实现ConverterRegistry
,该转换器提供用于注册转换器的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
:
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"/>
默认的ConversionService
能够在字符串、数字、枚举、集合、映射和其余常见类型之间进行转换。要用你本身的自定义转换器补充或覆盖默认转换器,请设置converters
属性。属性值能够实现Converter
,ConverterFactory
或GenericConverter
接口中的任何一个。
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <set> <bean class="example.MyCustomConverter"/> </set> </property> </bean>
在Spring MVC应用程序中使用ConversionService
也很常见。参见Spring MVC一章中的转换和格式化。
在某些状况下,你可能但愿在转换过程当中应用格式设置。有关使用FormattingConversionServiceFactoryBean
的详细信息,请参见FormatterRegistry SPI。
ConversionService
要以编程方式使用ConversionService
实例,能够像对其余任何bean同样注入对该bean例的引用。如下示例显示了如何执行此操做:
@Service public class MyService { public MyService(ConversionService conversionService) { this.conversionService = conversionService; } public void doIt() { this.conversionService.convert(...) } }
对于大多数用例,可使用指定targetType
的convert
方法,但不适用于更复杂的类型,例如参数化元素的集合。例如,若是要以编程方式将整数列表转换为字符串列表,则须要提供源类型和目标类型的格式定义。
幸运的是,以下面的示例所示,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
自动注册适用于大多数环境的转换器。这包括集合转换器、标量转换器和基本的对象到字符串转换器。你可使用DefaultConversionService
类上的静态addDefaultConverters
方法向任何ConverterRegistry
注册相同的转换器。
值类型的转换器可重用于数组和集合,所以,假设标准集合处理适当,则无需建立特定的转换器便可将S
的集合转换为T
的集合。
如上一节所述,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
值。更通用的core.convert
Converter
SPI不能直接知足此类格式化要求。为了直接解决这些问题,Spring 3 引入了方便的Formatter
SPI,它为客户端环境提供了PropertyEditor
实现的简单而强大的替代方案。
一般,当你须要实现通用类型转换逻辑时,可使用Converter
SPI,例如,在java.util.Date
和Long
之间转换。当你在客户端环境中(例如,web应用)而且须要去解析和打印本地化字段值时,你可使用Formatter
SPI。ConversionService
为这两个SPI提供统一的类型转换。
Formatter
SPIFormatter
SPI去实现字段格式逻辑是简单和强类型的。下面清单显示Formatter
接口信息:
package org.springframework.format; public interface Formatter<T> extends Printer<T>, Parser<T> { }
Formatter
从Printer
和Parser
构建块接口拓展。下面清单显示这两个接口定义:
public interface Printer<T> { String print(T fieldValue, Locale locale); }
import java.text.ParseException; public interface Parser<T> { T parse(String clientValue, Locale locale) throws ParseException; }
去建立你本身的Formatter
,实现前面展现的Formatter
接口。将T
参数化为你但愿格式化的对象类型-例如,java.util.Date
。实现print()
操做以打印T
的实例以在客户端语言环境中显示。实现parse()
操做,以从客户端本地返回的格式化表示形式解析T
的实例。若是尝试解析失败,你的Formatter
应该抛一个ParseException
或IllegalArgumentException
异常。注意确保你的Formatter
实现是线程安全的。
为了方便format
子包提供一些Formatter
实现。number
包提供NumberStyleFormatter
、CurrencyStyleFormatter
和PercentStyleFormatter
去格式化Number
对象,它使用java.text.NumberFormat
。datetime
包提供DateFormatter
去格式化java.util.Date
与java.text.DateFormat
对象。datetime.joda
包基于Joda-Time库提供了全面的日期时间格式支持。
下面DateFormatter
是Formatter
实现例子:
package org.springframework.format.datetime; public final class DateFormatter implements Formatter<Date> { private String pattern; public DateFormatter(String pattern) { this.pattern = pattern; } public String print(Date date, Locale locale) { if (date == null) { return ""; } return getDateFormat(locale).format(date); } public Date parse(String formatted, Locale locale) throws ParseException { if (formatted.length() == 0) { return null; } return getDateFormat(locale).parse(formatted); } protected DateFormat getDateFormat(Locale locale) { DateFormat dateFormat = new SimpleDateFormat(this.pattern, locale); dateFormat.setLenient(false); return dateFormat; } }
Spring欢迎社区驱动Formatter
贡献。查看GitHub Issues去贡献。
能够经过字段类型或注解配置字段格式。要将注解绑定到Formatter
,请实现AnnotationFormatterFactory
。下面清单显示AnnotationFormatterFactory
接口定义:
package org.springframework.format; public interface AnnotationFormatterFactory<A extends Annotation> { Set<Class<?>> getFieldTypes(); Printer<?> getPrinter(A annotation, Class<?> fieldType); Parser<?> getParser(A annotation, Class<?> fieldType); }
去建立一个实现:将A
参数化为要与格式逻辑关联的字段annotationType
,例如,org.springframework.format.annotation.DateTimeFormat
让getFieldTypes()
返回可在其上使用注解的字段类型。让getPrinter()
返回Printer
以打印带注解的字段的值。让getParser()
返回Parser
去为注解字段解析clientValue
。
下面的示例AnnotationFormatterFactory
实现将@NumberFormat
注解绑定到格式化程序,以指定数字样式或模式:
public final class NumberFormatAnnotationFormatterFactory implements AnnotationFormatterFactory<NumberFormat> { public Set<Class<?>> getFieldTypes() { return new HashSet<Class<?>>(asList(new Class<?>[] { Short.class, Integer.class, Long.class, Float.class, Double.class, BigDecimal.class, BigInteger.class })); } public Printer<Number> getPrinter(NumberFormat annotation, Class<?> fieldType) { return configureFormatterFrom(annotation, fieldType); } public Parser<Number> getParser(NumberFormat annotation, Class<?> fieldType) { return configureFormatterFrom(annotation, fieldType); } private Formatter<Number> configureFormatterFrom(NumberFormat annotation, Class<?> fieldType) { if (!annotation.pattern().isEmpty()) { return new NumberStyleFormatter(annotation.pattern()); } else { Style style = annotation.style(); if (style == Style.PERCENT) { return new PercentStyleFormatter(); } else if (style == Style.CURRENCY) { return new CurrencyStyleFormatter(); } else { return new NumberStyleFormatter(); } } } }
触发格式,可使用@NumberFormat
注解字段,如如下示例所示:
public class MyModel { @NumberFormat(style=Style.CURRENCY) private BigDecimal decimal; }
格式注解API
org.springframework.format.annotation
包中存在一个可移植的格式注解API。你可使用@NumberFormat
格式化Number
字段(例如Double
和Long
),并使用@DateTimeFormat
格式化java.util.Date
、java.util.Calendar
、Long
(用于毫秒时间戳)以及JSR-310 java.time
和Joda-Time
值类型。
下面例子使用@DateTimeFormat
去格式java.util.Date
为ISO日期(yyyy-MM-dd
);
public class MyModel { @DateTimeFormat(iso=ISO.DATE) private Date date; }
FormatterRegistry
SPIFormatterRegistry
是一个SPI用于注册格式化器和转换器。FormattingConversionService
是FormatterRegistry
实现适用于绝大环境。经过使用FormattingConversionServiceFactoryBean
,你能够编程式地或声明式配置这些变体做为Spring bean。因为此实现还实现了ConversionService
,所以你能够直接将其配置为与Spring的DataBinder
和Spring表达式语言(SpEL)一块儿使用。
下面清单显示FormatterRegistry
SPI接口定义:
package org.springframework.format; public interface FormatterRegistry extends ConverterRegistry { void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser); void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter); void addFormatterForFieldType(Formatter<?> formatter); void addFormatterForAnnotation(AnnotationFormatterFactory<?> factory); }
像在前面清单显示,你经过字段类型或经过注解注册格式化器。
FormatterRegistry
SPI使你能够集中配置格式设置规则,而没必要在控制器之间重复此类配置。例如,你可能要强制全部日期字段以某种方式设置格式或带有特定注解的字段以某种方式设置格式。使用共享的FormatterRegistry
,你能够一次定义这些规则,并在须要格式化时应用它们。
FormatterRegistrar
SPIFormatterRegistrar
是一个SPI,用于经过FormatterRegistry
注册格式器和转换器。如下清单显示了其接口定义:
package org.springframework.format; public interface FormatterRegistrar { void registerFormatters(FormatterRegistry registry); }
为给定的格式类别(例如日期格式)注册多个相关的转换器和格式器时,FormatterRegistrar
颇有用。在声明式注册不充分的状况下它也颇有用。例如,当格式化程序须要在不一样于其自身<T>的特定字段类型下进行索引时,或者在注册Printer
/Parser
对时。下一节将提供有关转换器和格式化注册的更多信息。
在Spring MVC章节中,查看 Conversion 和 Formatting 。
Date
和Time
格式默认状况下,未使用@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
格式:
@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 registrar = new DateTimeFormatterRegistrar(); registrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd")); registrar.registerFormatters(conversionService); // Register date conversion with a specific global format DateFormatterRegistrar registrar = new DateFormatterRegistrar(); registrar.setFormatter(new DateFormatter("yyyyMMdd")); registrar.registerFormatters(conversionService); return conversionService; } }
若是你偏好与基于XML配置,你可使用FormattingConversionServiceFactoryBean
。下面例子显示怎样去作(这里使用Joda Time
):
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans 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.joda.JodaTimeFormatterRegistrar"> <property name="dateFormatter"> <bean class="org.springframework.format.datetime.joda.DateTimeFormatterFactoryBean"> <property name="pattern" value="yyyyMMdd"/> </bean> </property> </bean> </set> </property> </bean> </beans>
注意:当在web应用中配置日期和时间格式时须要额外考虑。请查看 WebMVC Conversion 和 Formatting or WebFlux Conversion 和 Formatting.
Spring框架提供对Java Bean校验API。
Bean验证为Java应用程序提供了经过约束声明和元数据进行验证的通用方法。要使用它,你须要使用声明性验证约束对域模型属性进行注解,而后由经过运行时强制实施约束。有内置的约束,你也能够定义本身的自定义约束。
考虑如下示例,该示例显示了具备两个属性的简单PersonForm
模型:
public class PersonForm { private String name; private int age; }
Bean验证使你能够声明约束,如如下示例所示:
public class PersonForm { @NotNull @Size(max=64) private String name; @Min(0) private int age; }
而后,Bean验证器根据声明的约束来验证此类的实例。有关该API的通常信息,请参见Bean Validation。有关特定限制,请参见Hibernate Validator文档。要学习如何将bean验证提供程序设置为Spring bean,请继续阅读。
Spring提供了对Bean验证API的全面支持,包括将Bean验证提供程序做为Spring Bean执行引导。这使你能够在应用程序中须要验证的任何地方注入javax.validation.ValidatorFactory
或javax.validation.Validator
。
你可使用LocalValidatorFactoryBean
将默认的Validator
配置为Spring Bean,如如下示例所示:
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; @Configuration public class AppConfig { @Bean public LocalValidatorFactoryBean validator() { return new LocalValidatorFactoryBean; } }
前面示例中的基本配置触发Bean验证以使用其默认引导机制进行初始化。Bean验证提供程序,例如Hibernate
Validator
,应该存在于类路径中并被自动检测到。
注入校验器
LocalValidatorFactoryBean
同时实现javax.validation.ValidatorFactory
和javax.validation.Validator
以及Spring的org.springframework.validation.Validator
。你能够将对这些接口之一的引用注入须要调用验证逻辑的bean中。
若是你但愿直接使用Bean Validation
API,则能够注入对javax.validation.Validator
的引用,如如下示例所示:
import javax.validation.Validator; @Service public class MyService { @Autowired private Validator validator; }
配置自定义约束
每一个bean校验约束由两部分组成:
@Constraint
注解,用于声明约束及其可配置属性。javax.validation.ConstraintValidator
接口的实现,用于实现约束的行为。要将声明与实现相关联,每一个@Constraint
注解都引用一个对应的ConstraintValidator
实现类。在运行时,当在域模型中遇到约束注解时,ConstraintValidatorFactory
实例化引用的实现。
默认状况下,LocalValidatorFactoryBean
配置一个SpringConstraintValidatorFactory
,该工厂使用Spring建立ConstraintValidator
实例。这使你的自定义ConstraintValidators
像其余任何Spring bean同样受益于依赖项注入。
如下示例显示了一个自定义@Constraint
声明,后跟一个关联的ConstraintValidator
实现,该实现使用Spring进行依赖项注入:
@Target({ElementType.METHOD, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy=MyConstraintValidator.class) public @interface MyConstraint { }
import javax.validation.ConstraintValidator; public class MyConstraintValidator implements ConstraintValidator { @Autowired; private Foo aDependency; // ... }
如前面的示例所示,ConstraintValidator
实现能够像其余任何Spring bean同样具备@Autowired
依赖项。
参考代码:
com.liyong.ioccontainer.service.validator.ConstraintTest
Spring驱动方法验证
你能够经过MethodValidationPostProcessor
bean定义将Bean Validation 1.1
(以及做为自定义扩展,还包括Hibernate
Validator 4.3
)支持的方法验证功能集成到Spring上下文中:
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor; @Configuration public class AppConfig { @Bean public MethodValidationPostProcessor validationPostProcessor() { return new MethodValidationPostProcessor; } }
为了有资格进行Spring驱动的方法验证,全部目标类都必须使用Spring的@Validated
注解进行注释,该注解也能够选择声明要使用的验证组。有关使用Hibernate Validator
和Bean Validation 1.1
提供程序的设置详细信息,请参见MethodValidationPostProcessor。
方法验证依赖于目标类周围的AOP代理,即接口上方法的JDK动态代理或CGLIB代理。代理的使用存在某些限制,在 理解 AOP 代理中介绍了其中的一些限制。另外,请记住在代理类上使用方法和访问器;直接访问将不起做用。参考代码:
com.liyong.ioccontainer.starter.MethodvalidationIocContainer
其余配置选项
在大多数状况下,默认LocalValidatorFactoryBean
配置就足够了。从消息插值到遍历解析,有多种用于各类Bean验证构造的配置选项。有关这些选项的更多信息,请参见LocalValidatorFactoryBean Javadoc。
DataBinder
从Spring 3 开始,你可使用Validator
配置DataBinder
实例。配置完成后,你能够经过调用binder.validate()
来调用Validator
。任何验证错误都会自动添加到绑定的BindingResult
中。
下面的示例演示如何在绑定到目标对象后,以编程方式使用DataBinder
来调用验证逻辑:
Foo target = new Foo(); DataBinder binder = new DataBinder(target); binder.setValidator(new FooValidator()); // bind to the target object binder.bind(propertyValues); // validate the target object binder.validate(); // get BindingResult that includes any validation errors BindingResult results = binder.getBindingResult();
你还能够经过dataBinder.addValidators
和dataBinder.replaceValidators
配置具备多个Validator
实例的DataBinder
。当将全局配置的bean验证与在DataBinder
实例上本地配置的Spring Validator
结合使用时,这颇有用。查看Spring MVC 校验配置。
参考代码:
com.liyong.ioccontainer.service.validator.ValidatorTest
在Sprint MVC 章节中,查看Validation。
我的从事金融行业,就任过易极付、思建科技、某网约车平台等重庆一流技术团队,目前就任于某银行负责统一支付系统建设。自身对金融行业有强烈的爱好。同时也实践大数据、数据存储、自动化集成和部署、分布式微服务、响应式编程、人工智能等领域。同时也热衷于技术分享创立公众号和博客站点对知识体系进行分享。关注公众号: 青年IT男 获取最新技术文章推送!
博客地址: http://youngitman.tech
CSDN: https://blog.csdn.net/liyong1...
微信公众号:
技术交流群: