本文最后编辑于 前,其中的内容可能需要更新。
Spring的数据转换器 众所周知,在书写Spring的配置文件或者前端请求后端时,我们所有配置项的值或参数值都是字符串的形式存在(上传文件的IO流也类似),根据一定的书写规则,Spring可以将这些原本为string类型的值赋值到对应的bean上或SpringMVC控制层的方法的实参上,这得益于Spring中强大的数据转换能力,下面盘点一波“Spring的数据转换器”。
Spring中的数据转换器主要分两大派系:
PropertyEditor(属性编辑器)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package java.beans;public interface PropertyEditor { void setValue (Object value) ; Object getValue () ; boolean isPaintable () ; void paintValue (java.awt.Graphics gfx, java.awt.Rectangle box) ; String getJavaInitializationString () ; String getAsText () ; void setAsText (String text) throws java.lang.IllegalArgumentException; String[] getTags(); java.awt.Component getCustomEditor () ; boolean supportsCustomEditor () ; void addPropertyChangeListener (PropertyChangeListener listener) ; void removePropertyChangeListener (PropertyChangeListener listener) ; }
PropertyEditor
是JavaBean规范定义的接口,这是java.beans中一个接口,其设计方便对象与String之间的转换工作,而spring将其扩展,方便各种对象与String之间的转换工作。Spring所有的扩展都是通过继承PropertyEditorSupport
,因为它只聚焦于转换上,所以只需复写setAsText()、getAsText()以及构造方法即可实现扩展。
Spring 使用PropertyEditor的接口来实现对象和字符串之间的转换,比如将 2020-01-01转化为日期类型等,可以通过注册自定义编辑器来实现此功能。
应用场景:
在基于xml的配置中,我们往往通过字面值为Bean各种类型的属性提供设置值:如double、int类型,在配置文件配置字面值即可。Spring填充Bean属性时如何将这个字面值转换为对应的类型呢?我们可以隐约地感觉到一定有一个转换器在其中起作用,这个转换器就是属性编辑器。
再者便是Spring MVC框架使用多种PropertyEditor分析绑定HTTP请求的各种参数
Converter(转换器)
Spring的Converter
可以将一种类型转换成另一种类型的一个对象
1 2 3 4 5 6 package org.springframework.core.convert.converter;public interface Converter <S, T> { @Nullable T convert (S source) ; }
Spring提供了3种converter接口:
Converter
接口 :使用最简单,最不灵活,1:1
ConverterFactory
接口 :使用较复杂,比较灵活 1:N
1 2 3 4 5 package org.springframework.core.convert.converter;public interface ConverterFactory <S, R> { <T extends R > Converter<S, T> getConverter (Class<T> targetType) ; }
GenericConverter
接口 :使用最复杂,也最灵活 N:N
1 2 3 4 5 6 7 8 9 10 package org.springframework.core.convert.converter;public interface GenericConverter { @Nullable Set<ConvertiblePair> getConvertibleTypes () ; @Nullable Object convert (@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) ; final class ConvertiblePair { } }
既然有了PropertyEditor,那为何还需要有Converter呢?因为Java原生的PropertyEditor存在以下两点不足:
只能用于字符串和Java对象的转换,不适用于任意两个Java类型之间的转换;
对源对象及目标对象所在的上下文信息(如注解、所在宿主类的结构等)不敏感,在类型转换时不能利用这些上下文信息实施高级转换逻辑。
鉴于此,Spring 3.0在核心模型中添加了一个通用的类型转换模块。Spring希望用这个类型转换体系替换Java标准的PropertyEditor。但由于历史原因,Spring将同时支持两者。在Bean配置、Spring MVC处理方法入参绑定中使用它们。
**注:如今SpringBoot是开发首先,本文所列罗的源码均来自于SpringBoot 2.3.7.RELEASE **
PropertyEditor属性编辑器 PropertyEditor在Bean配置上的使用 以字符串转换为自定义对象为需求;
定义一个Student
实体类
1 2 3 4 5 6 7 8 9 10 @NoArgsConstructor @AllArgsConstructor @Data @ToString public class Student { @Max(100) private Integer id; private String name; }
定义一个Student的属性编辑器,继承PropertyEditorSupport
以实现PropertyEditor
接口
1 2 3 4 5 6 7 8 9 10 11 12 public class StudentEditor extends PropertyEditorSupport { @Override public void setAsText (String text) throws IllegalArgumentException { this .setValue(new Student (1001 ,"张三" )); } }
注入一个属性编辑器的配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 @Configuration public class PropertyEditorConfig { private static Map<Class<?>, Class<? extends PropertyEditor >> customEditors; @Bean public static CustomEditorConfigurer customEditorConfigurer () { CustomEditorConfigurer configurer = new CustomEditorConfigurer (); configurer.setCustomEditors(getPropertyEditors()); return configurer; } public static Map<Class<?>, Class<? extends PropertyEditor >> getPropertyEditors() { if (customEditors == null ) { synchronized (PropertyEditorConfig.class) { if (customEditors == null ) { customEditors = new HashMap <>(4 /3 + 1 ); customEditors.put(Student.class, StudentEditor.class); } } } return customEditors; } }
自定义bean组件需要注入Student类的属性,支持校验
1 2 3 4 5 6 7 8 9 10 @ConfigurationProperties("custom.bean") @Component @Slf4j @Data @NoArgsConstructor @AllArgsConstructor @ToString public class CustomBean { private Student student; }
配置文件
1 2 3 custom: bean: student: (81,张三)
疑点 :Spring如何使用到了我们注册的PropertyEditor?
因为CustomEditorConfigurer
实现了BeanFactoryPostProcessor
接口,往beanFactory注册了我们的PropertyEditor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package org.springframework.beans.factory.config;public class CustomEditorConfigurer implements BeanFactoryPostProcessor , Ordered { @Override public void postProcessBeanFactory (ConfigurableListableBeanFactory beanFactory) throws BeansException { if (this .propertyEditorRegistrars != null ) { for (PropertyEditorRegistrar propertyEditorRegistrar : this .propertyEditorRegistrars) { beanFactory.addPropertyEditorRegistrar(propertyEditorRegistrar); } } if (this .customEditors != null ) { this .customEditors.forEach(beanFactory::registerCustomEditor); } } }
PropertyEditor在MVC参数绑定上的使用 首先要清楚一个概念,MVC的参数绑定看起来很像bean配置过程,基本也是从字符串到java对象的转换,但是前者是MVC模块的功能,后者是beanFactory的能力,MVC只是Spring体系中的一员,IOC中beanFactory才是整个Spring体系的核心。所以数据转换这样的基础功能,MVC的参数绑定是不能使用beanFactory的转换能力的,因为参数绑定过程不是bean的创建过程,创建的对象不是SpringBean 。所以数据转换的功能在MVC模块是需要注册进去才有的,即一次编写,多处注册。
先定义一个控制层的增强
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 @Slf4j @RestControllerAdvice(assignableTypes = {PropertyEditorController.class}) public class CustomControllerAdvice { @InitBinder public void initBinder (WebDataBinder binder) { registerCustomEditor(binder); } private void registerCustomEditor (WebDataBinder binder) { PropertyEditorConfig.getPropertyEditors() .forEach((entityClass,entityPropertyEditorClass) -> registerCustomEditor(binder,entityClass,entityPropertyEditorClass)); } private void registerCustomEditor (WebDataBinder binder,Class<?> entityClass, Class<? extends PropertyEditor> entityPropertyEditorClass) { try { Constructor<? extends PropertyEditor > constructor = entityPropertyEditorClass.getConstructor(); PropertyEditor editor = constructor.newInstance(); binder.registerCustomEditor(entityClass,editor); } catch (ReflectiveOperationException e) { log.error("属性编辑器<{}>没有无参构造方法" ,entityPropertyEditorClass.getTypeName(),e); throw new UnsupportedOperationException (e); } } }
定义控制层
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @Slf4j @RestController public class PropertyEditorController { @GetMapping("/property-editor-assign") public String stringToStudentAssign (@Validated @RequestParam("student") Student student) { log.info("student: {}" ,student); return String.valueOf(student); } @GetMapping("/property-editor-non-assign") @Deprecated public String stringToStudentNonAssign (@Validated Student student) { log.info("student: {}" ,student); return String.valueOf(student); } }
总结 PropertyEditor是线程不安全的,一个实例对应一次String转换操作,而且在IOC启动时的bean配置和MVC参数绑定功能上需要各自注册,且MVC参数绑定增强时不能获取参数类型进行按需注册,退而求其次的做法是全部注册可能的PropertyEditor。
Converter转换器 Converter在Bean配置上的使用 在此之前我们需要了解一个新的接口ConversionService
1 2 3 4 5 6 7 8 9 package org.springframework.core.convert;public interface ConversionService { boolean canConvert (@Nullable Class<?> sourceType, Class<?> targetType) ; boolean canConvert (@Nullable TypeDescriptor sourceType, TypeDescriptor targetType) ; @Nullable <T> T convert (@Nullable Object source, Class<T> targetType) ; @Nullable Object convert (@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType) ; }
顾名思义就是Converter的服务,这个接口通过管理Converter
、ConverterFactory
、GenericConverter
统一对外提供转换服务,所以Spring的的Bean转换操作使用的是ConversionService,贴上ConfigurableBeanFactory
的接口声明
1 2 3 4 5 6 7 8 9 10 package org.springframework.beans.factory.config;public interface ConfigurableBeanFactory extends HierarchicalBeanFactory , SingletonBeanRegistry { void setConversionService (@Nullable ConversionService conversionService) ; }
再看看使用Converter如何实现以字符串转换为自定义对象的需求;
定义实体类Town
1 2 3 4 5 6 7 8 9 10 @NoArgsConstructor @AllArgsConstructor @Data @ToString public class Town { @Max(100) private Integer code; private String name; }
定义转换器且加入IOC
1 2 3 4 5 6 7 8 9 10 11 12 @Component public class TownConverter implements Converter <String, Town> { @Override public Town convert (String source) throws IllegalArgumentException { return new Town (100 ,"南京" ); } }
往IOC中注入配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Configuration public class ConverterConfig { @Bean public ConversionServiceFactoryBean conversionService (@Autowired TownConverter townConverter) { ConversionServiceFactoryBean conversionService = new ConversionServiceFactoryBean (); Set<Converter> converters = new HashSet <>(); converters.add(townConverter); conversionService.setConverters(converters); return conversionService; } }
ConversionServiceFactoryBean
类是一个工厂bean
1 2 3 4 package org.springframework.context.support;public class ConversionServiceFactoryBean implements FactoryBean <ConversionService>, InitializingBean { }
自定义bean组件需要注入Town类的属性,支持校验
1 2 3 4 5 6 7 8 9 10 11 12 13 @Validated @ConfigurationProperties("custom.bean") @Component @Slf4j @Data @NoArgsConstructor @AllArgsConstructor @ToString public class CustomBean { @Valid private Town town; }
配置文件
1 2 3 custom: bean: town: 100 -南京
疑点 :为什么我们注册一个到名为”conversionService”类型为ConversionService
的bean,Spring IOC容器在bean配置时就可以使用这个conversionService来完成属性的数据转换呢?
答案在ConfigurableApplicationContext
和AbstractApplicationContext
的源码里
1 2 3 4 5 6 7 8 9 10 11 package org.springframework.context;public interface ConfigurableApplicationContext extends ApplicationContext , Lifecycle, Closeable { String CONVERSION_SERVICE_BEAN_NAME = "conversionService" ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 package org.springframework.context.support;public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext { protected void finishBeanFactoryInitialization (ConfigurableListableBeanFactory beanFactory) { if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) && beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) { beanFactory.setConversionService( beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)); } } }
AbstractApplicationContext在完成beanFactory的初始化工作时,会从beanFactory中获取名为”conversionService”类型为ConversionService
的bean,将其作为后续beanFactory的转换服务。
Converter在MVC参数绑定上的使用 前面我们已经把TownConverter
加入IOC中了,在Controller上我们可以直接享受其转换能力。
定义控制层
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 @Slf4j @RestController public class ConvertController { @GetMapping("/converter/assign") public String stringToTown (@RequestParam("town") @Validated Town town) { log.info("town: {}" ,town); return String.valueOf(town); } @GetMapping("/converter/non-assign") public String stringToTown2 (@Validated Town town) { log.info("town: {}" ,town); return String.valueOf(town); } }
疑点 :为什么MVC的参数绑定可以直接使用我们加入到IOC的Converter bean的能力而不需要像PropertyEditor那样额外注册呢?
解答:
首先我们知道WebMvcConfigurer
接口是MVC模块的配置接口,其中有一个addFormatters方法,我们可以通过FormatterRegistry注册器注册我们的Formatter(见下文)、Converter等。
1 2 3 4 5 6 package org.springframework.web.servlet.config.annotation;public interface WebMvcConfigurer { default void addFormatters (FormatterRegistry registry) { } }
FormatterRegistry
继承了ConverterRegistry
接口
1 2 3 4 package org.springframework.format;public interface FormatterRegistry extends ConverterRegistry { }
ConverterRegistry
接口可以注册Converter等
1 2 3 4 5 6 7 8 9 package org.springframework.core.convert.converter;public interface ConverterRegistry { void addConverter (Converter<?, ?> converter) ; <S, T> void addConverter (Class<S> sourceType, Class<T> targetType, Converter<? super S, ? extends T> converter) ; void addConverter (GenericConverter converter) ; void addConverterFactory (ConverterFactory<?, ?> factory) ; void removeConvertible (Class<?> sourceType, Class<?> targetType) ; }
切入点:
在MVC的自动装配类WebMvcAutoConfiguration
中,可以看到这样一段代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package org.springframework.boot.autoconfigure.web.servlet;public class WebMvcAutoConfiguration { @Configuration(proxyBeanMethods = false) @Import(EnableWebMvcConfiguration.class) @EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class }) @Order(0) public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer { @Override public void addFormatters (FormatterRegistry registry) { ApplicationConversionService.addBeans(registry, this .beanFactory); } } ]
WebMvcAutoConfigurationAdapter
实现WebMvcConfigurer
接口注册到IOC中,并调用ApplicationConversionService.addBeans(registry, this.beanFactory);
方法注册了一些bean
继续跟源码 ApplicationConversionService.addBeans(registry, this.beanFactory);可以看到beanFactory中的Converter bean被注册到FormatterRegistry中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package org.springframework.boot.convert;public class ApplicationConversionService extends FormattingConversionService { public static void addBeans (FormatterRegistry registry, ListableBeanFactory beanFactory) { Set<Object> beans = new LinkedHashSet <>(); beans.addAll(beanFactory.getBeansOfType(GenericConverter.class).values()); beans.addAll(beanFactory.getBeansOfType(Converter.class).values()); beans.addAll(beanFactory.getBeansOfType(Printer.class).values()); beans.addAll(beanFactory.getBeansOfType(Parser.class).values()); for (Object bean : beans) { if (bean instanceof GenericConverter) { registry.addConverter((GenericConverter) bean); } else if (bean instanceof Converter) { registry.addConverter((Converter<?, ?>) bean); } else if (bean instanceof Formatter) { registry.addFormatter((Formatter<?>) bean); } else if (bean instanceof Printer) { registry.addPrinter((Printer<?>) bean); } else if (bean instanceof Parser) { registry.addParser((Parser<?>) bean); } } } }
同时WebMvcAutoConfigurationAdapter
还引入了EnableWebMvcConfiguration
配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package org.springframework.boot.autoconfigure.web.servlet;public class WebMvcAutoConfiguration { @Configuration(proxyBeanMethods = false) public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware { @Bean @Primary @Override public RequestMappingHandlerMapping requestMappingHandlerMapping ( @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager, @Qualifier("mvcConversionService") FormattingConversionService conversionService, @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) { return super .requestMappingHandlerMapping(contentNegotiationManager, conversionService, resourceUrlProvider); } @Bean @Override public FormattingConversionService mvcConversionService () { Format format = this .mvcProperties.getFormat(); WebConversionService conversionService = new WebConversionService (new DateTimeFormatters () .dateFormat(format.getDate()).timeFormat(format.getTime()).dateTimeFormat(format.getDateTime())); addFormatters(conversionService); return conversionService; } } ]
不难发现,MVC的数据转换服务是由名为”mvcConversionService”的ConversionService完成的,看看mvcConversionService这个bean的创建做了哪些事。
进WebConversionService
源码
1 2 3 4 5 6 7 8 9 10 11 12 13 package org.springframework.boot.autoconfigure.web.format;public class WebConversionService extends DefaultFormattingConversionService { public WebConversionService (DateTimeFormatters dateTimeFormatters) { super (false ); if (dateTimeFormatters.isCustomized()) { addFormatters(dateTimeFormatters); } else { addDefaultFormatters(this ); } } }
DefaultFormattingConversionService
构造器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package org.springframework.format.support;public class DefaultFormattingConversionService extends FormattingConversionService { public DefaultFormattingConversionService () { this (null , true ); } public DefaultFormattingConversionService (boolean registerDefaultFormatters) { this (null , registerDefaultFormatters); } public DefaultFormattingConversionService ( @Nullable StringValueResolver embeddedValueResolver, boolean registerDefaultFormatters) { if (embeddedValueResolver != null ) { setEmbeddedValueResolver(embeddedValueResolver); } DefaultConversionService.addDefaultConverters(this ); if (registerDefaultFormatters) { addDefaultFormatters(this ); } } }
小结,EnableWebMvcConfiguration
配置类提供了MVC的默认配置,并添加注册了框架默认的Converter 到mvcConversionService,利用mvcConversionService完成 RequestMappingHandlerMapping
的配置
如图:
总结 Converter加入IOC可以被MVC管理,从而在参数绑定上使用其能力,但是要注意controller方法写法的区别。在Bean配置上使用Converter则需要手动注册到名为”conversionService”类型为ConversionService
的配置bean中,IOC在bean配置时才能享受到其能力。
数据转换中还有一个特殊的接口,Formatter
1 2 3 package org.springframework.format;public interface Formatter <T> extends Printer <T>, Parser<T> {}
1 2 3 4 package org.springframework.format;public interface Printer <T> { String print (T object, Locale locale) ; }
1 2 3 4 package org.springframework.format;public interface Parser <T> { T parse (String text, Locale locale) throws ParseException; }
容易想到格式化器是java对象和String之间的转换功能在不同地区语言上的加强,所以它是在MVC控制层使用的,根据不同地区信息进行数据转换。
定义实体类User
1 2 3 4 5 6 7 8 9 10 11 @ToString @NoArgsConstructor @AllArgsConstructor @Data public class User { private Long id; private String name; @Email private String email; }
定义格式化器,加入IOC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @Slf4j @Component public class UserFormatter implements Formatter <User> { private static final Pattern USER_STRING_PATTERN = Pattern.compile("[0-9]{1,}-[a-zA-Z\\u4e00-\\u9fa5]{1,}-\\S*" ); @Override public User parse (String text, Locale locale) throws ParseException { log.info("parse: {}" , text); if (!USER_STRING_PATTERN.matcher(text).matches()) { throw new ParseException ("The value [" + text + "] is not matcher format <id-name-email>" ,0 ); } String[] fields = text.split("-" ); return new User (Long.valueOf(fields[0 ]),fields[1 ],fields[2 ]); } @Override public String print (User user, Locale locale) { log.info("print: {}" , user); return user.getId() + "-" + user.getName() + "-" + user.getEmail(); } }
定义controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 @Slf4j @Controller @RequestMapping("/formatter") public class FormatterController { @GetMapping("/user-request-query-assign") @ResponseBody public String formatUserWhenQueryParamAssign (@Validated @RequestParam("user") User user) { log.info("user: {}" ,user); return String.valueOf(user); } @GetMapping("/user-request-query-non-assign") @ResponseBody public String formatUserWhenQueryParamNonAssign (@Validated User user) { log.info("user: {}" ,user); return String.valueOf(user); } }
在MVC后端视图渲染时,我们可以通过格式化来指定数据在视图中的呈现内容,比如有一个pojo
1 2 3 4 5 6 7 8 9 10 11 12 @NoArgsConstructor @AllArgsConstructor @Data @ToString public class NumberWrapper { @NumberSeparate private Integer code; @NumberSeparate('=') @Max(100) private Long number; }
我们希望,视图中使用NumberWrapper的code属性时,将code数值进行单个拆分,如”123” -> “1-2-3”,将number也进行拆分,并指定分隔符为”=”,如如”123” -> “1=2=3”,分隔符通过NumberSeparate
注解指定。
在controller中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Slf4j @Controller @RequestMapping("/formatter") public class FormatterController { @RequestMapping("/number-print") public String numberWrapperPrint (Model model) { NumberWrapper wrapper = new NumberWrapper (123 ,456L ); log.info("wrapper: {}" ,wrapper); model.addAttribute("wrapper" ,wrapper); return "show" ; } }
show.jsp内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %> <%@ taglib prefix="from" uri="http://www.springframework.org/tags/form" %> <html> <head> <title>welcome</title> <meta charset="UTF-8" > </head> <body> <form:form modelAttribute="wrapper" > <%-- 1 -2 -3 --%> <h6><from:input path="code" /></h6> <%-- 4 =5 =6 --%> <h6><from:input path="number" /></h6> </form:form> <H1>JSP</H1> </body> </html>
效果
代码实现
定义注解
1 2 3 4 5 6 7 8 9 10 11 @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface NumberSeparate { @AliasFor("value") char separator () default '-' ; @AliasFor("separator") char value () default '-' ; }
实现一个注解格式化工厂AnnotationFormatterFactory
,且加入IOC容器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 @Component public class NumberSeparateAnnotationFormatterFactory implements AnnotationFormatterFactory <NumberSeparate> { @Override public Set<Class<?>> getFieldTypes() { return new HashSet <>(Arrays.asList(Integer.class,Long.class)); } @Override public Printer<?> getPrinter(NumberSeparate annotation, Class<?> fieldType) { char separator = annotation.separator(); return getFormatter(separator,fieldType); } @Override public Parser<?> getParser(NumberSeparate annotation, Class<?> fieldType) { char separator = annotation.separator(); return getFormatter(separator,fieldType); } private static Formatter<?> getFormatter(char separator, Class<?> clazz) { if (Integer.class.equals(clazz)) { return new NumberSeparateFormatter <>(separator, Integer::valueOf); } else if (Long.class.equals(clazz)) { return new NumberSeparateFormatter <>(separator, Long::valueOf); } else { throw new IllegalArgumentException ("不支持的类型" ); } } private static class NumberSeparateFormatter <T extends Number > implements Formatter <T> { private final char separator; private final Function<String,T> converter; private NumberSeparateFormatter (char separator, Function<String,T> converter) { this .separator = separator; this .converter = converter; } @Override public String print (T object, Locale locale) { char [] chars = object.toString().toCharArray(); StringBuilder sb = new StringBuilder (); for (char aChar : chars) { sb.append(aChar).append(separator); } return sb.substring(0 ,sb.length() - 1 ); } @Override public T parse (String text, Locale locale) throws ParseException { String replace = text.replace(String.valueOf(separator), "" ); try { return converter.apply(replace); } catch (NumberFormatException e) { throw new ParseException ("格式错误" ,0 ); } } } }
这样就可以在view视图上使用Formatter 的格式化能力
同时在参数绑定上也可以完成字符串到java对象转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Slf4j @Controller @RequestMapping("/formatter") public class FormatterController { @GetMapping("/number-parse") @ResponseBody public String numberWrapperParse (NumberWrapper wrapper) { log.info("wrapper: {}" ,wrapper); return String.valueOf(wrapper); } }
疑点 :为什么Formatter在MVC参数绑定上拥有和Converter一样的效果?
还记得MVC的自动装配中,名为”mvcConversionService”类型为 WebConversionService
的bean吗,其继承关系如图,看的出其继承FormattingConversionService
实现了ConversionService 接口
而FormattingConversionService
在注册Formatterr时,通过内部类PrinterConverter
和ParserConverter
进行封装,实际注册的是它俩,所以MVC才拥有了数据转换的能力,故表面上看Formatter拥有和Converter一样的效果,源码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 package org.springframework.format.support;public class FormattingConversionService extends GenericConversionService implements FormatterRegistry , EmbeddedValueResolverAware { @Override public void addPrinter (Printer<?> printer) { Class<?> fieldType = getFieldType(printer, Printer.class); addConverter(new PrinterConverter (fieldType, printer, this )); } @Override public void addParser (Parser<?> parser) { Class<?> fieldType = getFieldType(parser, Parser.class); addConverter(new ParserConverter (fieldType, parser, this )); } @Override public void addFormatter (Formatter<?> formatter) { addFormatterForFieldType(getFieldType(formatter), formatter); } @Override public void addFormatterForFieldType (Class<?> fieldType, Formatter<?> formatter) { addConverter(new PrinterConverter (fieldType, formatter, this )); addConverter(new ParserConverter (fieldType, formatter, this )); } @Override public void addFormatterForFieldType (Class<?> fieldType, Printer<?> printer, Parser<?> parser) { addConverter(new PrinterConverter (fieldType, printer, this )); addConverter(new ParserConverter (fieldType, parser, this )); } private static class PrinterConverter implements GenericConverter { private final Class<?> fieldType; private final TypeDescriptor printerObjectType; @SuppressWarnings("rawtypes") private final Printer printer; private final ConversionService conversionService; } private static class ParserConverter implements GenericConverter { private final Class<?> fieldType; private final Parser<?> parser; private final ConversionService conversionService; } }
总结 Formatter用于数据对不同地区的语言信息进行格式化,如MVC视图的数据呈现格式化,同时因为FormattingConversionService内部对Formatter进行了增强,所以Formatter有了数据转换的能力,而且可以根据语言信息进行不同的转换。