Spring文档阅读之校验,数据绑定和类型转换

Spring希望在应用的每一层都能进行数据校验而不是限制在Web层,所以提出了Validator接口。

数据绑定(Data binding)使我们可以动态的输入领域模型对象,一般和Validator接口组合使用用于接口入参校验。

1.Validation by Using Spring’s Validator Interface

Spring抽象了Validator接口给用于进行校验。Validator接口使用Errors对象来报告校验失败的情况和具体数据。

定义POJO类Person:

public class Person {

private String name;
private int age;

// the usual getters and setters...

}

下面定义一个实现了org.springframework.validation.Validator接口的校验实例:

supports(Class): 判断当前校验器是否能校验给定的对象

validate(Object, org.springframework.validation.Errors): 校验给定的对象Object,如果校验发现异常,通过Errors对象返回

使用ValidationUtils工具可以帮助我们方便的实现Validator接口:

public class PersonValidator implements Validator {

/**
 *  校验器只能校验Person实例
 */
public boolean supports(Class clazz) {
    return Person.class.equals(clazz);
}

public void validate(Object obj, Errors e) {
    ValidationUtils.rejectIfEmpty(e, "name", "name.empty"); 
    //自定义的校验条件
    Person p = (Person) obj;
    if (p.getAge() < 0) {
        e.rejectValue("age", "negativevalue");
    } else if (p.getAge() > 110) {
        e.rejectValue("age", "too.darn.old");
    }
}

}

ValidationUtils.rejectIfEmpty(...)方法用于判断入参obj,如果其为空或者是空字符串,则拒绝验证并返回自定义的异常返回语句。

当需要验证的对象持有其他对象时,最好单独定义每个对象的校验器,并将需要的校验器通过依赖注入的方式给予当前校验器使用,如下示例:

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

}

2.Bean Manipulation and the BeanWrapper

BeanWrapper作为javaBeans的一个重要类,主要作用是包装对象类,可以对被包装类进行属性(包括内置对象的属性)和方法的设置。用户通常不需要直接使用BeanWrapper,Spring的BeanFactory使用BeanWrapper来统一管理BeanWapper。

2.1. Setting and Getting Basic and Nested Properties

设置和获取Bean属性可以通过setPropertyValue, setPropertyValues, getPropertyValue, and getPropertyValues方法,以下是一些示例:

public class Company {

private String name;

//聚合对象managingDirector
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;
}

}

以下实例演示如何获取和操作Employee和Company对象们:

BeanWrapper company = new BeanWrapperImpl(new Company());
// setting the company name..
company.setPropertyValue("name", "Some Company Inc.");
// ... can also be done like this:
PropertyValue value = new PropertyValue("name", "Some Company Inc.");
company.setPropertyValue(value);

// ok, let's create the director and tie it to the company:
BeanWrapper jim = new BeanWrapperImpl(new Employee());
jim.setPropertyValue("name", "Jim Stravinsky");
company.setPropertyValue("managingDirector", jim.getWrappedInstance());

// retrieving the salary of the managingDirector through the company
Float salary = (Float) company.getPropertyValue("managingDirector.salary");

2.2. Spring Type Conversion

2.2.1. Converter SPI

Spring提供了SPI用于运行时类型转换:

package org.springframework.core.convert.converter;

public interface Converter<S, T> {

T convert(S source);

}

如果需要创建自己的转换器,只需要实现Converter接口并且指定范型S和T的数据类型,同时还可以对集合进行类型转行。

Spring的org.springframework.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);
}

}

2.2.2. Using ConverterFactory

我们可以使用ConverterFactory接口来统一管理Convert的功能,可以实现ConverterFactory接口,以下是示例:

package org.springframework.core.convert.converter;

public interface ConverterFactory<S, R> {

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

}

R是要转换成的结果类型,T是R的子类,S是要转换的类型。以下是String转换为java.lang.Enum转换成的例子:

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

}

2.2.3. Using GenericConverter

当需要实现复杂的Converter功能,可以考虑使用GenericConverter接口。GenericConverte接口定义如下r:

package org.springframework.core.convert.converter;

public interface GenericConverter {
//获取可以转换的资源类型数组
public Set getConvertibleTypes();
//TypeDescriptor包含原始资源的描述信息
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}

2.2.4 Using ConditionalGenericConverter

ConditionalGenericConverter是GenericConverter and ConditionalConverter接口的组合,使用户在满足matches()条件下才能使用转换器:

public interface ConditionalConverter {

boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);

}

public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
}

2.2.5. The 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);

}

2.2.6. Configuring a ConversionService

ConversionService作为一个引用启动时初始化的无状态对象,可以在不同线程间共享。ConversionService可以被注入任何Bean并且被调用。

可以使用XML配置文件的方式定义默认conversionService:


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 converters, set the converters property. Property values can implement any of the Converter, ConverterFactory, or GenericConverter interfaces.







2.2.7. Using a ConversionService Programmatically

在代码中使用ConversionService,需要以注入的方式获取一个当前应用的ConversionService的实例:

@Service
public class MyService {

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

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

}

以下例子讲List转换成List:

DefaultConversionService cs = new DefaultConversionService();

List input = ....;
cs.convert(input,
TypeDescriptor.forObject(input), // List type descriptor
TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class)));

DefaultConversionService自动注册转换器(适用于大部分场景)。 DefaultConversionService可以进行集合,scalar和基础的对象-字符串转换,可以使用addDefaultConverters方法注册相同类型的转换器。

  1. Spring Field Formatting

3.1. The Formatter SPI

以下是Formatter接口的定义:

package org.springframework.format;

public interface Formatter extends Printer, Parser {
}

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

Spring提供了NumberStyleFormatter, CurrencyStyleFormatter, 和PercentStyleFormatter通过java.text.NumberFormat用于格式化java.lang.Number对象. 提供DateFormatter使用 java.text.DateFormat用于格式化java.util.Date对象. joda包基于Joda-Time库提供全面的日期格式化支持。

以下的DateFormatter是一个Formatter接口的实现例子:

package org.springframework.format.datetime;

public final class DateFormatter implements Formatter {

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.2. Annotation-driven Formatting

以下是AnnotationFormatterFactory接口的定义:

package org.springframework.format;

public interface AnnotationFormatterFactory {

Set<Class<?>> getFieldTypes();

Printer<?> getPrinter(A annotation, Class<?> fieldType);

Parser<?> getParser(A annotation, Class<?> fieldType);

}

@NumberFormat使用示例:

public class MyModel {

@NumberFormat(style=Style.CURRENCY)
private BigDecimal decimal;

}

@DateTimeFormat格式化日期为 (yyyy-MM-dd)格式:

public class MyModel {

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

}

3.3.1. The FormatterRegistry SPI

FormatterRegistry用于注册formatters和converters,FormattingConversionService实现了FormatterRegistry接口并能适用于大部分场景。

FormatterRegistry的源码:

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

}

3.3.2. The FormatterRegistrar SPI(Service Provider Interface 提供给用户以继承实现或者拓展的接口和方法)

FormatterRegistrar通过FormatterRegistry注册formatters和converters,以下是其定义:

package org.springframework.format;

public interface FormatterRegistrar {

void registerFormatters(FormatterRegistry registry);

}

3.3.3. Configuring a Global Date and Time Format

默认情况下,日期和时间字段如果没有添加@DateTimeFormat注解使用的是DateFormat.SHORT风格的数据形式。如果需要使用自定义形式的数据形式,则需要确保Spring没有注册默认格式化器并且需要用户手动注册所有格式化器(formatters),使用org.springframework.format.datetime.joda.JodaTimeFormatterRegistrar或者 org.springframework.format.datetime.DateFormatterRegistrar 。

以下示例注册了一个全局使用yyyyMMdd日期数据格式的格式化器:

@Configuration
public class AppConfig {

@Bean
public FormattingConversionService conversionService() {

    // Use the DefaultFormattingConversionService but do not register defaults(false)
    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配置文件的话也可以:

<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>
  1. Spring Validation

Spring 3 完全支持JSR-303 Bean Validation API ,同时使用Spring’s DataBinder可以校验并且绑定数据. SpringMVC支持声明式的进行数据校验。

4.1. Overview of the JSR-303 Bean Validation API

/**
*POJO对象
**/
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;

}

4.2. Configuring a Bean Validation Provider

可以使用LocalValidatorFactoryBean来配置一个Spring Bean来作为默认校验器,如下所示:


The basic configuration in the preceding example triggers bean validation to initialize by using its default bootstrap mechanism. A JSR-303 or JSR-349 provider, such as the Hibernate Validator, is expected to be present in the classpath and is automatically detected.

4.3. Injecting a Validator

用户可以注入javax.validation.Validator的引用来使用校验API:
import javax.validation.Validator;

@Service
public class MyService {

@Autowired
private Validator validator;

org.springframework.validation.Validator引用也可以注入:

import org.springframework.validation.Validator;

@Service
public class MyService {

@Autowired
private Validator validator;

}

原文地址:https://www.cnblogs.com/Simon-cat/p/10056889.html