天天看点

【Spring】Spring Framework Reference Documentation中文版8

9. Validation, Data Binding, and Type Conversion

验证、数据绑定和类型转换

9.1 Introduction

介绍

JSR-303/JSR-349 Bean Validation

JSR-303/JSR-349的bean验证

Spring Framework 4.0 supports Bean Validation 1.0 (JSR-303) and Bean Validation 1.1 (JSR-349) in terms of setup support, also adapting it to Spring’s Validator interface.

spring框架4支持bean验证1.0(JSR303)和bean验证1.1(JSR349),同时适用于spring的验证接口。

An application can choose to enable Bean Validation once globally, as described in Section 9.8, “Spring Validation”, and use it exclusively for all validation needs.

应用可以选择是否开启全局的bean验证,在9.8节中描述的“spring验证”,将其用于所有验证的需要。

An application can also register additional Spring Validator instances per DataBinder instance, as described in Section 9.8.3, “Configuring a DataBinder”. This may be useful for plugging in validation logic without the use of annotations.

一个应用可以注册额外的spring验证实例对于每个数据绑定实例,在9.8.3节中描述的“配置一个数据绑定”。这对不使用注解而实现验证逻辑的插件化处理很有用。

There are pros and cons for considering validation as business logic, and Spring offers a design for validation (and data binding) that does not exclude either one of them. Specifically validation should not be tied to the web tier, should be easy to localize and it should be possible to plug in any validator available. Considering the above, Spring has come up with a Validator interface that is both basic and eminently usable in every layer of an application.

将验证作为业务逻辑,spring提供一个设计用于验证(和数据绑定)包括全部。明确的验证不应该和web层绑定,应该易于局部化并且可以在需要验证时以插件的方式使用。考虑到上面的几点,spring提供了一个验证器接口可以用于一个应用的每一层。

Data binding is useful for allowing user input to be dynamically bound to the domain model of an application (or whatever objects you use to process user input). Spring provides the so-called DataBinder to do exactly that. The Validator and the DataBinder make up the validation package, which is primarily used in but not limited to the MVC framework.

数据绑定是有用的允许读者动态输入绑定应用的对象模型(或你需要处理用户输入的object)。spring提供了这样的DataBinder用于处理这些。验证器和数据绑定器组成了验证的包,主要用于但是不限制于MVC的框架中。

The BeanWrapper is a fundamental concept in the Spring Framework and is used in a lot of places. However, you probably will not have the need to use the BeanWrapper directly. Because this is reference documentation however, we felt that some explanation might be in order. We will explain the BeanWrapper in this chapter since, if you were going to use it at all, you would most likely do so when trying to bind data to objects.

BeanWrapper是spring框架的基本内容用在很多的位置。然而,你不需要直接使用BeanWrapper。因为这是一个参考文档,然而我们会提供一些解释。我们将会这一节解释BeanWrapper,如果你决定使用它,当你试图将数据绑定到object时会用到。

Spring’s DataBinder and the lower-level BeanWrapper both use PropertyEditors to parse and format property values. The PropertyEditor concept is part of the JavaBeans specification, and is also explained in this chapter. Spring 3 introduces a "core.convert" package that provides a general type conversion facility, as well as a higher-level "format" package for formatting UI field values. These new packages may be used as simpler alternatives to PropertyEditors, and will also be discussed in this chapter.

spring的DataBingder和低级的BeanWrapper都使用了PropertyEditors来解析和格式化属性值。PropertyEditor是JavaBean定义的一部分,并且在这节中得到解释。spring3介绍了一个core.conver包用于提供通用的类型转换方法,在formate包中由于格式化UI属性值。这些新的包将可以简单的使用PropertyEditors来替代,并且将会这节中讲解。

9.2 Validation using Spring’s Validator interface

使用spring的验证接口进行验证

Spring features a Validator interface that you can use to validate objects. The Validator interface works using an Errors object so that while validating, validators can report validation failures to the Errors object.

你可以使用spring的验证器接口的来验证object。验证器接口使用一个Errors的object来工作用于完成验证,验证器可以对验证失败的object提供验证报告。

Let’s consider a small data object:

让我们考虑一个小的object

public class Person {

    private String name;

    private int age;

    // the usual getters and setters...

// 通常的get和set方法

}

We’re going to provide validation behavior for the Person class by implementing the following two methods of the org.springframework.validation.Validator interface:

我们通过实现org.springframework.validation.Validator接口中的两个方法用于对Person类添加验证行为。

    supports(Class) - Can this Validator validate instances of the supplied Class?

验证器验证的实例是否是提供的类的实例?

    validate(Object, org.springframework.validation.Errors) - validates the given object and in case of validation errors, registers those with the given Errors object

验证给定的object是否是由于验证的error,注册在给定的Errors的object上

Implementing a Validator is fairly straightforward, especially when you know of the ValidationUtils helper class that the Spring Framework also provides.

直接实现Valiator接口,尤其是当你知道spring框架提供了一个ValidationUtils的工具类时。

public class PersonValidator implements Validator {

    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");

        }

    }

}

As you can see, the static rejectIfEmpty(..) method on the ValidationUtils class is used to reject the 'name' property if it is null or the empty string. Have a look at the ValidationUtils javadocs to see what functionality it provides besides the example shown previously.

你看到了,ValidationUtils中的静态rejectIfEmpty方法用于拒绝当如果name属性是null或者是一个空字符串。可以查看ValidationUtils的javadocs来查看之前的例子是如何工作的。

While it is certainly possible to implement a single Validator class to validate each of the nested objects in a rich object, it may be better to encapsulate the validation logic for each nested class of object in its own Validator implementation. A simple example of a 'rich' object would be a Customer that is composed of two String properties (a first and second name) and a complex Address object. Address objects may be used independently of Customer objects, and so a distinct AddressValidator has been implemented. If you want your CustomerValidator to reuse the logic contained within the AddressValidator class without resorting to copy-and-paste, you can dependency-inject or instantiate an AddressValidator within your CustomerValidator, and use it like so:

可以单独实现一个简单的Validator类用于验证每个富object中的内置object,最好将对于内置object的验证逻辑压缩到一个自有的Validator实现中。一个富object的简单例子可以是由两个字符串属性组成的Customer(一个姓,一个名)和一个复杂的Address的object。Address的object可以独立于Customer的object,因此可以实现一个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;

    }

    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();

        }

    }

}

Validation errors are reported to the Errors object passed to the validator. In case of Spring Web MVC you can use <spring:bind/> tag to inspect the error messages, but of course you can also inspect the errors object yourself. More information about the methods it offers can be found in the javadocs.

Validation错误用于报告封装于验证器中的错误object。在spring的mvc中你可以使用<spring:bing/>标签来检查错误信息,但是你也可以自行注入错误object。更多有关方法的信息见给定的javadocs。

9.3 Resolving codes to error messages

拆解错误信息和代码的对应

We’ve talked about databinding and validation. Outputting messages corresponding to validation errors is the last thing we need to discuss. In the example we’ve shown above, we rejected the name and the age field. If we’re going to output the error messages by using a MessageSource, we will do so using the error code we’ve given when rejecting the field ('name' and 'age' in this case). When you call (either directly, or indirectly, using for example the ValidationUtils class) rejectValue or one of the other reject methods from the Errors interface, the underlying implementation will not only register the code you’ve passed in, but also a number of additional error codes. What error codes it registers is determined by the MessageCodesResolver that is used. By default, the DefaultMessageCodesResolver is used, which for example not only registers a message with the code you gave, but also messages that include the field name you passed to the reject method. So in case you reject a field using rejectValue("age", "too.darn.old"), apart from the too.darn.old code, Spring will also register too.darn.old.age and too.darn.old.age.int (so the first will include the field name and the second will include the type of the field); this is done as a convenience to aid developers in targeting error messages and suchlike.

我们讨论一下数据绑定和验证。输出的信息和相关的验证错误是我们最后需要讨论的问题。在上面的例子,我们拒绝了name和age属性。如果我们通过使用MessageSource来输出错误信息,我们可以使用错误码当我们在决绝属性时(上面例子中的name和age)。当你调用(无论是直接或间接的使用ValidationUtils类)rejectValue或其他reject方法从Error接口中,其中不只会传递你提供的代码,也包括一些其他的错误代码。这些被注册的错误代码由使用的MessageCodesResolver来决定。默认的,会使用DefaultMessageCodesResolver,不只会注册一个你给定的错误信息,也包括你传递给reject方法的属性名。因此你可以通过使用rejectValue("age", "too.darn.old")来决绝一个field而区分与too.darn.old代码,spring 将会注册too.darn.old.age和too.darn.old.age.int(因此首先包括field名,另一个会包括field的类型),这样会方便开发者用于定位错误信息。

More information on the MessageCodesResolver and the default strategy can be found online in the javadocs of MessageCodesResolver and DefaultMessageCodesResolver, respectively.

MessageCodesResolver的更多信息和默认策略可以分别查看MessageCodesResolver和DefaultMessageCodesResolver的javadocs。

9.4 Bean manipulation and the BeanWrapper

bean操作和BeanWrapper

The org.springframework.beans package adheres to the JavaBeans standard provided by Oracle. A JavaBean is simply a class with a default no-argument constructor, which follows a naming convention where (by way of an example) a property named bingoMadness would have a setter method setBingoMadness(..) and a getter method getBingoMadness(). For more information about JavaBeans and the specification, please refer to Oracle’s website ( javabeans).

org.springframework.beans包和oracle提供的javabean标准绑定。一个javabean就是一个简单的类,有一个默认的无参构造器,按照一定的参数命名规范,如有个属性名字为bingoMadness,则有set方法setBingoMadness和get方法getBingoMadness。更多有关javabean和定义的信息请参考oracle的官网。

One quite important class in the beans package is the BeanWrapper interface and its corresponding implementation ( BeanWrapperImpl). As quoted from the javadocs, the BeanWrapper offers functionality to set and get property values (individually or in bulk), get property descriptors, and to query properties to determine if they are readable or writable. Also, the BeanWrapper offers support for nested properties, enabling the setting of properties on sub-properties to an unlimited depth. Then, the BeanWrapper supports the ability to add standard JavaBeans PropertyChangeListeners and VetoableChangeListeners, without the need for supporting code in the target class. Last but not least, the BeanWrapper provides support for the setting of indexed properties. The BeanWrapper usually isn’t used by application code directly, but by the DataBinder and the BeanFactory.

在bean包中很重要的类是BeanWrapper接口和他的相关实现(BeanWrapperImpl)。引用在javadocs,BeanWrapper提供了设置和获取属性值的功能(单个或批量)、获得属性描述、查询属性来确定是否是可读或可写的。BeanWrapper也提供支持对内置的属性、允许无限制深度的设置属性的子属性。BeanWrapper支持添加JavaBeans PropertyChangeListeners和VetoableChangeListeners,不需要目标类的代码支持。BeanWrapper支持索引属性的设置。BeanWrapper通常不会再应用代码中直接使用,但是会在DataBinder和BeanFactory中使用。

The way the BeanWrapper works is partly indicated by its name: it wraps a bean to perform actions on that bean, like setting and retrieving properties.

BeanWrapper工作方式和名字暗示的一部分:他处理bean的行为,类似于设置和获得属性。

9.4.1 Setting and getting basic and nested properties

设置或获得基本和内置属性

Setting and getting properties is done using the setPropertyValue(s) and getPropertyValue(s) methods that both come with a couple of overloaded variants. They’re all described in more detail in the javadocs Spring comes with. What’s important to know is that there are a couple of conventions for indicating properties of an object. A couple of examples:

设置和获得属性由setPropertyValue和getPropertyValue方法来实现,有几个重载的方法。详细描述在javadocs中。重要的知道有一些方法用于声明object的属性。例子:

Table 9.1. Examples of properties

属性的例子

Expression

表达式

Explanation

解释

name

Indicates the property name corresponding to the methods getName() or isName() and setName(..)

声明属性name相关的方法是getName()或isName()或setName()

account.name

Indicates the nested property name of the property account corresponding e.g. to the methods getAccount().setName() or getAccount().getName()

声明account属性的name属性相对应的是getAccount().setName()或getAccount().getName()

account[2]

Indicates the third element of the indexed property account. Indexed properties can be of type array, list or other naturally ordered collection

声明是account的第三个元素。属性是数组类型、列表或其他有序的collection

account[COMPANYNAME]

Indicates the value of the map entry indexed by the key COMPANYNAME of the Map property account

声明map类型的account属性中key为COMPANYNAME的值

Below you’ll find some examples of working with the BeanWrapper to get and set properties.

下面你会发现一些使用BeanWrapper来获得或设置属性的例子

(This next section is not vitally important to you if you’re not planning to work with the BeanWrapper directly. If you’re just using the DataBinder and the BeanFactory and their out-of-the-box implementation, you should skip ahead to the section about PropertyEditors.)

(下面的内容不是很重要如果在你并不想直接使用BeanWrapper的情况下。如果你只是使用DataBinder和BeanFactory和他们的实现,你可以跳过有关PropertyEditors的内容)

Consider the following two classes:

考虑下面两个类:

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;

    }

}

The following code snippets show some examples of how to retrieve and manipulate some of the properties of instantiated Companies and Employees:

下面的代码片段展示了一些例子是如果操纵Company和Employee实例的属性值。

BeanWrapper company = new BeanWrapperImpl(new Company());

// setting the company name..

// 设置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:

// 好,我们创建director并和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

// 通过company获得managingDirector的salary

Float salary = (Float) company.getPropertyValue("managingDirector.salary");

9.4.2 Built-in PropertyEditor implementations

内置的PropertyEditor实现

Spring uses the concept of PropertyEditors to effect the conversion between an Object and a String. If you think about it, it sometimes might be handy to be able to represent properties in a different way than the object itself. For example, a Date can be represented in a human readable way (as the String '2007-14-09'), while we’re still able to convert the human readable form back to the original date (or even better: convert any date entered in a human readable form, back to Date objects). This behavior can be achieved by registering custom editors, of type java.beans.PropertyEditor. Registering custom editors on a BeanWrapper or alternately in a specific IoC container as mentioned in the previous chapter, gives it the knowledge of how to convert properties to the desired type. Read more about PropertyEditors in the javadocs of the java.beans package provided by Oracle.

spring使用PropertyEditors来影响object和字符串的转换。如果你这样考虑,有时会比较简单以不同的方式代表属性而不是object本身。例如,一个日期可以以易于读的方式来展示(例如字符串'2007-14-09'),当我们试图从原始日期类型转换为易于读取的形式(或者将任意日期转化为易于阅读的形式)。这种行为可以通过注册自定义的编辑器,类型是java.beans.PropertyEditor来实现。注册自定义编辑器在BeanWrapper上火替代之前章节中提到的特定的IOC容器,用于实现属性和目标类型的转换。详见PropertyEditors的javadocs。

A couple of examples where property editing is used in Spring:

spring中的一些例子中使用了属性编辑

    setting properties on beans is done using PropertyEditors. When mentioning java.lang.String as the value of a property of some bean you’re declaring in XML file, Spring will (if the setter of the corresponding property has a Class-parameter) use the ClassEditor to try to resolve the parameter to a Class object.

使用PropertyEditors来设置bean的属性。如果你在xml文件中指定某个bean的属性是java.lang.String类型,spring会(如果相应属性的set方法有一个Class-parameter)使用ClassEditor来试图处理一个Class object的参数。

    parsing HTTP request parameters in Spring’s MVC framework is done using all kinds of PropertyEditors that you can manually bind in all subclasses of the CommandController.

解析spring的mvc框架中的http请求参数是通过使用各种的PropertyEditors来实现的,你可以手动绑定所有CommandController的子类。

Spring has a number of built-in PropertyEditors to make life easy. Each of those is listed below and they are all located in the org.springframework.beans.propertyeditors package. Most, but not all (as indicated below), are registered by default by BeanWrapperImpl. Where the property editor is configurable in some fashion, you can of course still register your own variant to override the default one:

spring有一些内置的PropertyEditors来简化处理。每个PropertyEditors都列在下面的表中都在org.springframework.beans.propertyeditors包中。大部分不是全部(下面提到的)都是通过BeanWrapperImpl默认被注册的。当属性编辑器被设置后,你依然可以注册自定义的编辑器来覆盖默认值:

Table 9.2. Built-in PropertyEditors

内置的PropertyEditors

Class

Explanation

解释

ByteArrayPropertyEditor

Editor for byte arrays. Strings will simply be converted to their corresponding byte representations. Registered by default by BeanWrapperImpl.

byte数组的编辑器。字符串会转化为相应的字节。默认由BeanWrapperImpl注册

ClassEditor

Parses Strings representing classes to actual classes and the other way around. When a class is not found, an IllegalArgumentException is thrown. Registered by default by BeanWrapperImpl.

解析字符串指向实际的类和其他。如果类没有被找到会抛出IllegalArgumentException异常。默认由BeanWrapperImpl注册

CustomBooleanEditor

Customizable property editor for Boolean properties. Registered by default by BeanWrapperImpl, but, can be overridden by registering custom instance of it as custom editor.

可定制的属性编辑器用于boolean属性。默认由BeanWrapperImpl注册但是可以通过自定义编辑器来覆盖。

CustomCollectionEditor

Property editor for Collections, converting any source Collection to a given target Collection type.

用于Collections的属性编辑器,将任何元结合转化为给定的集合类型

CustomDateEditor

Customizable property editor for java.util.Date, supporting a custom DateFormat. NOT registered by default. Must be user registered as needed with appropriate format.

可定制属性编辑器用于java.util.Data,支持自定义日期格式。默认没有被注册。必须根据需要使用适当的格式来手动注册

CustomNumberEditor

Customizable property editor for any Number subclass like Integer, Long, Float, Double. Registered by default by BeanWrapperImpl, but can be overridden by registering custom instance of it as a custom editor.

可定制属性编辑器用于Number的子类例如Integer、Long、Float、Double。默认由BeanWrapperImpe注册但是可以被自定义的属性编辑器覆盖

FileEditor

Capable of resolving Strings to java.io.File objects. Registered by default by BeanWrapperImpl.

处理java.io.File的object的字符串。默认由BeanWrapperImplementation注册

InputStreamEditor

One-way property editor, capable of taking a text string and producing (via an intermediate ResourceEditor and Resource) an InputStream, so InputStream properties may be directly set as Strings. Note that the default usage will not close the InputStream for you! Registered by default by BeanWrapperImpl.

单向属性编辑器,可以获得文本字符串和产生输入流(通过ResourceEditor和Resource作为辅助),因此输入流可以直接设置为字符串。注意默认使用中将不会替你关闭输入流。默认由BeanWrapperImpl注册

LocaleEditor

Capable of resolving Strings to Locale objects and vice versa (the String format is [country][variant], which is the same thing the toString() method of Locale provides). Registered by default by BeanWrapperImpl.

处理本地object的字符串,反之亦然(字符串类型是[country][variant]和本地提供的toString方法一样)。默认由BeanWrapperImpl注册

PatternEditor

Capable of resolving Strings to java.util.regex.Pattern objects and vice versa.

将字符串转化为java.util.regex.Pattern,反之亦然

PropertiesEditor

Capable of converting Strings (formatted using the format as defined in the javadocs of the java.util.Properties class) to Properties objects. Registered by default by BeanWrapperImpl.

可以将字符串转化为Properties,(使用定义在java.util.Properties中javadocs说明的格式进行格式化)。默认由BeanWrapperImpl注册

StringTrimmerEditor

Property editor that trims Strings. Optionally allows transforming an empty string into a null value. NOT registered by default; must be user registered as needed.

属性编辑器可选对字符串去空格并允许转化空字符串和null值。默认没有被注册。根据需要手动注册

URLEditor

Capable of resolving a String representation of a URL to an actual URL object. Registered by default by BeanWrapperImpl.

用于处理字符串来代表URL或实际的URL的object。默认由BeanWrapperImpl注册。

Spring uses the java.beans.PropertyEditorManager to set the search path for property editors that might be needed. The search path also includes sun.bean.editors, which includes PropertyEditor implementations for types such as Font, Color, and most of the primitive types. Note also that the standard JavaBeans infrastructure will automatically discover PropertyEditor classes (without you having to register them explicitly) if they are in the same package as the class they handle, and have the same name as that class, with 'Editor' appended; for example, one could have the following class and package structure, which would be sufficient for the FooEditor class to be recognized and used as the PropertyEditor for Foo-typed properties.

spring使用java.beans.PropertyEditorManger来设置搜索路径用于属性编辑器根据需要。搜索路径也包括sun.bean.editors,包括PropertyEditor的实现为了其他类型,如Font、Color和大部分原始类型。注意编著的JavaBean架构可以自动发现PropertyEditor类(不需要你来手动注册)如果在相同的包中作为类来处理,或有相同的类名,然后以Editor结尾,例如,可以有如下的类和包结构将足够对于FooEditor类来识别用于Foo类型属性的属性编辑。

com

  chank

    pop

      Foo

      FooEditor // the PropertyEditor for the Foo class

Note that you can also use the standard BeanInfo JavaBeans mechanism here as well (described in not-amazing-detail here). Find below an example of using the BeanInfo mechanism for explicitly registering one or more PropertyEditor instances with the properties of an associated class.

注意你也可以使用标准的BeanInfo JavaBean的策略(。。。)。见下面这个使用BeanInfo策略的例子用于注册一个或多个属性编辑器实例相对于相关类。

com

  chank

    pop

      Foo

      FooBeanInfo // the BeanInfo for the Foo class

Here is the Java source code for the referenced FooBeanInfo class. This would associate a CustomNumberEditor with the age property of the Foo class.

这是java源代码参考FooBeanInfo类。下面的例子会将CustomNumberEditor和Foo类的age属性联系在一起。

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());

        }

    }

}

Registering additional custom PropertyEditors

注册额外的自定义属性编辑器

When setting bean properties as a string value, a Spring IoC container ultimately uses standard JavaBeans PropertyEditors to convert these Strings to the complex type of the property. Spring pre-registers a number of custom PropertyEditors (for example, to convert a classname expressed as a string into a real Class object). Additionally, Java’s standard JavaBeans PropertyEditor lookup mechanism allows a PropertyEditor for a class simply to be named appropriately and placed in the same package as the class it provides support for, to be found automatically.

当设置一个bean的属性为字符串时,spring的ioc容器会使用基本的JavaBean属性编辑器来将字符串转化为复杂的属性类型。spring会事先注册一些自定义的属性编辑器(例如,将一个类名表达为一个字符串设置到实际的class的object中)。此外,java的标砖JavaBeans属性编辑器的查询策略允许一个类的属性编辑器简单被命名并并在同一个包中定义,以便可以自动被发现。

If there is a need to register other custom PropertyEditors, there are several mechanisms available. The most manual approach, which is not normally convenient or recommended, is to simply use the registerCustomEditor() method of the ConfigurableBeanFactory interface, assuming you have a BeanFactory reference. Another, slightly more convenient, mechanism is to use a special bean factory post-processor called CustomEditorConfigurer. Although bean factory post-processors can be used with BeanFactory implementations, the CustomEditorConfigurer has a nested property setup, so it is strongly recommended that it is used with the ApplicationContext, where it may be deployed in similar fashion to any other bean, and automatically detected and applied.

如果需要注册其他的自定义属性编辑器则有一些策略可用。其中最手动的方法不是很方便也不被推荐使用,就是简单的使用ConfigurableBeanFactory接口的registerCustomEditor方法,假设你有一个BeanFactory的引用。另外,稍微有些方便的策略是使用特定bean工厂后处理器名为CustomEditorConfigurer。尽管bean工厂后处理器可以用于BeanFactory的实现,CustomEditorConfigurer有一个内置属性的设置,因此强烈推荐可以使用在ApplicationContext中,可以和其他bean一样以相似的方式部署并可以自动被探测和应用。

Note that all bean factories and application contexts automatically use a number of built-in property editors, through their use of something called a BeanWrapper to handle property conversions. The standard property editors that the BeanWrapper registers are listed in the previous section. Additionally, ApplicationContexts also override or add an additional number of editors to handle resource lookups in a manner appropriate to the specific application context type.

注意所有的bean工厂和应用上下文自动使用一定数量的内置属性编辑器,尽管他们使用的一些叫BeanWrapper用于处理属性转换。BeanWrapper注册的标准的属性编辑器在前一节中已经列出。此外应用上下文也可以覆盖和添加额外的编辑器用于处理资源的查找在管理以适应特定的应用上下文类型。

Standard JavaBeans PropertyEditor instances are used to convert property values expressed as strings to the actual complex type of the property. CustomEditorConfigurer, a bean factory post-processor, may be used to conveniently add support for additional PropertyEditor instances to an ApplicationContext.

标准的JavaBeans属性编辑器实例可以用于将属性值表达为字符串用于实际复杂的属性类型中。bean的工厂后处理器,CustomEditorConfigurer可以方便用于支持应用上下文中的属性编辑器实例。

Consider a user class ExoticType, and another class DependsOnExoticType which needs ExoticType set as a property:

考虑一个用户类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;

    }

}

When things are properly set up, we want to be able to assign the type property as a string, which a PropertyEditor will behind the scenes convert into an actual ExoticType instance:

当被正确的设置好,我们希望指定属性的类型是字符串,PropertyEditor在后面会将其转化为ExoticType实例。

<bean id="sample" class="example.DependsOnExoticType">

    <property name="type" value="aNameForExoticType"/>

</bean>

The PropertyEditor implementation could look similar to this:

PropertyEditor实现看上去是这样的:

// converts string representation to ExoticType object

// 转化字符串为ExoticType的object

package example;

public class ExoticTypeEditor extends PropertyEditorSupport {

    public void setAsText(String text) {

        setValue(new ExoticType(text.toUpperCase()));

    }

}

Finally, we use CustomEditorConfigurer to register the new PropertyEditor with the ApplicationContext, which will then be able to use it as needed:

最后,我们使用应用上下文的CustomEditorConfigurer来注册新的属性编辑器,可以根据需要来使用:

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">

    <property name="customEditors">

        <map>

            <entry key="example.ExoticType" value="example.ExoticTypeEditor"/>

        </map>

    </property>

</bean>

Using PropertyEditorRegistrars

使用PropertyEditorRegistrars

Another mechanism for registering property editors with the Spring container is to create and use a PropertyEditorRegistrar. This interface is particularly useful when you need to use the same set of property editors in several different situations: write a corresponding registrar and reuse that in each case. PropertyEditorRegistrars work in conjunction with an interface called PropertyEditorRegistry, an interface that is implemented by the Spring BeanWrapper (and DataBinder). PropertyEditorRegistrars are particularly convenient when used in conjunction with the CustomEditorConfigurer (introduced here), which exposes a property called setPropertyEditorRegistrars(..): PropertyEditorRegistrars added to a CustomEditorConfigurer in this fashion can easily be shared with DataBinder and Spring MVC Controllers. Furthermore, it avoids the need for synchronization on custom editors: a PropertyEditorRegistrar is expected to create fresh PropertyEditor instances for each bean creation attempt.

spirng容器中其他注册属性编辑器的策略是创建和使用PropertyEditorRegistrar。当你需要在不同的环境中使用相同的一些属性编辑器时还是很有用的接口:创建一个相关的注册者然后在每个类中重复使用。PropertyEditorRegistrars和PropertyEditorRegistry接口同时使用,一个接口的实现是spring的BeanWrapper(和DataBinder)。PropertyEditorRegistrars当和CustomEditorConfigurer一起使用时是很方便的(在这里介绍),暴露了一个属性名为setPropertyEditorRegistrars:PropertyEditorRegistrars和CustomEditorConfigurer结合使用可以简单的在DataBinder和spring的MVC控制之间共享。进一步说,他避免了自定义编辑器对于同步的使用:PropertyEditorRegistrar试图对每个bean创建PropertyEditor的实例。

Using a PropertyEditorRegistrar is perhaps best illustrated with an example. First off, you need to create your own PropertyEditorRegistrar implementation:

使用PropertyEditorRegistrar或许是最好的方法在这个例子中。首先,你需要创建你自己的PropertyEditorRegistrar实现:

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

// 创建一个新的PropertyEditor实例

        registry.registerCustomEditor(ExoticType.class, new ExoticTypeEditor());

        // you could register as many custom property editors as are required here...

// 你可以注册许多自定义属性编辑器根据要求

    }

}

See also the org.springframework.beans.support.ResourceEditorRegistrar for an example PropertyEditorRegistrar implementation. Notice how in its implementation of the registerCustomEditors(..) method it creates new instances of each property editor.

也可参考org.springframework.beans.support.ResourceEditorRegistrar作为一个PropertyEditorRegistrar实现的例子。注意实现中的registerCustomEditors方法创建每个属性编辑器的新实例。

Next we configure a CustomEditorConfigurer and inject an instance of our CustomPropertyEditorRegistrar into it:

下面我们配置一个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"/>

Finally, and in a bit of a departure from the focus of this chapter, for those of you using Spring’s MVC web framework, using PropertyEditorRegistrars in conjunction with data-binding Controllers (such as SimpleFormController) can be very convenient. Find below an example of using a PropertyEditorRegistrar in the implementation of an initBinder(..) method:

最后,暂时脱离一下本节,在你使用spring的mvc的web框架时,一起使用PropertyEditorRegistrars和数据绑定控制器会更加方便。下面的例子中使用PropertyEditorRegistrar并实现了initBinder方法:

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

// 其他方法用于注册User

}

This style of PropertyEditor registration can lead to concise code (the implementation of initBinder(..) is just one line long!), and allows common PropertyEditor registration code to be encapsulated in a class and then shared amongst as many Controllers as needed.

这种使用PropertyEditor注册的风格可以简化代码(initBinder的实现只是一部分),允许通用的PropertyEditor注册代码在一个类中然后根据需要在许多控制器间共享。

9.5 Spring Type Conversion

spring的类型转换

Spring 3 introduces a core.convert package that provides a general type conversion system. The system defines an SPI to implement type conversion logic, as well as an API to execute type conversions at runtime. Within a Spring container, this system can be used as an alternative to PropertyEditors to convert externalized bean property value strings to required property types. The public API may also be used anywhere in your application where type conversion is needed.

spirng3介绍了core.convert包用于提供通用类型的转换系统。系统定义了SPI来实现类型转换逻辑,和API在运行时执行类型转换一样。在spring的容器中,这个系统可以用于PropertyEditors来转化属性字符串到需要的属性类型。公共API也可以在你应用中使用当需要类型转换时。

9.5.1 Converter SPI

The SPI to implement type conversion logic is simple and strongly typed:

SPI用于类型转换的逻辑时简单的而且是强类型的:

package org.springframework.core.convert.converter;

public interface Converter<S, T> {

    T convert(S source);

}

To create your own converter, simply implement the interface above. Parameterize S as the type you are converting from, and T as the type you are converting to. Such a converter can also be applied transparently if a collection or array of S needs to be converted to an array or collection of T, provided that a delegating array/collection converter has been registered as well (which DefaultConversionService does by default).

创建你自己的转化器需要实现上面的接口。参数S是你需要转换的类型,并且T是转换后的类型。这个转化器也可以应用在集合或数组上用于从S转换为T,提供了一个数组和集合转化器也被注册(默认是DefaultConversionService)。

For each call to convert(S), the source argument is guaranteed to be NOT null. Your Converter may throw any unchecked exception if conversion fails; specifically, an IllegalArgumentException should be thrown to report an invalid source value. Take care to ensure that your Converter implementation is thread-safe.

对于每次调用转化,源参数不可以是null。如果转换失败该方法可以抛出任何非检查异常:特殊的,一个IllegalArgumentException应该被抛出来反映一个错误的原数据值。保证你的转换实现是线程安全的。

Several converter implementations are provided in the core.convert.support package as a convenience. These include converters from Strings to Numbers and other common types. Consider StringToInteger as an example for a typical Converter implementation:

一些转换器的实现在core.convert.support包中被提供作为方便。包括将字符串转化为数字和其他常用的类型转换。考虑StringToInteger作为一个列子描述典型的转换器实现:

package org.springframework.core.convert.support;

final class StringToInteger implements Converter<String, Integer> {

    public Integer convert(String source) {

        return Integer.valueOf(source);

    }

}

9.5.2 ConverterFactory

转换工厂

When you need to centralize the conversion logic for an entire class hierarchy, for example, when converting from String to java.lang.Enum objects, implement ConverterFactory:

当你需要集中转换逻辑用于这个类的层级,例如,当将字符串转换为java.lang.Enum的object时,可以实现ConverterFactory。

package org.springframework.core.convert.converter;

public interface ConverterFactory<S, R> {

    <T extends R> Converter<S, T> getConverter(Class<T> targetType);

}

Parameterize S to be the type you are converting from and R to be the base type defining the range of classes you can convert to. Then implement getConverter(Class<T>), where T is a subclass of R.

参数S是你需要转换的类型,R是你需要转换的类型范围。getConverter实现中T是R的一个子类。

Consider the StringToEnum ConverterFactory as an example:

考虑StringToEnum的ConverterFactory作为一个例子:

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());

        }

    }

}

9.5.3 GenericConverter

通用转换器

When you require a sophisticated Converter implementation, consider the GenericConverter interface. With a more flexible but less strongly typed signature, a GenericConverter supports converting between multiple source and target types. In addition, a GenericConverter makes available source and target field context you can use when implementing your conversion logic. Such context allows a type conversion to be driven by a field annotation, or generic information declared on a field signature.

当你需要一个复杂的转换器实现,考虑一下GenericConverter接口。更加方便但是没有强类型标识,支持多个源码到目标类型的转换。此外,GenericConverter使得当你实现你的转换逻辑时使用源码和目标域。这样的上下文允许一个类型转换通过域注解或定义在域中通用的信息。

package org.springframework.core.convert.converter;

public interface GenericConverter {

    public Set<ConvertiblePair> getConvertibleTypes();

    Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);

}

To implement a GenericConverter, have getConvertibleTypes() return the supported source→target type pairs. Then implement convert(Object, TypeDescriptor, TypeDescriptor) to implement your conversion logic. The source TypeDescriptor provides access to the source field holding the value being converted. The target TypeDescriptor provides access to the target field where the converted value will be set.

实现GenericConverter,getConvertibleTypes需要返回支持源类型到目标类型的对。然后实现convert方法来实现你的转换逻辑。源TypeDescriptor提供访问被转换的元域的值。目标TypeDescriptor提供访问转换后的目标域的值,该值可以被设置。

A good example of a GenericConverter is a converter that converts between a Java Array and a Collection. Such an ArrayToCollectionConverter introspects the field that declares the target Collection type to resolve the Collection’s element type. This allows each element in the source array to be converted to the Collection element type before the Collection is set on the target field.

一个比较好的GenericConverter例子就是Java数组到集合的转换。这样的ArrayToCollectionConverter自省定义在目标集合类型的域用于处理集合元素类型。允许每个在源数组的元素被转换为集合元素类型在结合被设置到目标域之前。

[Note]

注意

Because GenericConverter is a more complex SPI interface, only use it when you need it. Favor Converter or ConverterFactory for basic type conversion needs.

因为GenericConverter是一个比较复杂的SPI接口,当你需要的时候才会使用。倾向Converter或ConverterFactory用于基本类型转换根据需要。

ConditionalGenericConverter

Sometimes you only want a Converter to execute if a specific condition holds true. For example, you might only want to execute a Converter if a specific annotation is present on the target field. Or you might only want to execute a Converter if a specific method, such as a static valueOf method, is defined on the target class. ConditionalGenericConverter is the union of the GenericConverter and ConditionalConverter interfaces that allows you to define such custom matching criteria:

有时你希望转换器在特定条件下进行转换。例如,你或许希望执行转换如果一个特定注解在目标域上使用时。或许你只是希望执行转换如果一个特定的方法例如静态的valueOf方法被定义在目标类中。ConditionalGenericConverter就是一个GenericConverter和ConditionalConverter的组合接口允许你定义自定义的匹配逻辑条件:

public interface ConditionalGenericConverter

        extends GenericConverter, ConditionalConverter {

    boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);

}

A good example of a ConditionalGenericConverter is an EntityConverter that converts between an persistent entity identifier and an entity reference. Such a EntityConverter might only match if the target entity type declares a static finder method e.g. findAccount(Long). You would perform such a finder method check in the implementation of matches(TypeDescriptor, TypeDescriptor).

ConditionalGenericConverter的一个好例子是EntityConverter用于转换一个持久化实体定义为一个实体引用。例如EntityConverter可以只匹配目标实体类型定义一个静态的finder方法例如findAccount(Long)。你或许可以使finder方法检测定义在matches(TypeDescriptor, TypeDescriptor)的实现中。

9.5.4 ConversionService API

转换服务接口

The ConversionService defines a unified API for executing type conversion logic at runtime. Converters are often executed behind this facade interface:

ConversionService定义了一个统一的API用于在运行时执行类型转换逻辑。Converters经常执行在此接口的后面:

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);

}

Most ConversionService implementations also implement ConverterRegistry, which provides an SPI for registering converters. Internally, a ConversionService implementation delegates to its registered converters to carry out type conversion logic.

大部分ConversionService实现也实现了ConverterRegistry,用于提供SPI来注册转换器。在其内部,ConversionService实现委托了注册机制给他的注册转换器用于处理类型转换逻辑。

A robust ConversionService implementation is provided in the core.convert.support package. GenericConversionService is the general-purpose implementation suitable for use in most environments. ConversionServiceFactory provides a convenient factory for creating common ConversionService configurations.

一个强健的ConversionService实现在core.convert.support包中被提供。GenericConversionService是一个通用的实现可以用于大部分的情况。ConversionServiceFactory提供了一个方便的工厂用于创建普通的ConversionService配置。

9.5.5 Configuring a ConversionService

配置一个ConversionService

A ConversionService is a stateless object designed to be instantiated at application startup, then shared between multiple threads. In a Spring application, you typically configure a ConversionService instance per Spring container (or ApplicationContext). That ConversionService will be picked up by Spring and then used whenever a type conversion needs to be performed by the framework. You may also inject this ConversionService into any of your beans and invoke it directly.

一个ConversionService是一个无状态的object设计加载应用启动时被实例化,在多个线程间被共享。

[Note]

注意

If no ConversionService is registered with Spring, the original PropertyEditor-based system is used.

如果spring没有注册ConversionService,则原始的基于属性编辑器的系统将被使用。

To register a default ConversionService with Spring, add the following bean definition with id conversionService:

为了使用spring注册默认的ConversionService,需要添加id为conversionService的如下bean定义

<bean id="conversionService"

    class="org.springframework.context.support.ConversionServiceFactoryBean"/>

A default ConversionService can convert between strings, numbers, enums, collections, maps, and other common types. To supplement or override the default converters with your own custom converter(s), set the converters property. Property values may implement either of the Converter, ConverterFactory, or GenericConverter interfaces.

一个默认的ConversionService可以在字符串、数组、枚举、集合、map和其他通用类型间实现转换。为了补充或覆盖默认的转化器使用自定义的转化器,需要设置converters属性。属性值是一个实现Converter或ConverterFactory或GenericConverter接口的某个类。

<bean id="conversionService"

        class="org.springframework.context.support.ConversionServiceFactoryBean">

    <property name="converters">

        <set>

            <bean class="example.MyCustomConverter"/>

        </set>

    </property>

</bean>

It is also common to use a ConversionService within a Spring MVC application. See Section 22.16.3, “Conversion and Formatting” in the Spring MVC chapter.

在spring的mvc应用中使用GenericConverter也很常见。见22.16.3节,“转换和格式化”在spring的mvc章节。

In certain situations you may wish to apply formatting during conversion. See Section 9.6.3, “FormatterRegistry SPI” for details on using FormattingConversionServiceFactoryBean.

在特定的情况你或许希望在转换中使用格式化。见9.6.3节,“FormatterRegistry SPI”中的详细内容使用FormattingConversionServiceFactoryBean。

9.5.6 Using a ConversionService programmatically

编程使用ConversionService

To work with a ConversionService instance programmatically, simply inject a reference to it like you would for any other bean:

为了编程使用ConversionService实例,简单在一个bean中注入一个引用如下:

@Service

public class MyService {

    @Autowired

    public MyService(ConversionService conversionService) {

        this.conversionService = conversionService;

    }

    public void doIt() {

        this.conversionService.convert(...)

    }

}

For most use cases, the convert method specifying the targetType can be used but it will not work with more complex types such as a collection of a parameterized element. If you want to convert a List of Integer to a List of String programmatically, for instance, you need to provide a formal definition of the source and target types.

在大部分使用方式中,转换方法定义了目标类型可以被使用但是但是在一些参数化元素的集合类型这样复杂的类型可能无法工作。如果你希望编程转换一个Integer的List到一个字符串的List,例如,你需要提供一个格式定义对源类型和目标类型。

Fortunately, TypeDescriptor provides various options to make that straightforward:

幸运的是,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)));

Note that DefaultConversionService registers converters automatically which are appropriate for most environments. This includes collection converters, scalar converters, and also basic Object to String converters. The same converters can be registered with any ConverterRegistry using the static addDefaultConverters method on the DefaultConversionService class.

注意DefaultConversionService自动注册转换器可以使用于大部分的情况。这里包括集合转换、标量转换和基本object到字符串转化。通过ConverterRegistry同样的转换可以被注册使用DefaultConversionService类中的静态的addDefaultConverters方法。

Converters for value types will be reused for arrays and collections, so there is no need to create a specific converter to convert from a Collection of S to a Collection of T, assuming that standard collection handling is appropriate.

用于值类型的转换可以重用在数组和集合中,因此不需要创建特定的转换器用于转换一个集合S到另一个集合T,假设标准集合处理是合适的。

9.6 Spring Field Formatting

spring的域格式化

As discussed in the previous section, core.convert is a general-purpose type conversion system. It provides a unified ConversionService API as well as a strongly-typed Converter SPI for implementing conversion logic from one type to another. A Spring Container uses this system to bind bean property values. In addition, both the Spring Expression Language (SpEL) and DataBinder use this system to bind field values. For example, when SpEL needs to coerce a Short to a Long to complete an expression.setValue(Object bean, Object value) attempt, the core.convert system performs the coercion.

在前面的几节中讨论,core.convert是一个通用的类型转换系统。他提供特定的ConversionService的API和强健的类型转换器实现用于实现一个类型到另一个类型的转换。一个spring的容器使用这个系统来绑定bean的属性值。此外,spring的表达式语言和DataBinder使用这个系统来绑定属性值。例如,当SpEL需要迫使一个Short类型转换为Long类型用于试图完成expression.setValue(Object bean, Object value),那这个系统可以提供的功能。

Now consider the type conversion requirements of a typical client environment such as a web or desktop application. In such environments, you typically convert from String to support the client postback process, as well as back to String to support the view rendering process. In addition, you often need to localize String values. The more general core.convert Converter SPI does not address such formatting requirements directly. To directly address them, Spring 3 introduces a convenient Formatter SPI that provides a simple and robust alternative to PropertyEditors for client environments.

现在考虑典型客户端环境下的类型转换例如作为一个web或桌面应用。在这样的环境下,你通常需要转换字符串来支持客户端回传程序,也包括转换成为字符串用于支持视图表现程序。此外,你也需要本地化字符串值。通常的转化器SPI没有直接进行直接的格式转换根据需要。为了实现这个功能,spring3加入了方便的格式化SPI提供简单强健的的属性编辑器用于客户端环境。

In general, use the Converter SPI when you need to implement general-purpose type conversion logic; for example, for converting between a java.util.Date and and java.lang.Long. Use the Formatter SPI when you’re working in a client environment, such as a web application, and need to parse and print localized field values. The ConversionService provides a unified type conversion API for both SPIs.

通常,当你需要事项通用类型转换时使用转换器SPI;例如,为了方便将java.util.Date转换为java.lang.Long。当你在客户端环境使用格式化SPI,例如作为一个web营养共,你需要格式化和打印本地化的属性值。ConversionService提供了统一的类型转换API用于所有的SPI。

9.6.1 Formatter SPI

The Formatter SPI to implement field formatting logic is simple and strongly typed:

Formatter的SPI用于实现域格式化逻辑是简单而且强类型的:

package org.springframework.format;

public interface Formatter<T> extends Printer<T>, Parser<T> {

}

Where Formatter extends from the Printer and Parser building-block interfaces:

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;

}

To create your own Formatter, simply implement the Formatter interface above. Parameterize T to be the type of object you wish to format, for example, java.util.Date. Implement the print() operation to print an instance of T for display in the client locale. Implement the parse() operation to parse an instance of T from the formatted representation returned from the client locale. Your Formatter should throw a ParseException or IllegalArgumentException if a parse attempt fails. Take care to ensure your Formatter implementation is thread-safe.

为了创建你自己的Formatter,需要实现上面的Formatter接口。参数T类型是你需要格式化的类型,例如,java.util.Data。实现print方法用于打印T的实例在客户端本地。实现parse方法用于将T实例从客户端本地格式化。你的Formatter应该抛出ParseException或IllegalArgumentException如果格式化尝试失败的时候。注意保证你的Formatter是线程安全的。

Several Formatter implementations are provided in format subpackages as a convenience. The number package provides a NumberFormatter, CurrencyFormatter, and PercentFormatter to format java.lang.Number objects using a java.text.NumberFormat. The datetime package provides a DateFormatter to format java.util.Date objects with a java.text.DateFormat. The datetime.joda package provides comprehensive datetime formatting support based on the Joda Time library.

一些Formatter实现提供在format子包中作为方便使用。该Number子包中提供了NumberFormatter、CurrencyFormatter和PercentFormatter用于格式化java.lang.Number通过使用java.text.NumberFormat。datetime子包中提供了DateFormatter用于格式化java.util.Data通过使用java.text.DataFormat。datetime.joda子包中提供了复杂的日期转化用于支持Joda的时间包。

Consider DateFormatter as an example Formatter implementation:

考虑Formatter的实现DataFormatter作为一个例子:

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;

    }

}

The Spring team welcomes community-driven Formatter contributions; see jira.spring.io to contribute.

spring小组欢迎社区的格式化贡献:见jira.spring.io用于贡献。

9.6.2 Annotation-driven Formatting

基于注解的格式化

As you will see, field formatting can be configured by field type or annotation. To bind an Annotation to a formatter, implement AnnotationFormatterFactory:

你看到域格式化可以通过域类型或注解来配置。为了将注解绑定到formatter上需要实现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);

}

Parameterize A to be the field annotationType you wish to associate formatting logic with, for example org.springframework.format.annotation.DateTimeFormat. Have getFieldTypes() return the types of fields the annotation may be used on. Have getPrinter() return a Printer to print the value of an annotated field. Have getParser() return a Parser to parse a clientValue for an annotated field.

参数A用于域注解类型你希望可以和格式化逻辑相关联,例如org.springframework.format.annotation.DateTimeFormat。getFieldTypes方法可以返回使用的注解域的类型。getPrinter可以返回一个Printer用于打印注解域的值。getParser方法可以返回一个Parser用于格式化用于注解域的clientValue。

The example AnnotationFormatterFactory implementation below binds the @NumberFormat Annotation to a formatter. This annotation allows either a number style or pattern to be specified:

下面实现AnnotationFormatterFactory的类将NumberFormatter注解和格式化器绑定。注解允许number类型或特定的格式:

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 NumberFormatter(annotation.pattern());

        } else {

            Style style = annotation.style();

            if (style == Style.PERCENT) {

                return new PercentFormatter();

            } else if (style == Style.CURRENCY) {

                return new CurrencyFormatter();

            } else {

                return new NumberFormatter();

            }

        }

    }

}

To trigger formatting, simply annotate fields with @NumberFormat:

为了触发格式化,简单声明@NumberFormat注解在域中:

public class MyModel {

    @NumberFormat(style=Style.CURRENCY)

    private BigDecimal decimal;

}

Format Annotation API

格式化注解API

A portable format annotation API exists in the org.springframework.format.annotation package. 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.

方便的格式化注解API在org.springframework.format.annotation包中提供。使用@NumberFormat用于格式化java.lang.Number域。使用@DateTimeFormat用于格式化java.util.Date、java.util.Calendar、java.util.Long或Joda时间域,

The example below uses @DateTimeFormat to format a java.util.Date as a ISO Date (yyyy-MM-dd):

下面的例子展示了使用@DateTimeFormate来将java.util.Date格式化为ISO日期(yyyy-MM-dd):

public class MyModel {

    @DateTimeFormat(iso=ISO.DATE)

    private Date date;

}

9.6.3 FormatterRegistry SPI

The FormatterRegistry is an SPI for registering formatters and converters. FormattingConversionService is an implementation of FormatterRegistry suitable for most environments. This implementation may be configured programmatically or declaratively as a Spring bean using FormattingConversionServiceFactoryBean. Because this implementation also implements ConversionService, it can be directly configured for use with Spring’s DataBinder and the Spring Expression Language (SpEL).

FormatterRegistry是一个SPI用于注册格式化器和转换器。FormattingConversionService是FormatterRegistry的实现用于大部分环境。该实现可以通过编程配置或定义为一个spring的bean通过使用FormattingConversionServiceFactoryBean。因为这个实现也实现了ConversionService,可以直接配置用于spring的DataBinder和spring的表达式语言。

Review the FormatterRegistry SPI below:

回忆下面的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);

}

As shown above, Formatters can be registered by fieldType or annotation.

就像上面展示的,可以通过域类型或注解来注册格式化器。

The FormatterRegistry SPI allows you to configure Formatting rules centrally, instead of duplicating such configuration across your Controllers. For example, you might want to enforce that all Date fields are formatted a certain way, or fields with a specific annotation are formatted in a certain way. With a shared FormatterRegistry, you define these rules once and they are applied whenever formatting is needed.

FormatterRegistry的SPI允许你配置格式化规则,避免跨控制器的重复配置。例如,你可以强制所有的日期域格式化为一个特定的形式或有特定注解的域格式化为特定的形式。通过共享FormatterRegistry,你可以一次定义这些规则并将它们应用于你需要的位置。

9.6.4 FormatterRegistrar SPI

The FormatterRegistrar is an SPI for registering formatters and converters through the FormatterRegistry:

FormatterRegistrar作为一个SPI用于注册格式化器和转换器通过FormatterRegistry:

package org.springframework.format;

public interface FormatterRegistrar {

    void registerFormatters(FormatterRegistry registry);

}

A FormatterRegistrar is useful when registering multiple related converters and formatters for a given formatting category, such as Date formatting. It can also be useful where declarative registration is insufficient. For example when a formatter needs to be indexed under a specific field type different from its own <T> or when registering a Printer/Parser pair. The next section provides more information on converter and formatter registration.

FormatterRegistrar是有用的当注册多个相关的转换器和格式化器用于给定的格式化种类,例如日期格式化。在直接注册不能实现时可以很有用。例如当一个格式化去需要被索引在特定的域类型下不同于T类型或当注册Printer/Parser对时使用。下一节将展示更多的有关转换器和格式化器注册的内容。

9.6.5 Configuring Formatting in Spring MVC

在spring的mvc中配置格式化

See Section 22.16.3, “Conversion and Formatting” in the Spring MVC chapter.

见22.16.3节,“转换和格式化”在spring的mvc章节。

9.7 Configuring a global date & time format

配置全局的日期和时间格式

By default, date and time fields that are not annotated with @DateTimeFormat are converted from strings using the DateFormat.SHORT style. If you prefer, you can change this by defining your own global format.

默认,日期和时间域没有使用@DateTimeFormat来修饰可以使用DateFormat.SHORT的风格从字符串来转化。如果你需要,你可以通过定义你自己的全局格式来改变。

You will need to ensure that Spring does not register default formatters, and instead you should register all formatters manually. Use the org.springframework.format.datetime.joda.JodaTimeFormatterRegistrar or org.springframework.format.datetime.DateFormatterRegistrar class depending on whether you use the Joda Time library.

你将会需要确保spring没有注册默认的格式化器,作为代替你需要手动注册所有的格式化去。使用org.springframework.format.datetime.joda.JodaTimeFormatterRegistrar或org.springframework.format.datetime.DateFormatterRegistrar类根据你是否使用Joda时间库。

For example, the following Java configuration will register a global ' `yyyyMMdd’ format. This example does not depend on the Joda Time library:

例如,下面的Java配置注册了一个全局的“yyyyMMdd”格式。这个例子不需要依赖Joda时间库。

@Configuration

public class AppConfig {

    @Bean

    public FormattingConversionService conversionService() {

        // Use the DefaultFormattingConversionService but do not register defaults

// 使用DefaultFormattingConversionService但是没有将其注册为默认

        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(false);

        // Ensure @NumberFormat is still supported

// 依然支持@NumberFormat

        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;

    }

}

If you prefer XML based configuration you can use a FormattingConversionServiceFactoryBean. Here is the same example, this time using Joda Time:

如果你倾向于基于xml的配置你可以使用FormattingConversionServiceFactoryBean。这里给一个例子,其中这里的时间使用了Joda时间库:

<?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

        http://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>

[Note]

注意

Joda Time provides separate distinct types to represent date, time and date-time values. The dateFormatter, timeFormatter and dateTimeFormatter properties of the JodaTimeFormatterRegistrar should be used to configure the different formats for each type. The DateTimeFormatterFactoryBean provides a convenient way to create formatters.

Joda时间提供了分割的域来代表日期、时间和日期时间值。JodaTimeFormatterRegistrar类的dateFormatter、timeFormatter和dateTimeFormatter属性应该被使用用于配置不同的类型的格式。DateTimeFormatterFactoryBean提供了一个方便的方式用于创建格式化器。

If you are using Spring MVC remember to explicitly configure the conversion service that is used. For Java based @Configuration this means extending the WebMvcConfigurationSupport class and overriding the mvcConversionService() method. For XML you should use the 'conversion-service' attribute of the mvc:annotation-driven element. See Section 22.16.3, “Conversion and Formatting” for details.

如果你使用spring的mvc,记住明确配置需要使用的转换服务。对于@Configuration注解来说这意味着扩展WebMvcConfigurationSupport类和覆盖mvcConversionService方法。对于使用xml,你应当使用mvc:annotation-driven元素的conversion-service属性。见22.16.3,“转换和格式化”。

9.8 Spring Validation

spring的验证

Spring 3 introduces several enhancements to its validation support. First, the JSR-303 Bean Validation API is now fully supported. Second, when used programmatically, Spring’s DataBinder can now validate objects as well as bind to them. Third, Spring MVC now has support for declaratively validating @Controller inputs.

spring3增加一些特性用于支持验证。首先,JSR303的bean验证api是全部支持的。其次,当编程使用验证时,spring的DataBinder可以验证object并绑定他们。第三,spring的mvc现在支持在@Controller中输入验证内容。

9.8.1 Overview of the JSR-303 Bean Validation API

JSR303的bean验证api的概述

JSR-303 standardizes validation constraint declaration and metadata for the Java platform. Using this API, you annotate domain model properties with declarative validation constraints and the runtime enforces them. There are a number of built-in constraints you can take advantage of. You may also define your own custom constraints.

JSR303标准提供验证约束定义和元数据用于java平台。使用这个api,你使用显示的验证约束声明域模型并且运行时会强制执行他们。有很多内置的验证约束需要关注。你也可以定义你自己的自定义约束。

To illustrate, consider a simple PersonForm model with two properties:

为了说明,考虑一个有两个属性的简单的PersonForm模型:

public class PersonForm {

    private String name;

    private int age;

}

JSR-303 allows you to define declarative validation constraints against such properties:

JSR303允许你定义显示的验证约束在这些属性中:

public class PersonForm {

    @NotNull

    @Size(max=64)

    private String name;

    @Min(0)

    private int age;

}

When an instance of this class is validated by a JSR-303 Validator, these constraints will be enforced.

当这个实例在JSR303验证器下被验证,这些约束会被强制执行。

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. To learn how to setup a Bean Validation provider as a Spring bean, keep reading.

在JSR303或JSR349中,参考bean验证的网站。关于特定的功能对于默认的引用实现,见Hibernate的验证文档。为了给spring的bean提供bean验证,请继续阅读。

9.8.2 Configuring a Bean Validation Provider

配置bean验证提供者

Spring provides full support for the Bean Validation API. This includes convenient support for bootstrapping a JSR-303/JSR-349 Bean Validation provider as a Spring bean. This allows for a javax.validation.ValidatorFactory or javax.validation.Validator to be injected wherever validation is needed in your application.

spring提供全面的支持对于bean验证的api。包括方便支持JSR303或JSR349的bean提供者用于spring的bean。这允许javax.validation.ValidatorFactory或javax.validation.Validator可以注入你需要在你的应用中使用验证的位置。

Use the LocalValidatorFactoryBean to configure a default Validator as a Spring bean:

使用LocalValidatorFactoryBean为spring的bean配置默认的验证器:

<bean id="validator"

    class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>

The basic configuration above will trigger Bean Validation to initialize using its default bootstrap mechanism. A JSR-303/JSR-349 provider, such as Hibernate Validator, is expected to be present in the classpath and will be detected automatically.

上面的配置将会在初始化时激活bean的验证,使用默认的启动策略。JSR303或JSR349的提供,例如Hibernate的验证器,被期望出现在classpath中并且会自动被探测。

Injecting a Validator

注入一个验证器

LocalValidatorFactoryBean implements both javax.validation.ValidatorFactory and javax.validation.Validator, as well as Spring’s org.springframework.validation.Validator. You may inject a reference to either of these interfaces into beans that need to invoke validation logic.

LocalValidatorFactoryBean实现了javax.validation.ValidatorFactory和javax.validation.Validator接口,也包括Spring的org.springframework.validation.Validator。你可以注入任何一个上面三个接口的引用到你的bean中如果你需要调用验证逻辑。

Inject a reference to javax.validation.Validator if you prefer to work with the Bean Validation API directly:

如果你希望直接使用bean验证api可以注入javax.validation.Validator的引用:

import javax.validation.Validator;

@Service

public class MyService {

    @Autowired

    private Validator validator;

Inject a reference to org.springframework.validation.Validator if your bean requires the Spring Validation API:

如果你需要spring的验证api则注入org.springframework.validation.Validator的引用:

import org.springframework.validation.Validator;

@Service

public class MyService {

    @Autowired

    private Validator validator;

}

Configuring Custom Constraints

配置自定义的约束

Each Bean Validation constraint consists of two parts. First, a @Constraint annotation that declares the constraint and its configurable properties. Second, an implementation of the javax.validation.ConstraintValidator interface that implements the constraint’s behavior. To associate a declaration with an implementation, each @Constraint annotation references a corresponding ValidationConstraint implementation class. At runtime, a ConstraintValidatorFactory instantiates the referenced implementation when the constraint annotation is encountered in your domain model.

每个bean的验证约束包括两个部分。第一,@Constraint注解声明了约束和他的可配置属性。第二,实现javax.validation.ConstraintValidator接口用于实现约束的行为。为了关联实现的定义,每个@Constraint注解引用一个相关的ValidationConstraint实现类。在运行时,一个ConstraintValidatorFactory实例化相关的实现当约束注解定义你的域模型中。

By default, the LocalValidatorFactoryBean configures a SpringConstraintValidatorFactory that uses Spring to create ConstraintValidator instances. This allows your custom ConstraintValidators to benefit from dependency injection like any other Spring bean.

默认的,LocalValidatorFactoryBean配置了SpringConstraintValidatorFactory用于spring来创建ConstraintValidator实例。允许你自定义ConstraintValidators来独立注入类似于其他spring的bean。

Shown below is an example of a custom @Constraint declaration, followed by an associated ConstraintValidator implementation that uses Spring for dependency injection:

下面是一个@Constraint声明的例子,使用spring的依赖注入来管理ConstraintValidator的实现:

@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.

可见,ConstraintValidator实现可以有独立的@Autowired类似于其他的spring的bean。

Spring-driven Method Validation

基于spring的方法验证

The method validation feature supported by Bean Validation 1.1, and as a custom extension also by Hibernate Validator 4.3, can be integrated into a Spring context through a MethodValidationPostProcessor bean definition:

方法验证特性通过bean验证1.1来支持,Hibernate验证4.3的自定义扩展也可以注入spring的上下文通过一个MethodValidationPostProcessor的bean定义。

<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>

In order to be eligible for Spring-driven method validation, all target classes need to be annotated with Spring’s @Validated annotation, optionally declaring the validation groups to use. Check out the MethodValidationPostProcessor javadocs for setup details with Hibernate Validator and Bean Validation 1.1 providers.

为了适用于基于spring的方法验证,所有的目标的类需要使用spring的@Validated注解,可选的使用验证组。查看MethodValidationPostProcessor的javadocs用于了解Hibernate验证器和bean验证1.1的详情。

Additional Configuration Options

额外的配置选项

The default LocalValidatorFactoryBean configuration should prove sufficient for most cases. There are a number of configuration options for various Bean Validation constructs, from message interpolation to traversal resolution. See the LocalValidatorFactoryBean javadocs for more information on these options.

默认的LocalValidatorFactoryBean配置应当适合大多数场景。需要很多配置选项用于多种bean的验证约束,从信息采集到解决方案。详细见LocalValidatorFactoryBean的javadocs。

9.8.3 Configuring a DataBinder

配置DataBinder

Since Spring 3, a DataBinder instance can be configured with a Validator. Once configured, the Validator may be invoked by calling binder.validate(). Any validation Errors are automatically added to the binder’s BindingResult.

自从spring3,DataBinder实例可以使用验证器来配置。一次配置,验证器可以通过调用binder.validate来使用。任何验证错误可以自动加入binder的BindingResult中。

When working with the DataBinder programmatically, this can be used to invoke validation logic after binding to a target object:

当编程使用DataBinder,可以在目标object上调用验证逻辑:

Foo target = new Foo();

DataBinder binder = new DataBinder(target);

binder.setValidator(new FooValidator());

// bind to the target object

// 绑定到目标object

binder.bind(propertyValues);

// validate the target object

// 验证目标object

binder.validate();

// get BindingResult that includes any validation errors

// 获得BindingResult里面包含所有验证错误

BindingResult results = binder.getBindingResult();

A DataBinder can also be configured with multiple Validator instances via dataBinder.addValidators and dataBinder.replaceValidators. This is useful when combining globally configured Bean Validation with a Spring Validator configured locally on a DataBinder instance. See ???.

DataBinder可以使用多个验证器实例来配置,通过使用dataBinder.addValidators和dataBinder.replaceValidators。当组合全局配置的bean验证通过使用spring的验证器在本地的DataBinder实例上使用。见……

9.8.4 Spring MVC 3 Validation

spring的mvc3的验证

See Section 22.16.4, “Validation” in the Spring MVC chapter.

将22.16.4,spirng的mvc章节中的验证。