《Spring 5 官方文档》5. 验证、数据绑定和类型转换(二)

5.5 Spring类型转换

Spring 3引入了core.convert包来提供一个一般类型的转换系统。这个系统定义了实现类型转换逻辑的服务提供接口(SPI)以及在运行时执行类型转换的API。在Spring容器内,这个系统可以当作是PropertyEditor的替代选择,用于将外部bean的属性值字符串转换成所需的属性类型。这个公共的API也可以在你的应用程序中任何需要类型转换的地方使用。

5.5.1 Converter SPI

实现类型转换逻辑的SPI是简单并且强类型的:

package org.springframework.core.convert.converter;

public interface Converter<S, T> {

    T convert(S source);

}

要创建属于你自己的转换器,只需要简单的实现以上接口即可。泛型参数S表示你想要进行转换的源类型,而泛型参数T表示你想要转换的目标类型。如果一个包含S类型元素的集合或数组需要转换为一个包含T类型的数组或集合,那么这个转换器也可以被透明地应用,前提是已经注册了一个委托数组或集合的转换器(默认情况下会是DefaultConversionService处理)。

对每次方法convert(S)的调用,source参数值必须确保不为空。如果转换失败,你的转换器可以抛出任何非受检异常(unchecked exception);具体来说,为了报告一个非法的source参数值,应该抛出一个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);
    }

}

5.5.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的一个子类。

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

5.5.3 GenericConverter

当你需要一个复杂的转换器实现时,请考虑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可以通过内省声明了目标集合类型的字段以解析集合元素的类型,这将允许原数组中每个元素可以在集合被设置到目标字段之前转换成集合元素的类型。

由于GenericConverter是一个更复杂的SPI接口,所以对基本类型的转换需求优先使用Converter或者ConverterFactory。

ConditionalGenericConverter

有时候你只想要在特定条件成立的情况下Converter才执行,例如,你可能只想要在目标字段存在特定注解的情况下才执行Converter,或者你可能只想要在目标类中定义了特定方法,比如static valueOf方法,才执行ConverterConditionalGenericConverterGenericConverterConditionalConveter接口的联合,允许你定义这样的自定义匹配条件:

public interface ConditionalGenericConverter
        extends GenericConverter, ConditionalConverter {

    boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);

}

ConditionalGenericConverter的一个很好的例子是一个在持久化实体标识和实体引用之间进行转换的实体转换器。这个实体转换器可能只匹配这样的条件–目标实体类声明了一个静态的查找方法,例如findAccount(Long),你将在matches(TypeDescriptor,TypeDescriptor)方法实现里执行这样的查找方法的检测。

5.5.4 ConversionService API

ConversionService接口定义了运行时执行类型转换的统一API,转换器往往是在这个门面(facade)接口背后执行:

package org.springframework.core.convert;

public interface ConversionService {

    boolean canConvert(Class<?> sourceType, Class<?> targetType);

    <T> T convert(Object source, Class<T> targetType);

    boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);

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

}

大多数ConversionService实现也会实现ConverterRegistry接口,这个接口提供一个用于注册转换器的服务提供接口(SPI)。在内部,一个ConversionService实现会以委托给注册其中的转换器的方式来执行类型转换逻辑。

core.convert.support包已经提供了一个强大的ConversionService实现,GenericConversionService是适用于大多数环境的通用实现,ConversionServiceFactory以工厂的方式为创建常见的ConversionService配置提供了便利。

5.5.5 配置ConversionService

ConversionService是一个被设计成在应用程序启动时会进行实例化的无状态对象,随后可以在多个线程之间共享。在一个Spring应用程序中,你通常会为每一个Spring容器(或者应用程序上下文ApplicationContext)配置一个ConversionService实例,它会被Spring接收并在框架需要执行一个类型转换时使用。你也可以将这个ConversionService直接注入到你任何的Bean中并直接调用。

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

要向Spring注册默认的ConversionService,可以用conversionService作为id来添加如下的bean定义:

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

默认的ConversionService可以在字符串、数字、枚举、映射和其他常见类型之间进行转换。为了使用你自己的自定义转换器来补充或者覆盖默认的转换器,可以设置converters属性,该属性值可以是Converter、ConverterFactory或者GenericConverter之中任何一个的接口实现。

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

在一个Spring MVC应用程序中使用ConversionService也是比较常见的,可以去看Spring MVC章节的Section 18.16.3 “Conversion and Formatting”

在某些情况下,你可能希望在转换期间应用格式化,可以看5.6.3 “FormatterRegistry SPI”获取使用FormattingConversionServiceFactoryBean的细节。

5.5.6 编程方式使用ConversionService

要以编程方式使用ConversionService,你只需要像处理其他bean一样注入一个引用即可:

@Service
public class MyService {

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

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

对大多数用例来说,convert方法指定了可以使用的目标类型,但是它不适用于更复杂的类型比如参数化元素的集合。例如,如果你想要以编程方式将一个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的特定转换器。

5.6 Spring字段格式化

如上一节所述,core.convert包是一个通用类型转换系统,它提供了统一的ConversionService API以及强类型的Converter SPI用于实现将一种类型转换成另外一种的转换逻辑。Spring容器使用这个系统来绑定bean属性值,此外,Spring表达式语言(SpEL)和DataBinder也都使用这个系统来绑定字段值。举个例子,当SpEL需要将Short强制转换成Long来完成一次expression.setValue(Object bean, Object value)尝试时,core.convert系统就会执行这个强制转换。

现在让我们考虑一个典型的客户端环境如web或桌面应用程序的类型转换要求,在这样的环境里,你通常会经历将字符串进行转换以支持客户端回传的过程以及转换回字符串以支持视图渲染的过程。此外,你经常需要对字符串值进行本地化。更通用的core.convert包中的Converter SPI不直接解决这种格式化要求。Spring 3为此引入了一个方便的Formatter SPI来直接解决这些问题,这个接口为客户端环境提供一种简单强大并且替代PropertyEditor的方案。

一般来说,当你需要实现通用的类型转换逻辑时请使用Converter SPI,例如,在java.util.Date和java.lang.Long之间进行转换。当你在一个客户端环境(比如web应用程序)工作并且需要解析和打印本地化的字段值时,请使用Formatter SPI。ConversionService接口为这两者提供了一套统一的类型转换API。

5.6.1 Formatter SPI

Formatter SPI实现字段格式化逻辑是简单并且强类型的:

package org.springframework.format;

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

Formatter接口扩展了Printer和Parser这两个基础接口:

public interface Printer<T> {
    String print(T fieldValue, Locale locale);
}
import java.text.ParseException;

public interface Parser<T> {
    T parse(String clientValue, Locale locale) throws ParseException;
}

要创建你自己的格式化器,只需要实现上面的Formatter接口。泛型参数T代表你想要格式化的对象的类型,例如,java.util.Date。实现print()操作可以将类型T的实例按客户端区域设置的显示方式打印出来。实现parse()操作可以从依据客户端区域设置返回的格式化表示中解析出类型T的实例。如果解析尝试失败,你的格式化器应该抛出一个ParseException或者IllegalArgumentException。请注意确保你的格式化器实现是线程安全的。

为方便起见,format子包中已经提供了一些格式化器实现。number包提供了NumberFormatterCurrencyFormatterPercentFormatter,它们通过使用java.text.NumberFormat来格式化java.lang.Number对象 。datetime包提供了DateFormatter,其通过使用java.text.DateFormat来格式化java.util.Datedatetime.joda包基于Joda Time library提供了全面的日期时间格式化支持。

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

package org.springframework.format.datetime;

public final class DateFormatter implements Formatter<Date> {

    private String pattern;

    public DateFormatter(String pattern) {
        this.pattern = pattern;
    }

    public String print(Date date, Locale locale) {
        if (date == null) {
            return "";
        }
        return getDateFormat(locale).format(date);
    }

    public Date parse(String formatted, Locale locale) throws ParseException {
        if (formatted.length() == 0) {
            return null;
        }
        return getDateFormat(locale).parse(formatted);
    }

    protected DateFormat getDateFormat(Locale locale) {
        DateFormat dateFormat = new SimpleDateFormat(this.pattern, locale);
        dateFormat.setLenient(false);
        return dateFormat;
    }

}

Spring团队欢迎社区驱动的Formatter贡献,可以登陆网站jira.spring.io了解如何参与贡献。

5.6.2 注解驱动的格式化

如你所见,字段格式化可以通过字段类型或者注解进行配置,要将一个注解绑定到一个格式化器,可以实现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代表你想要关联格式化逻辑的字段注解类型,例如org.springframework.format.annotation.DateTimeFormat。让getFieldTypes()方法返回可能使用注解的字段类型,让getPrinter()方法返回一个可以打印被注解字段的值的打印机(Printer),让getParser()方法返回一个可以解析被注解字段的客户端值的解析器(Parser)。

下面这个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 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();
            }
        }
    }
}

要触发格式化,只需要使用@NumberFormat对字段进行注解:

public class MyModel {

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

}

Format Annotation API

org.springframework.format.annotation包中存在一套可移植(portable)的格式化注解API。请使用@NumberFormat格式化java.lang.Number字段,使用@DateTimeFormat格式化java.util.Date、java.util.Calendar、java.util.Long(注:此处可能是原文错误,应为java.lang.Long)或者Joda Time字段。

下面这个例子使用@DateTimeFormat将java.util.Date格式化为ISO时间(yyyy-MM-dd)

public class MyModel {

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

}

 

5.6.3 FormatterRegistry SPI

FormatterRegistry是一个用于注册格式化器和转换器的服务提供接口(SPI)。FormattingConversionService是一个适用于大多数环境的FormatterRegistry实现,可以以编程方式或利用FormattingConversionServiceFactoryBean声明成Spring bean的方式来进行配置。由于它也实现了ConversionService,所以可以直接配置它与Spring的DataBinder以及Spring表达式语言(SpEL)一起使用。

请查看下面的FormatterRegistry SPI:

package org.springframework.format;

public interface FormatterRegistry extends ConverterRegistry {

    void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser);

    void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter);

    void addFormatterForFieldType(Formatter<?> formatter);

    void addFormatterForAnnotation(AnnotationFormatterFactory<?, ?> factory);

}

如上所示,格式化器可以通过字段类型或者注解进行注册。

FormatterRegistry SPI允许你集中地配置格式化规则,而不是在你的控制器之间重复这样的配置。例如,你可能要强制所有的时间字段以某种方式被格式化,或者是带有特定注解的字段以某种方式被格式化。通过一个共享的FormatterRegistry,你可以只定义这些规则一次,而在需要格式化的时候应用它们。

5.6.4 FormatterRegistrar SPI

FormatterRegistrar是一个通过FormatterRegistry注册格式化器和转换器的服务提供接口(SPI):

package org.springframework.format;

public interface FormatterRegistrar {

    void registerFormatters(FormatterRegistry registry);

}

当要为一个给定的格式化类别(比如时间格式化)注册多个关联的转换器和格式化器时,FormatterRegistrar会非常有用。

下一部分提供了更多关于转换器和格式化器注册的信息。

5.6.5 在Spring MVC中配置格式化

请查看Spring MVC章节的Section 18.16.3 “Conversion and Formatting”

转载自 并发编程网 - ifeve.com    

时间: 2024-10-01 11:36:45

《Spring 5 官方文档》5. 验证、数据绑定和类型转换(二)的相关文章

《Spring 5官方文档》翻译邀请

公司新的应用已经开始使用Spring 5,所以本月组织大家翻译<Spring 5 官方文档> SINGLE网页版  PDF版本. 如何领取 通过评论领取想要翻译的文章,每次领取一章或一节(根据内容长短),翻译完后再领取其他章节.领取完成之后,建议在一个星期内翻译完成,如果不能完成翻译,也欢迎你邀请其他同学和你一起完成翻译.请谨慎领取,并发网是非盈利组织,没办法去跟进每一篇译文的进展,所以很多文章领取了没有翻译,会导致文章长时间没人翻译. 如何提交? 翻译完成之后请登录到并发编程网后台,点击左上

《Spring 5 官方文档》5. 验证、数据绑定和类型转换(一)

5 验证.数据绑定和类型转换 5.1 介绍 JSR-303/JSR-349 Bean Validation 在设置支持方面,Spring Framework 4.0支持Bean Validation 1.0(JSR-303)和Bean Validation 1.1(JSR-349),也将其改写成了Spring的Validator接口. 正如5.8 Spring验证所述,应用程序可以选择一次性全局启用Bean验证,并使其专门用于所有的验证需求. 正如5.8.3 配置DataBinder所述,应用程

《Spring 5 官方文档》18. Web MVC 框架(四)

使用@ResponseBody注释映射响应体 该@ResponseBody注释是类似@RequestBody.该注释可以放在一个方法上,并指示返回类型应该直接写入HTTP响应体(而不是放在模型中,或者解释为视图名称).例如: @GetMapping("/ something") @ResponseBody public String helloWorld(){ return "Hello World" ; } 上述示例将导致文本Hello World被写入HTTP响

《Spring 5 官方文档》18. Web MVC 框架(十)

18.16配置Spring MVC 第18.2.1节"WebApplicationContext中的特殊Bean类型"和第18.2.2节"默认DispatcherServlet配置"解释了Spring MVC的特殊bean以及该使用的默认实现DispatcherServlet.在本节中,您将了解配置Spring MVC的两种其他方法.即MVC Java配置和MVC XML命名空间. MVC Java配置和MVC命名空间提供了类似的默认配置,可以覆盖Dispatche

《Spring 5 官方文档》18. Web MVC 框架(五)

自定义WebDataBinder初始化 要通过Spring定制与PropertyEditor的请求参数绑定 WebDataBinder,可以使用@InitBinder控制器中的-annotated @InitBinder方法,@ControllerAdvice类中的方法或提供自定义 WebBindingInitializer.有关更多详细信息,请参阅"使用@ControllerAdvice和@RestControllerAdvice建议控制器"一节. 使用@InitBinder自定义数

《Spring 5 官方文档》4. 资源(二)

4.6 资源依赖 如果bean本身将通过某种动态过程来确定和提供资源路径,那么bean可以使用ResourceLoader接口来加载资源. j假设以某种方式加载一个模板,其中需要的特定资源取决于用户的角色. 如果资源是静态的,那么完全消除ResourceLoader接口的使用是有意义的,只需让bean公开它需要的Resource属性,那么它们就会以你所期望的方式被注入. 什么使得它们轻松注入这些属性,是所有应用程序上下文注册和使用一个特殊的JavaBeans PropertyEditor,它可以

《Spring 5 官方文档》18. Web MVC 框架(八)

18.8.5 LocaleChangeInterceptor 您可以通过添加LocaleChangeInterceptor到其中一个处理程序映射来启用更改区域设置(请参见第18.4节"处理程序映射").它将检测请求中的一个参数并更改区域设置.它呼吁setLocale()在LocaleResolver上下文中也存在.以下示例显示,对包含*.view名为的参数的所有资源的调用siteLanguage现在将更改语言环境.因此,例如,对以下URL的请求http://www.sf.net/hom

《Spring 5 官方文档》16.ORM和数据访问(二)

16.3.4编程式事务划分 开发者可以在应用程序的更高级别上对事务进行标定,而不用考虑低级别的数据访问执行了多少操作.这样不会对业务服务的实现进行限制:只需要定义一个Spring的PlatformTransactionManager即可.当然,PlatformTransactionManager可以从多处获取,但最好是通过setTransactionManager(..)方法以Bean来注入,正如ProductDAO应该由setProductDao(..)方法配置一样.下面的代码显示Spring

《Spring 5 官方文档》18. Web MVC 框架(三)

Consumable Media 类型 您可以通过指定consumable media类型的列表来缩小主要映射. 只有当Content-Type请求头与指定的媒体类型匹配时,才会匹配该请求. 例如: @PostMapping(path = "/pets", consumes = "application/json") public void addPet(@RequestBody Pet pet, Model model) { // implementation o