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

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

考虑将验证作为业务逻辑是有利有弊,Spring 提供了一种验证(和数据绑定)设计,但并不排除其中任何一个。具体来说,验证不应与网络层绑定,并且应该易于本地化,并且应该可以插入任何可用的验证器。考虑到这些问题,Spring 提供了一个Validator接口,该接口既基本又可以在应用程序的每一层使用。

数据绑定对于使用户 Importing 动态绑定到应用程序(或用于处理用户 Importing 的任何对象)域模型很有用。 Spring 提供了恰当地命名为DataBinder的名称。 Validator接口DataBinder类组成了org.springframework.validation程序包,该程序包主要用于但不限于 MVC 框架。

 

BeanWrapper接口(org.springframework.beans是 Spring 框架中的一个基本概念,在很多地方都使用过。但是,您可能不需要直接使用BeanWrapper。如果您将要使用它,则在尝试将数据绑定到对象时最有可能使用它。

 

Spring 的DataBinder和较低级别的BeanWrapper都使用PropertyEditorSupport实现来解析和格式化属性值。 PropertyEditor接口PropertyEditorSupport类(java.beans是 JavaBeans 规范的一部分。

 

Spring 3 引入了core.convert软件包,该软件包提供了常规的类型转换工具,以及用于格式化 UI 字段值的高级“格式”软件包。可以将这些软件包用作PropertyEditorSupport实现的更简单替代方案。

 

JSR-303/JSR-349 Bean 验证

从 4.0 版本开始,Spring 框架支持 Bean 验证 1.0(JSR-303)和 Bean 验证 1.1(JSR-349),以支持设置并使其适应 Spring 的Validator接口。

 

应用程序可以选择一次全局启用 Bean 验证(如Spring Validation中所述),并将其专用于所有验证需求。

应用程序还可以为每个DataBinder实例注册其他 Spring Validator实例,如配置一个 DataBinder中所述。

 

3.1. 使用 Spring 的 Validator 接口进行验证

Spring 具有Validator接口,可用于验证对象。 Validator接口通过使用Errors对象工作,因此验证器可以在验证时向Errors对象报告验证失败。

考虑以下小型数据对象的示例:

public class Person {

    private String name;
    private int age;

    // the usual getters and setters...
}

下一个示例通过实现org.springframework.validation.Validator接口的以下两种方法来提供Person类的验证行为:

  • supports(Class):此Validator是否可以验证提供的Class的实例。

  • validate(Object, org.springframework.validation.Errors):验证给定的对象,并在验证错误的情况下,将其注册到给定的Errors对象。

实现Validator非常简单,尤其是当您知道 Spring 框架还提供的ValidationUtils helper 类时。以下示例为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类上的static rejectIfEmpty(..)方法用于拒绝name属性(如果它是null或空字符串)。  

 

虽然可以实现单个Validator类来验证丰富对象中的每个嵌套对象,但是最好将每个嵌套类的验证逻辑封装在自己的Validator实现中。一个“丰富”对象的简单示例是一个Customer,它由两个String属性(名字和第二个名称)和一个复杂的Address对象组成。 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对象。  

 

3.2. 将代码解析为错误消息

我们介绍了数据绑定和验证。本节介绍与验证错误相对应的输出消息。在preceding section所示的示例中,我们拒绝了nameage字段。如果要使用MessageSource输出错误消息,可以使用拒绝字段时提供的错误代码(在这种情况下为“名称”和“年龄”)进行输出。当您从Errors接口调用(直接或间接使用ValidationUtils类)rejectValue或其他reject方法时,基础实现不仅会注册您传入的代码,还会注册许多其他错误代码。 MessageCodesResolver确定Errors接口寄存器的错误代码。默认情况下,使用DefaultMessageCodesResolver,例如,它不仅使用您提供的代码注册一条消息,而且还注册包含您传递给 reject 方法的字段名称的消息。因此,如果您使用rejectValue("age", "too.darn.old")拒绝字段,除了too.darn.old代码,Spring 还会注册too.darn.old.agetoo.darn.old.age.int(第一个包含字段名称,第二个包含字段类型)。这样做是为了方便开发人员在定位错误消息时提供帮助。

 

3.3. Bean 操作和 BeanWrapper

org.springframework.beans软件包遵循 JavaBeans 标准。 JavaBean 是具有默认无参数构造函数的类,并且遵循命名约定,其中(例如)名为bingoMadness的属性将具有 setter 方法setBingoMadness(..)和 getter 方法getBingoMadness()

Bean 包中的一个非常重要的类是BeanWrapper接口及其相应的实现(BeanWrapperImpl(org.springframework.beans)。正如从 Javadoc 引用的那样,BeanWrapper提供了以下功能:设置和获取属性值(单独或批量),获取属性 Descriptors 以及查询属性以确定它们是否可读或可写。此外,BeanWrapper还支持嵌套属性,从而可以将子属性上的属性设置为无限深度。 BeanWrapper还支持添加标准 JavaBean PropertyChangeListenersVetoableChangeListeners的能力,而无需在目标类中支持代码。最后但并非最不重要的一点是,BeanWrapper支持设置索引属性。 BeanWrapper通常不直接由应用程序代码使用,而由DataBinderBeanFactory使用。

 

BeanWrapper的工作方式部分地由其名称表示:它包装一个 bean 以对该 bean 执行操作,例如设置和检索属性。

 

3.3.1. BeanWrapper设置和获取基本和嵌套属性

设置和获取属性的方法是使用setPropertyValuesetPropertyValuesgetPropertyValuegetPropertyValues方法,这些方法带有一些重载的变体。

表 11.属性示例

ExpressionExplanation
name 表示与nameisName()setName(..)方法相对应的属性name
account.name 表示与getAccount().setName()getAccount().getName()方法相对应的属性account的嵌套属性name
account[2] 指示索引属性account的* third *元素。索引属性可以是arraylist或其他自然排序的集合。
account[COMPANYNAME] 表示由account Map属性的COMPANYNAME键索引的 Map 条目的值。

以下两个示例类使用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;
    }
}
BeanWrapper company = new BeanWrapperImpl(new Company());
company.setPropertyValue("name", "Some Company Inc.");
// 或者 PropertyValue value = new PropertyValue("name", "Some Company Inc."); company.setPropertyValue(value); BeanWrapper jim = new BeanWrapperImpl(new Employee()); jim.setPropertyValue("name", "Jim Stravinsky"); company.setPropertyValue("managingDirector", jim.getWrappedInstance()); // retrieving the salary of the managingDirector through the company Float salary = (Float) company.getPropertyValue("managingDirector.salary");

 

3.3.2. 内置的 PropertyEditor(属性编辑器)实现

Spring 使用PropertyEditor的概念来实现ObjectString之间的转换。以不同于对象本身的方式表示属性可能很方便。例如,Date可以以人类可读的方式表示(如String'2007-14-09'),而我们仍然可以将人类可读的形式转换回原始日期(或者更好的是,转换以人类可读的形式 Importing 的任何日期)回到Date个对象)。通过注册java.beans.PropertyEditor类型的自定义编辑器可以实现此行为。在BeanWrapper上或在特定的 IoC 容器中注册自定义编辑器(如上一章所述),使它具有如何将属性转换为所需类型的知识。有关PropertyEditor的更多信息,请参见Oracle 的 java.beans 包的 javadoc。  

在 Spring 中使用属性编辑的几个示例:

  • 通过使用PropertyEditor实现在 bean 上设置属性。当使用java.lang.String作为在 XML 文件中声明的某些 bean 的属性值时,Spring(如果相应属性的设置器具有Class参数)将使用ClassEditor尝试将参数解析为Class对象。

  • 在 Spring 的 MVC 框架中,通过使用各种PropertyEditor实现来解析 HTTP 请求参数,您可以在CommandController的所有子类中手动绑定这些实现。

Spring 具有许多内置的PropertyEditor实现,以简化生活。它们都位于org.springframework.beans.propertyeditors包中。默认情况下,大多数(但不是全部,如下表所示)由BeanWrapperImpl注册。如果可以通过某种方式配置属性编辑器,则仍可以注册自己的变体以覆盖默认变体。下表描述了 Spring 提供的各种PropertyEditor实现:

表 12.内置PropertyEditor实现

ClassExplanation
ByteArrayPropertyEditor 字节数组的编辑器。将字符串转换为其相应的字节表示形式。默认情况下由BeanWrapperImpl注册。
ClassEditor 将表示类的字符串解析为实际类,反之亦然。如果找不到类,则会抛出IllegalArgumentException默认情况下,由BeanWrapperImpl注册。
CustomBooleanEditor Boolean个属性的可定制属性编辑器。默认情况下,由BeanWrapperImpl注册,但是可以通过将其自定义实例注册为自定义编辑器来覆盖。
CustomCollectionEditor 集合的属性编辑器,可将任何源Collection转换为给定目标Collection类型。
CustomDateEditor java.util.Date的可定制属性编辑器,支持定制DateFormat。默认未注册。必须根据需要以适当的格式进行用户注册。
CustomNumberEditor 任何Number子类(例如IntegerLongFloatDouble)的可定制属性编辑器。默认情况下,由BeanWrapperImpl注册,但是可以通过将其自定义实例注册为自定义编辑器来覆盖。
FileEditor 将字符串解析为java.io.File个对象。默认情况下,由BeanWrapperImpl注册。
InputStreamEditor 单向属性编辑器,它可以获取字符串并产生(通过中间的ResourceEditorResource)InputStream,以便可以将InputStream属性直接设置为字符串。请注意,默认用法不会为您关闭InputStream默认情况下,由BeanWrapperImpl注册。
LocaleEditor 可以将字符串解析为Locale对象,反之亦然(字符串格式为[country][variant],与LocaletoString()方法相同)。默认情况下,由BeanWrapperImpl注册。
PatternEditor 可以将字符串解析为java.util.regex.Pattern个对象,反之亦然。
PropertiesEditor 可以将字符串(格式为java.util.Properties类的 javadoc 中定义的格式)转换为Properties对象。默认情况下,由BeanWrapperImpl注册。
StringTrimmerEditor 修剪字符串的属性编辑器。 (可选)允许将空字符串转换为null值。默认情况下未注册—必须是用户注册的。
URLEditor 可以将 URL 的字符串表示形式解析为实际的URL对象。默认情况下,由BeanWrapperImpl注册。

 

Spring 使用java.beans.PropertyEditorManager来设置可能需要的属性编辑器的搜索路径。搜索路径还包括sun.bean.editors,其中sun.bean.editors包括针对FontColor等类型和大多数基本类型的PropertyEditor实现。还要注意,如果标准 JavaBeans 基础结构与它们处理的类在相同的程序包中并且与该类具有相同的名称并附加了Editor,则自动发现PropertyEditor类(无需显式注册它们)。例如,可能具有以下类和包结构,足以识别SomethingEditor类并将其用作Something类型的属性的PropertyEditor

com
  chank
    pop
      Something
      SomethingEditor // the PropertyEditor for the Something class

 

您也可以在此处使用标准的BeanInfo JavaBeans 机制(在某种程度上已描述here)。下面的示例使用BeanInfo机制使用关联类的属性显式注册一个或多个PropertyEditor实例:  

com
  chank
    pop
      Something
      SomethingBeanInfo // the BeanInfo for the Something class

所引用的SomethingBeanInfo类的以下 Java 源代码将CustomNumberEditorSomething类的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());
        }
    }
}
 
注册其他自定义 PropertyEditor 实现

当将 bean 属性设置为字符串值时,Spring IoC 容器最终使用标准 JavaBeans PropertyEditor实现将这些字符串转换为属性的复杂类型。 Spring 预注册了许多自定义PropertyEditor实现(例如,将表示为字符串的类名转换为Class对象)。另外,Java 的标准 JavaBeans PropertyEditor查找机制允许为类的PropertyEditor适当命名,并与它提供支持的类放在同一包中,以便可以自动找到它。

 

如果需要注册其他自定义PropertyEditors,则可以使用几种机制。

最手动的方法(通常不方便或不建议使用)是使用ConfigurableBeanFactory接口的registerCustomEditor()方法(假设您有BeanFactory引用)。

另一种(稍微方便些)的机制是使用称为CustomEditorConfigurer的特殊BeanFactoryPostProcessor

 

尽管您可以将BeanFactoryPostProcessor与BeanFactory实现一起使用,但是CustomEditorConfigurer具有嵌套的属性设置,因此我们强烈建议您将CustomEditorConfigurerApplicationContext一起使用,在这里您可以以与其他任何 Bean 类似的方式部署它,并且可以将其放置在自动检测并应用。

 

请注意,所有 beanFactory和ApplicationContext通过使用BeanWrapper来处理属性转换,都会自动使用许多内置的属性编辑器。 BeanWrapper寄存器的标准属性编辑器在上一节中列出。此外,ApplicationContexts还重写或添加其他编辑器,以适合于特定应用程序上下文类型的方式处理资源查找。

 

标准 JavaBeans PropertyEditor实例用于将以字符串表示的属性值转换为该属性的实际复杂类型您可以使用 Bean 工厂后处理程序CustomEditorConfigurer来方便地向ApplicationContext添加对其他PropertyEditor实例的支持。

 

使用CustomEditorConfigurer注册自定义属性编译器

考虑以下示例,该示例定义了一个名为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的实现可能类似于以下内容:

package example;

public class ExoticTypeEditor extends PropertyEditorSupport {

    public void setAsText(String text) {
        setValue(new ExoticType(text.toUpperCase()));
    }
}

最后,以下示例显示了如何使用CustomEditorConfigurerApplicationContext注册新的PropertyEditor,然后便可以根据需要使用它了:  

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
    <property name="customEditors">
        <map>
            <entry key="example.ExoticType" value="example.ExoticTypeEditor"/>
        </map>
    </property>
</bean>
 
使用 PropertyEditorRegistrar注册自定义属性编译器

向 Spring 容器注册属性编辑器的另一种机制是创建并使用PropertyEditorRegistrar

当需要在几种不同情况下使用同一组属性编辑器时,此接口特别有用。您可以编写相应的注册商,并在每种情况下重复使用它。

 PropertyEditorRegistrar实例与名为PropertyEditorRegistry的接口配合使用,PropertyEditorRegistry接口由 Spring BeanWrapper(和DataBinder)实现。

 PropertyEditorRegistrar实例CustomEditorConfigurer(描述为here)结合使用时特别方便,后者公开了称为setPropertyEditorRegistrars(..)的属性。以这种方式添加到CustomEditorConfigurerPropertyEditorRegistrar实例可以轻松地与DataBinder和 Spring MVC 控制器共享。

此外,它避免了在自定义编辑器上进行同步的需要:PropertyEditorRegistrar有望为每次 bean 创建尝试创建新的PropertyEditor实例。

 

以下示例显示了如何创建自己的PropertyEditorRegistrar实现:  

package com.foo.editors.spring;

public final class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {

    public void registerCustomEditors(PropertyEditorRegistry registry) {

     
        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"/>

 

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注册代码封装在一个类中,然后根据需要在多个Controllers之间共享。  

 

3.4. Spring Type Conversion

 Spring 3 引入了core.convert软件包,该软件包提供了常规的类型转换系统。该系统定义了一个用于实现类型转换逻辑的 SPI 和一个用于在运行时执行类型转换的 API

在 Spring 容器中,您可以将此系统用作PropertyEditor实现的替代方法,以将外部化的 bean 属性值字符串转换为所需的属性类型。您还可以在应用程序中需要类型转换的任何地方使用公共 API。

 

3.4.1. converter SPI

 如以下接口定义所示,用于实现类型转换逻辑的 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);
    }
}

 

3.4.2. 使用 ConverterFactory

当需要集中整个类层次结构的转换逻辑时(例如,从 String 转换为 java.lang.Enum 对象时),可以实现ConverterFactory,如以下示例所示:  

package org.springframework.core.convert.converter;

public interface ConverterFactory<S, R> {

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

参数化 S 为您要转换的类型,参数化 R 为定义可以转换为的类的“范围”的基本类型。然后实现 getConverter(Class),其中 T 是 R 的子类。  

 

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

 

3.4.3. 使用 GenericConverter

当您需要复杂的Converter实现时,请考虑使用GenericConverter接口。 GenericConverter具有比Converter更灵活但类型不那么强的签名,支持GenericConverter在多种源类型和目标类型之间进行转换。另外,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会检查声明目标集合类型的字段以解析集合的元素类型。这样就可以在将集合设置到目标字段上之前,将源数组中的每个元素转换为集合元素类型。  

 

Note

由于GenericConverter是更复杂的 SPI 接口,因此仅应在需要时使用它。ConverterConverterFactory以满足基本类型转换需求。

 

使用 ConditionalGenericConverter

有时,您希望Converter仅在满足特定条件的情况下运行。例如,您可能只想在目标字段上存在特定注解时才运行Converter,或者仅在目标类上定义了特定方法(例如static valueOf方法)时才运行Converter。 ConditionalGenericConverterGenericConverterConditionalConverter接口的并集,可用于定义以下自定义匹配条件:  

public interface ConditionalConverter {

    boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}

public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
}

ConditionalGenericConverter的一个很好的例子是EntityConverter,它在持久性实体标识符和实体引用之间转换。仅当目标实体类型声明静态查找器方法(例如findAccount(Long))时,此类EntityConverter才可能匹配。您可以在matches(TypeDescriptor, TypeDescriptor)的实现中执行这种 finder 方法检查。  

 

3.4.4. ConversionService API

ConversionService定义了一个统一的 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,该ConverterRegistry提供了用于注册转换器的 SPI。在内部,ConversionService实现委派其注册的转换器执行类型转换逻辑。  

 

core.convert.support软件包中提供了一种强大的ConversionService实现。 GenericConversionService是适用于大多数环境的通用实现。 ConversionServiceFactory提供了一个方便的工厂来创建通用的ConversionService配置。

 

3.4.5. 配置 ConversionService

ConversionService是无状态对象,旨在在应用程序启动时实例化,然后在多个线程之间共享。在 Spring 应用程序中,通常为每个 Spring 容器(或ApplicationContext)配置一个ConversionService实例。 Spring 需要ConversionService并在框架需要执行类型转换时使用它。您也可以将此ConversionService注入到您的任何 bean 中并直接调用它。

 

Note

如果没有ConversionService向 Spring 注册,则使用基于PropertyEditor的原始系统。

 

要向 Spring 注册默认的ConversionService,请添加以下 Bean 定义,并将id命名为conversionService

<bean id="conversionService"  class="org.springframework.context.support.ConversionServiceFactoryBean"/>

默认ConversionService可以在字符串,数字,枚举,集合,Map 和其他常见类型之间进行转换。要用您自己的自定义转换器补充或覆盖默认转换器,请设置converters属性。属性值可以实现ConverterConverterFactoryGenericConverter接口中的任何一个。  

<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <bean class="example.MyCustomConverter"/>
        </set>
    </property>
</bean>

在 Spring MVC 应用程序中通常使用ConversionService。  

 

3.4.6. 以编程方式使用 ConversionService

要以编程方式使用ConversionService实例,可以像对其他任何 bean 一样注入对该实例的引用。以下示例显示了如何执行此操作:

@Service
public class MyService {

    @Autowired
    public MyService(ConversionService conversionService) {
        this.conversionService = conversionService;
    }

    public void doIt() {
        this.conversionService.convert(...)
    }
}

对于大多数使用情况,可以使用指定targetTypeconvert方法,但不适用于更复杂的类型,例如参数化元素的集合。例如,如果要以编程方式将IntegerList转换为StringList,则需要提供源类型和目标类型的正式定义。  

 

幸运的是,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自动注册适用于大多数环境的转换器。这包括集合转换器,标量转换器和基本的ObjectString转换器。您可以使用DefaultConversionService类上的静态addDefaultConverters方法将相同的转换器注册到任何ConverterRegistry中。

值类型的转换器可重用于数组和集合,因此,无需使用特定的转换器即可将SCollection转换为TCollection,前提是需要标准的集合处理。

 

3.5. Spring Field Formatting

如上一节所述,core.convert是通用类型转换系统。它提供了统一的ConversionService API 以及强类型的Converter SPI,用于实现从一种类型到另一种类型的转换逻辑。 Spring 容器使用此系统绑定 bean 属性值。此外,Spring Expression Language(SpEL)和DataBinder都使用此系统绑定字段值。例如,当 SpEL 需要将Short强制转换为Long才能完成expression.setValue(Object bean, Object value)尝试时,core.convert系统将执行强制转换。

现在考虑典型 Client 端环境(例如 Web 或桌面应用程序)的类型转换要求。在这种环境中,通常您会从String转换为支持 Client 端回发过程,而从String转换为支持视图渲染过程。另外,您通常需要本地化String值。更为通用的core.convert Converter SPI 不能直接解决此类格式化要求。为了直接解决这些问题,Spring 3 引入了一个方便的Formatter SPI,它为 Client 端环境提供了一种简单且健壮的替代PropertyEditor实现的方法。

通常,当您需要实现通用类型转换逻辑时(例如,在java.util.Datejava.lang.Long之间进行转换),可以使用Converter SPI。在 Client 端环境(例如 Web 应用程序)中工作并且需要解析和打印本地化的字段值时,可以使用Formatter SPI。 ConversionService为两个 SPI 提供统一的类型转换 API。

 

3.5.1. 格式化器 SPI

用于实现字段格式逻辑的Formatter SPI 非常简单且类型严格。以下清单显示了Formatter接口定义:  

package org.springframework.format;

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

Formatter来自PrinterParser构件块接口。以下清单显示了这两个接口的定义:  

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的实例以在 Client 端语言环境中显示。实现parse()操作,以从 Client 端语言环境返回的格式化表示形式解析T的实例。如果解析尝试失败,则您的Formatter应该抛出ParseExceptionIllegalArgumentException。注意确保Formatter实现是线程安全的。 

 

 为了方便起见,format子软件包提供了Formatter个实现。

 number包提供NumberStyleFormatterCurrencyStyleFormatterPercentStyleFormatter,用来将java.lang.Number对象格式化为java.text.NumberFormat

 datetime包提供了DateFormatter,用于将java.util.Date对象格式化为java.text.DateFormat。 

 

以下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;
    }
}

 

3.5.2.注解驱动的格式

可以通过字段类型或注解配置字段格式。要将注解绑定到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.DateTimeFormatgetFieldTypes()返回可以在其上使用注解的字段类型 。让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格式化 java.lang.Number 字段,使用@DateTimeFormat格式化java.util.Datejava.util.Calendarjava.util.Long或 Joda-Time 字段。

下面的示例使用@DateTimeFormat格式化java.util.Date作为 ISO 日期(yyyy-MM-dd):

public class MyModel {

    @DateTimeFormat(iso=ISO.DATE)
    private Date date;
}

 

3.5.3. FormatterRegistry SPI

FormatterRegistry是用于注册格式器和转换器的 SPI。 FormattingConversionService是适用于大多数环境的FormatterRegistry的实现。您可以使用FormattingConversionServiceFactoryBean以编程方式或声明方式将此实现配置为 Spring bean。因为此实现还实现了ConversionService,所以您可以直接配置它以与 Spring 的DataBinder和 Spring Expression Language(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,您一次定义这些规则,并在需要格式化时将它们应用。

 

3.5.4. FormatterRegistrar SPI

FormatterRegistrar是用于通过 FormatterRegistry 注册格式器和转换器的 SPI。以下清单显示了其接口定义:  

package org.springframework.format;

public interface FormatterRegistrar {

    void registerFormatters(FormatterRegistry registry);
}

为给定的格式类别(例如日期格式)注册多个相关的转换器和格式器时,FormatterRegistrar很有用。在声明式注册不充分的情况下它也很有用。例如,当格式化程序需要在不同于其自己的<T>的特定字段类型下进行索引时,或者在注册Printer/Parser对时。下一节将提供有关转换器和格式化程序注册的更多信息。  

3.5.5. 在 Spring MVC 中配置格式

 

3.6. 配置全局日期和时间格式

默认情况下,未使用@DateTimeFormat注解 的日期和时间字段是使用DateFormat.SHORT样式从字符串转换的。如果愿意,可以通过定义自己的全局格式来更改此设置。  

为此,您需要确保 Spring 不注册默认格式器。相反,您应该手动注册所有格式化程序。使用org.springframework.format.datetime.joda.JodaTimeFormatterRegistrarorg.springframework.format.datetime.DateFormatterRegistrar类,具体取决于您使用的是 Joda-Time 库。 

@Configuration
public class AppConfig {

    @Bean
    public FormattingConversionService conversionService() {

        // Use the DefaultFormattingConversionService but do not register defaults
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(false);

        // Ensure @NumberFormat is still supported
        conversionService.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());

        // Register date conversion with a specific global format
        DateFormatterRegistrar registrar = new DateFormatterRegistrar();
        registrar.setFormatter(new DateFormatter("yyyyMMdd"));
        registrar.registerFormatters(conversionService);

        return conversionService;
    }
}

如果您喜欢基于 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
        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 提供了单独的不同类型来表示datetimedate-time值。 JodaTimeFormatterRegistrardateFormattertimeFormatterdateTimeFormatter属性应用于为每种类型配置不同的格式。 DateTimeFormatterFactoryBean提供了一种创建格式化程序的便捷方法。

 

Note

如果您使用 Spring MVC,请记住显式配置所使用的转换服务。对于基于 Java 的@Configuration,这意味着扩展WebMvcConfigurationSupport类并覆盖mvcConversionService()方法。对于 XML,应使用mvc:annotation-driven元素的conversion-service属性。有关详情,请参见转换和格式化

 

3.7. Spring 验证

Spring 3 对其验证支持进行了一些增强。首先,完全支持 JSR-303 Bean 验证 API。其次,当以编程方式使用时,Spring 的DataBinder可以验证对象并绑定到它们。第三,Spring MVC 支持以声明方式验证@ControllerImporting。  

 

3.7.1. JSR-303 Bean 验证 API 概述

JSR-303 标准化了 Java 平台的验证约束声明和元数据。通过使用此 API,您可以使用声明性验证约束来注解域模型属性,并且运行时会强制执行它们。您可以使用许多内置约束。您还可以定义自己的自定义约束。

考虑以下示例,该示例显示了具有两个属性的简单PersonForm模型:

public class PersonForm {
    private String name;
    private int age;
}

JSR-303 允许您针对此类属性定义声明性验证约束,如以下示例所示:

public class PersonForm {

    @NotNull
    @Size(max=64)
    private String name;

    @Min(0)
    private int age;
}

 

3.7.2. 配置 Bean 验证提供程序

Spring 提供了对 Bean 验证 API 的全面支持。这包括对将 JSR-303 或 JSR-349 Bean 验证提供程序引导为 Spring Bean 的便捷支持。这样,您就可以在应用程序中需要验证的地方注入javax.validation.ValidatorFactoryjavax.validation.Validator

您可以使用LocalValidatorFactoryBean将默认的 Validator 配置为 Spring Bean,如以下示例所示:

<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>

  

注入验证器

LocalValidatorFactoryBean实现javax.validation.ValidatorFactoryjavax.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 需要使用 Spring Validation API,则可以注入对org.springframework.validation.Validator的引用,如以下示例所示:  

import org.springframework.validation.Validator;

@Service
public class MyService {

    @Autowired
    private Validator validator;
}
配置自定义约束

每个 bean 验证约束由两部分组成:* @Constraint注解,用于声明约束及其可配置属性。 * javax.validation.ConstraintValidator接口的实现,用于实现约束的行为。

要将声明与实现相关联,每个@Constraint注解都引用一个对应的ConstraintValidator实现类。在运行时,当域模型中遇到约束注解时,ConstraintValidatorFactory实例化引用的实现。

默认情况下,LocalValidatorFactoryBean配置使用 Spring 创建ConstraintValidator实例的SpringConstraintValidatorFactory。这使您的自定义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。
Spring 驱动的方法验证

您可以通过MethodValidationPostProcessor bean 定义将 Bean Validation 1.1(以及作为自定义扩展,还包括 Hibernate Validator 4.3)支持的方法验证功能通过MethodValidationPostProcessor bean 定义集成到 Spring 上下文中,如下所示:  

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

为了有资格通过 Spring 驱动的方法验证,所有目标类都需要使用 Spring 的@Validated注解 进行 注解。  

其他配置选项

在大多数情况下,默认的LocalValidatorFactoryBean配置就足够了。从消息插值到遍历解析,有许多用于各种 Bean 验证构造的配置选项。

 

3.7.3. 配置一个 DataBinder

从 Spring 3 开始,您可以使用Validator配置DataBinder实例。配置完成后,您可以通过调用binder.validate()来调用Validator。任何验证Errors都会自动添加到 Binder 的BindingResult中。

下面的示例显示在绑定到目标对象之后,如何以编程方式使用DataBinder来调用验证逻辑:

Foo target = new Foo();
DataBinder binder = new DataBinder(target);
binder.setValidator(new FooValidator());

// bind to the target object
binder.bind(propertyValues);

// validate the target object
binder.validate();

// get BindingResult that includes any validation errors
BindingResult results = binder.getBindingResult();

您还可以通过dataBinder.addValidatorsdataBinder.replaceValidators配置具有多个Validator实例的DataBinder。当将全局配置的 bean 验证与在 DataBinder 实例上本地配置的 Spring Validator结合使用时,这很有用。  

 

 

 

 

 

 

 

 

BeanWrapper

BeanWrapper是一个方便开发人员使用字符串来对Java Bean的属性执行get、set操作的工具类。它为那些UI类app提供了极大的便利,是以字符串和用户交互的

  1. Foo foo = new Foo();
  2. BeanWrapperImpl fooWrapper = new BeanWrapperImpl(foo);
  3. fooWrapper.setPropertyValue("intProperty", "1"); 
  4. Object intProperty = fooWrapper.getPropertyValue("intProperty");

另外,BeanWrapper内部使用了两种机制:  

  1. PropertyEditor

  BeanWrapper和java bean的内省模式密切关联,而PropertyEditor(只提供了String >> Object的转换)隶属于Java Bean规范。

  

  2. ConversionService

  Spring自3.0之后提供的替代PropertyEditor的机制

 

 注:按照Spring官方文档的说法,当容器内没有注册ConversionService的时候,会退回使用PropertyEditor机制。 

ConversionService

 

onversionService及其相关一套类型转换机制是一套通用的类型转换SPI,相比PropertyEditor只提供String >> Object的转换,ConversionService能够提供任意Object >> Object的转换。

由此我们可以看出,Spring为何要使用ConversionService替代PropertyEditor有三个原因:

  1. ConversionService功能更强大,支持的类型转换范围更广
  2. ConverterFactory支持一整个class hierarchy的转换(也就是多态),PropertyEditor则不行
  3. Java Bean这个规范最初是和Java GUI(Swing)一起诞生的,PropertyEditor接口里有大量和GUI相关的方法,显然已经过时了。顺便提一句,Java Bean和POJO不是一个概念,Java Bean不仅有setter、getter,还有一系列和Java GUI配套的东西。

Formatter

Formatter SPI是另外一套和PropertyEditor类似的,String<->Object的转换机制,但是有两个优点:

  1. 接口更干净,没有关于GUI的部分,只有 Printer.print() 和 Parser.parse() 两个方法
  2. 基于注解,支持同一类型的属性根据不同的格式来做String<->Object的转换。比如日期类型,一个字段的格式是yyyy-MM-dd,另一个格式是yyyyMMdd,如果利用PropertyEditor是比较麻烦,但是在这里就可以利用@DateTimeFormat来达到这个效果。

Spring提供了DefaultFormattingConversionService来支持Formatter SPI,也就是说如果要使用Formatter SPI,依然可以利用ConversionService接口。

注:Formatter SPI必须基于注解才可以使用,这点和ConversionService基于类型不同。

DataBinder

DataBinder主要提供了两个功能:

  1. 利用BeanWrapper,给对象的属性设值
  2. 在设值的同时做Validation

前四者关系图

ConversionService有两种实现,也就是说,如果要支持Formatter SPI,只需要让BeanWrapper切换使用不同的ConversionService即可。

  • DefaultConversionService,不支持Formatter SPI
  • DefaultFormattingConversionService,支持Formatter SPI

 

 

Validator

Validator较简单,如果类路径上存在 Bean Validation(例如,Hibernate Validator),则将LocalValidatorFactoryBean 注册为全局Validator,以便与@Valid一起使用在controller 方法参数上。

非Spring MVC的使用

Spring Core Context(要解析spring xml的)其实也使用ConversionService,但是是非强制的。让Spring Core Context使用conversionService的方式很简单,配置一个名字叫做conversionService的Bean即可。需要注意的是,因为这个Bean是在非常早的时候就被使用的(AbstractApplicationContext#L834),因此它最好不要依赖过多的其他的Bean,避免造成启动失败。

 

 

Spring在读取xml配置文件的时候,因为xml文件实际上是一个文本文件,所有值的设置都是String,这个时候如果给bean的复杂类型属性设置值,它会用到PropertyEditor或ConversionService。

<bean id="someBean" class="a.b.c.SomeBean">
    <property name="color" value="red"/>
</bean>  

例子中的color属性是Color类型,这时就会利用到PropertyEditor和ConversionService。

Spring MVC的使用

Spring MVC对于conversionService的使用比较特殊,它自己会注册一个名字叫做mvcConversionService类型为DefaultFormattingConversionService的Bean。因此会存在以下陷阱:

  • 如果Core Context中也定义了一个ConversionService,那么在MVC环境下,会有两个ConversionService的Bean。
  • 针对Core Context的ConversionService做的Customize如FormatterRegistrarConverterRegistryFormatterRegistryConversionServiceFactoryBeanFormattingConversionServiceFactoryBean是不会应用到MVC的那个ConversionService上。

 

 

HttpMessageConverter和ConversionService是什么关系?

个人理解,可以他们是不同的两种东西,二者各司其职,前者转换请求body信息和响应body信息,后者用于请求参数的转换。都可以接受文本信息,最终解析成对象。根本的区别:

HttpMessageConvert

官方文档中的说明:We can use the @RequestBody annotation on the argument of a Controller method to indicate that the body of the HTTP Request is deserialized to that particular Java entity. To determine the appropriate converter, Spring will use the “Content-Type” header from the client request.

对,@RequestBody决定了要使用HttpMessageConverter,而Content-Type则是选择具体某一个配置器。HttpMessageConverter<T>,默认有很多配置器:StringHttpMessageConverter,ByteArrayHttpMessageConverter,SourceHttpMessageConverter,FormHttpMessageConverter 。

ConversionService

它使用的是 WebDataBinder (extends DataBinder),处理url或@RequestParam等非@RequestBody参数,它数据绑定是这样的流程:

  1. 将ServletRequest对象及处理方法入参对象实例传给DataBinder 
  2. DataBinder 调用转配在Spring Web上下文中的ConversionService进行数据类型转换、数据格式化等工作,将ServletRequest中的消息填充到入参对象中
  3. 调用 Validator 对已经绑定的请求信息数据的入参对象进行数据合法性校验,生成数据绑定结果 BindingResult。BindingResult 包含完成绑定的入参对象和相应的校验错误对象。而后将 BindingResult 中的入参对象及校验错误对象赋给处理方法的入参。

  

  

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  

 

posted @ 2020-12-19 15:18  节日快乐  阅读(240)  评论(0编辑  收藏  举报