文档章节

【原创】遨游springmvc之Converter

开源中国首席脑科主任
 开源中国首席脑科主任
发布于 2016/08/08 11:50
字数 2854
阅读 52
收藏 2

1.前言

在前一篇WebDataBinder中讲述了了一个PropertyEditor,它通过setAsText满足了字符串到指定类型的转换,但是它实现不了从任意类型转换到目标类型,所以在spring3.x之后引入了Converter,它实现了上述需求的转换。

2.原理

2.1 工作原理图

2.2 原理介绍

①:类型转换:内部的ConversionService会根据S源类型/T目标类型自动选择相应的Converter SPI进行类型转换,而且是强类型的,能在任意类型数据之间进行转换;
②:数据验证:支持JSR-303验证框架,如将@Valid放在需要验证的目标类型上即可;
③:格式化显示:其实就是任意目标类型---->String的转换,完全可以使用Converter SPI完成。
 
Spring为了更好的诠释格式化/解析功能提供了Formatter SPI,支持根据Locale信息进行格式化/解析,而且该套SPI可以支持字段/参数级别的细粒度格式化/解析,流程如下:
①:类型解析(转换):String---->T类型目标对象的解析,和PropertyEditor类似;
②:数据验证:支持JSR-303验证框架,如将@Valid放在需要验证的目标类型上即可;
③:格式化显示:任意目标类型---->String的转换,和PropertyEditor类似。

 

3.Converter

3.1 接口介绍

public interface Converter<S, T> {

	/**
	 * Convert the source object of type {@code S} to target type {@code T}.
	 * @param source the source object to convert, which must be an instance of {@code S} (never {@code null})
	 * @return the converted object, which must be an instance of {@code T} (potentially {@code null})
	 * @throws IllegalArgumentException if the source cannot be converted to the desired target type
	 */
	T convert(S source);

}

Converter只提供了一个convert方法,其中S代表源类型,T代表目标类型

3.2 实例

3.2.1 TelephoneConverter

public class TelephoneConverter implements Converter<String, Telephone> {
    @Override
    public Telephone convert(String source) {
        if (source.matches("\\d{3,4}-\\d{7,8}")) {
            String[] telephoneArray = source.split("-");
            return new Telephone(telephoneArray[0], telephoneArray[1]);
        }
        return null;
    }
}

3.2.2 控制器

    @RequestMapping (value="/converter/1",method= RequestMethod.GET)
    @ResponseBody
    public Person demo1(Person p) {
        return p;
    }

3.2.3 配置

如果我们同时配置了PropertyEditor和Converter,spring默认先作用PropertyEditor,再作用Converter,但是最好不要这样子2个都上。

    <mvc:annotation-driven conversion-service="myConverterService"></mvc:annotation-driven>
    
    <bean id="myConverterService" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <bean class="com.kings.template.mvc.TelephoneConverter"></bean>
            </set>
        </property>
    </bean>

 

3.3 spring提供的转换器

在spring-core下的org.springframework.core.convert包下提供了许多内置的converter

标量转换器

转换器类 功能
StringToBooleanConverter String----->Boolean true: true/on/yes/1;false: false/off/no/0
ObjectToStringConverter Object----->String 调用toString方法转换
StringToNumberConverterFactory String----->Number(如Integer、Long等)
NumberToNumberConverterFactory Number子类型(Integer、Long、Double等)-----> Number子类型(Integer、Long、Double等)
StringToCharacterConverter String----->java.lang.Character 取字符串第一个字符
NumberToCharacterConverter Number子类型(Integer、Long、Double等)-----> java.lang.Character
CharacterToNumberFactory java.lang.Character ----->Number子类型(Integer、Long、Double等)
StringToEnumConverterFactory String----->enum类型
EnumToStringConverter enum类型----->String 返回enum对象的name()值
StringToLocaleConverter String----->java.util.Local
PropertiesToStringConverter java.util.Properties----->String
StringToPropertiesConverter String----->java.util.Properties

集合、数组相关转换器

转换器类 功能
ArrayToCollectionConverter 任意S数组---->任意T集合(List、Set)
CollectionToArrayConverter 任意T集合(List、Set)---->任意S数组
ArrayToArrayConverter 任意S数组<---->任意T数组
CollectionToCollectionConverter 任意T集合(List、Set)<---->任意T集合(List、Set)
MapToMapConverter Map<---->Map之间的转换
ArrayToStringConverter 任意S数组---->String类型
StringToArrayConverter String----->数组 默认通过“,”分割,且去除字符串的两边空格(trim)
ArrayToObjectConverter 任意S数组---->任意Object的转换
ObjectToArrayConverter Object----->单元素数组
CollectionToStringConverter 任意T集合(List、Set)---->String类型
StringToCollectionConverter String----->集合(List、Set)
CollectionToObjectConverter 任意T集合---->任意Object的转换
ObjectToCollectionConverter Object----->单元素集合

默认(fallback)转换器之前的转换器不能转换时调用

转换器类 功能
ObjectToObjectConverter Object(S)----->Object(T)首先尝试valueOf进行转换、没有则尝试new 构造器(S)
IdToEntityConverter Id(S)----->Entity(T)
FallbackObjectToStringConverter Object----->String 最终转换方法,调用toString()

 

4.ConverterFactory

需求:需要将一个类中的String转换成Enum,而且是有多个,如:

@Data
public class Person {
    private String name;
    
    private Telephone telephone;
    
    private Sex sex;
    
    private Race race;

那么我们再通过写一个SexConverter和RaceConverter是可以实现,但是我们当然有更懒的实现方法。

通过工厂方法,抽象出将String转化成枚举的过程,省去每次再去定义一个方法去实现类似业务的转化器。

4.1 接口介绍

public interface ConverterFactory<S, R> {

	/**
	 * Get the converter to convert from S to target type T, where T is also an instance of R.
	 * @param <T> the target type
	 * @param targetType the target type to convert to
	 * @return A converter from S to T
	 */
	<T extends R> Converter<S, T> getConverter(Class<T> targetType);

}

4.2 实例

我们来一个覆盖spring默认的StringConvertorFactory

4.2.1 工具类

/**
 * <p class="detail">
 * 功能:将枚举的数字转化成枚举列
 * </p>
 * @param <T> the type parameter
 *
 * @author Kings
 * @ClassName Convent tag num 2 emum list.
 * @Version V1.0.
 * @date 2016.03.24 18:46:21
 */
public class ConventNum2Emum<T extends Enum<T>> {
    
    /**
     * <p class="detail">
     * 功能:与或转化
     * </p>
     * @param status   :状态
     * @param enumType :枚举类型
     *
     * @return list
     * @author Kings
     * @date 2016.03.24 18:46:21
     */
    public List<T> convent(Long status,Class enumType) {
        List<T> tags = new ArrayList<T>();
        if(status != null){
            Field[] fields = enumType.getFields();
            for (Field f : fields) {
                Enum<T> e = Enum.valueOf(enumType,f.getName());
                Long eValue = Long.parseLong(e.toString());
                if((eValue & status) == eValue){
                    tags.add((T) e);
                }
            }
        }
        return tags;
    }
    
    /**
     * <p class="detail">
     * 功能:非与或转换
     * </p>
     * @param status   :状态
     * @param enumType :枚举类型
     *
     * @return list
     * @author Kings
     * @date 2016.06.15 10:46:35
     */
    public T conventNormal(Long status, Class enumType) {
        if (status != null) {
            Field[] fields = enumType.getFields();
            for (Field f : fields) {
                Enum<T> e = Enum.valueOf(enumType, f.getName());
                Long eVlue = Long.parseLong(e.toString());
                if (status.longValue() == eVlue.longValue()) {
                    return (T) e;
                }
            }
        }
        return null;
    }
    
}

4.2.1 自定义ConverterFactory

public final class MyStringConverterFactory implements ConverterFactory<String, Enum> {
    
    @Override
    public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
        return new LongToEnum(targetType);
    }
    
    private class LongToEnum<T extends Enum> implements Converter<String, T> {
        
        private final Class<T> enumType;
        
        public LongToEnum(Class<T> enumType) {
            this.enumType = enumType;
        }
        
        @Override
        public T convert(String source) {
            return (T) new ConventNum2Emum().conventNormal(Long.parseLong(source), enumType);
        }
    }
}

4.2.2 配置

    <mvc:annotation-driven conversion-service="myConverterService"></mvc:annotation-driven>
    
    <bean id="myConverterService" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <bean class="com.kings.template.mvc.MyStringConverterFactory"></bean>
            </set>
        </property>
    </bean>

 

5. ConditionalGenericConverter

ConditionalGenericConverter继承了2个重要的接口GenericConverter和ConditionConverter

5.1 接口说明

GenericConverter

/**
	 * Return the source and target types that this converter can convert between.
	 * <p>Each entry is a convertible source-to-target type pair.
	 * <p>For {@link ConditionalConverter conditional converters} this method may return
	 * {@code null} to indicate all source-to-target pairs should be considered.
	 */
	Set<ConvertiblePair> getConvertibleTypes();

	/**
	 * Convert the source object to the targetType described by the {@code TypeDescriptor}.
	 * @param source the source object to convert (may be {@code null})
	 * @param sourceType the type descriptor of the field we are converting from
	 * @param targetType the type descriptor of the field we are converting to
	 * @return the converted object
	 */
	Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);

GenericConverter接口是所有的Converter接口中最灵活也是最复杂的一个类型转换接口。像我们之前介绍的Converter接口只支持从一个原类型转换为一个目标类型;ConverterFactory接口只支持从一个原类型转换为一个目标类型对应的子类型;而GenericConverter接口支持在多个不同的原类型和目标类型之间进行转换,这也就是GenericConverter接口灵活和复杂的地方。GenericConverter接口中一共定义了两个方法,getConvertibleTypes()和convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType)。getConvertibleTypes方法用于返回这个GenericConverter能够转换的原类型和目标类型的这么一个组合;convert方法则是用于进行类型转换的,我们可以在这个方法里面实现我们自己的转换逻辑。

5.2 实例

5.2.1 实体类

为了方便我就直接用User和Person

@Data
public class Person {
    private String name;
    
    private Telephone telephone;
    
    private Sex sex;
    
    private Race race;
    
    private User u;
}

5.2.1 转化器

public class MyGenericConverter implements GenericConverter {
    
    @Autowired
    private UserService userService;
    @Override
    public Set<ConvertiblePair> getConvertibleTypes() {
        Set<ConvertiblePair> pairs = new HashSet<ConvertiblePair>();
        pairs.add(new ConvertiblePair(String.class, User.class));
        //受web层的request.getParamater()的影响,在web层Long作用不了
        pairs.add(new ConvertiblePair(Long.class, User.class));
        return pairs;
    }
    
    @Override
    public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
        if (source == null) {
            return null;
        }
        if(sourceType.getType() == Long.class){
           return userService.selectByPrimaryKey(source); 
        } else if(sourceType.getType() == String.class){
            User u4q = new User();
            u4q.setName(source+"");
            return userService.selectOne(u4q);
        }
        return null;
    }
    
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
}

5.2.3 控制器

    @RequestMapping (value="/converter/2",method= RequestMethod.GET)
    @ResponseBody
    public Person demo2(Person p) {
        return p;
    }

直接访问:http://localhost:8080/kingstemplate/converter/2?u=ws

得到Json结果如下:

{"name":null,"telephone":null,"sex":null,"race":null,"u":{"id":1,"name":"ws","age":26}}

5.2.4 非web项目

在5.2.3里面我们用不了Long转User,因为受web项目的影响,这次我们来个非web项目的

首先注册一个DefaultConversionService,因为它里面有个addConverter方便我么自己加入容器中的converter

<bean id="defaultConversionService" class="org.springframework.core.convert.support.DefaultConversionService"></bean>

再上测试demo

public class ConverterDemo {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("config/spring.xml");
        
        UserService userService = (UserService) context.getBean("userServiceImpl");
        MyGenericConverter myGenericConverter = new MyGenericConverter();
        //注入接口
        myGenericConverter.setUserService(userService);
        
        DefaultConversionService defaultConversionService = (DefaultConversionService) context.getBean("defaultConversionService");
        defaultConversionService.addConverter(myGenericConverter);
        User u = defaultConversionService.convert(1L, User.class);//Long->User
        System.out.println(u.getAge());//age输出不告诉你
        User u1 = defaultConversionService.convert("ws", User.class);//String->User
        System.out.println(u1.getAge());//age输出不告诉你
    }
}

6.ConversionService

一般的ConversionService最底层都会继承ConverterRegistry和ConversionService

6.1 ConverterRegistry

6.1.1 接口说明

类型转换器注册支持,可以注册/删除相应的类型转换器

注册的时候添加了:Converter、GenericConverter、ConverterFactory以及移除某些类型的转换

void addConverter(Converter<?, ?> converter);

	/**
	 * Add a plain converter to this registry.
	 * The convertible source/target type pair is specified explicitly.
	 * <p>Allows for a Converter to be reused for multiple distinct pairs without
	 * having to create a Converter class for each pair.
	 * @since 3.1
	 */
	<S, T> void addConverter(Class<S> sourceType, Class<T> targetType, Converter<? super S, ? extends T> converter);

	/**
	 * Add a generic converter to this registry.
	 */
	void addConverter(GenericConverter converter);

	/**
	 * Add a ranged converter factory to this registry.
	 * The convertible source/target type pair is derived from the ConverterFactory's parameterized types.
	 * @throws IllegalArgumentException if the parameterized types could not be resolved.
	 */
	void addConverterFactory(ConverterFactory<?, ?> converterFactory);

	/**
	 * Remove any converters from sourceType to targetType.
	 * @param sourceType the source type
	 * @param targetType the target type
	 */
	void removeConvertible(Class<?> sourceType, Class<?> targetType);

 

6.2 ConversionService

6.2.1 接口说明

提供运行期类型转换的支持

/**
	 * Return {@code true} if objects of {@code sourceType} can be converted to the {@code targetType}.
	 * <p>If this method returns {@code true}, it means {@link #convert(Object, Class)} is capable
	 * of converting an instance of {@code sourceType} to {@code targetType}.
	 * <p>Special note on collections, arrays, and maps types:
	 * For conversion between collection, array, and map types, this method will return {@code true}
	 * even though a convert invocation may still generate a {@link ConversionException} if the
	 * underlying elements are not convertible. Callers are expected to handle this exceptional case
	 * when working with collections and maps.
	 * @param sourceType the source type to convert from (may be {@code null} if source is {@code null})
	 * @param targetType the target type to convert to (required)
	 * @return {@code true} if a conversion can be performed, {@code false} if not
	 * @throws IllegalArgumentException if {@code targetType} is {@code null}
	 */
	boolean canConvert(Class<?> sourceType, Class<?> targetType);

	/**
	 * Return {@code true} if objects of {@code sourceType} can be converted to the {@code targetType}.
	 * The TypeDescriptors provide additional context about the source and target locations
	 * where conversion would occur, often object fields or property locations.
	 * <p>If this method returns {@code true}, it means {@link #convert(Object, TypeDescriptor, TypeDescriptor)}
	 * is capable of converting an instance of {@code sourceType} to {@code targetType}.
	 * <p>Special note on collections, arrays, and maps types:
	 * For conversion between collection, array, and map types, this method will return {@code true}
	 * even though a convert invocation may still generate a {@link ConversionException} if the
	 * underlying elements are not convertible. Callers are expected to handle this exceptional case
	 * when working with collections and maps.
	 * @param sourceType context about the source type to convert from
	 * (may be {@code null} if source is {@code null})
	 * @param targetType context about the target type to convert to (required)
	 * @return {@code true} if a conversion can be performed between the source and target types,
	 * {@code false} if not
	 * @throws IllegalArgumentException if {@code targetType} is {@code null}
	 */
	boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);

	/**
	 * Convert the given {@code source} to the specified {@code targetType}.
	 * @param source the source object to convert (may be {@code null})
	 * @param targetType the target type to convert to (required)
	 * @return the converted object, an instance of targetType
	 * @throws ConversionException if a conversion exception occurred
	 * @throws IllegalArgumentException if targetType is {@code null}
	 */
	<T> T convert(Object source, Class<T> targetType);

	/**
	 * Convert the given {@code source} to the specified {@code targetType}.
	 * The TypeDescriptors provide additional context about the source and target locations
	 * where conversion will occur, often object fields or property locations.
	 * @param source the source object to convert (may be {@code null})
	 * @param sourceType context about the source type to convert from
	 * (may be {@code null} if source is {@code null})
	 * @param targetType context about the target type to convert to (required)
	 * @return the converted object, an instance of {@link TypeDescriptor#getObjectType() targetType}
	 * @throws ConversionException if a conversion exception occurred
	 * @throws IllegalArgumentException if targetType is {@code null},
	 * or {@code sourceType} is {@code null} but source is not {@code null}
	 */
	Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);

Spring提供了两个默认实现(其都实现了ConverterRegistry、ConversionService接口):
DefaultConversionService:默认的类型转换服务实现;

DefaultFormattingConversionService:带数据格式化支持的类型转换服务实现,一般使用该服务实现即可。

 

发现一个机智的导航😳

© 著作权归作者所有

共有 人打赏支持
开源中国首席脑科主任
粉丝 61
博文 17
码字总数 18226
作品 0
宁波
后端工程师
【原创】遨游springmvc之原理篇

1.Springmvc是什么 spring web mvc是一种基于java实现的请求驱动(请求-响应模型)的web层轻量级框架,spring web mvc采用了MVC(模型-视图-控制器)框架设计,将web层进行职责解耦,围绕核心处理...

开源中国首席脑科主任
2016/07/23
294
0
【原创】遨游springmvc之WebDataBinder

1.前言 先上原理图 在我们学习servlet的时候我们知道有一个方法叫做:request.getParameter("paramName"),它返回的是一个String类型,但是如果一切都是这样子我们开发程序的时候就会显得特别...

开源中国首席脑科主任
2016/08/02
471
1
【原创】遨游springmvc之DispatcherServlet

1.机制 Dispatcher是springmvc前端控制器模式的实现,它提供一个集中的请求处理机制,所有的请求都将由一个单一的处理程序处理,Dispatcher负责请求的派遣,它与spring ioc完美继承,从而可以...

开源中国首席脑科主任
2016/07/23
71
0
如何将Spring Bean注入到JSF Converter

在项目中,因为想在自定义的JSF Converter中使用Spring Bean,经过搜索和测试,有两种方式可以达到目的 1)使用工具类获取Spring Bean,这个是最容易想到的 //需要在Spring配置文件设置//<be...

huntering
2014/01/11
0
0
关于Spring MVC 3.1.x中如何替换数据Converter的问题

参考文献(15-09-20补充): + http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#rest-message-conversion+ http://docs.spring.io/spring/docs/current/sp......

IncRediblE
2014/07/25
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

阿里云API网关使用教程

API 网关(API Gateway)提供高性能、高可用的 API 托管服务,帮助用户对外开放其部署在 ECS、容器服务等阿里云产品上的应用,提供完整的 API 发布、管理、维护生命周期管理。用户只需进行简...

mcy0425
28分钟前
4
0
解决远程登陆误按ctrl+s锁屏假死恢复

使用putty时,偶尔发生屏幕假死,不能输入等情况。 后来发现,只要数据ctrl+s,就会假死;输入ctrl+q就可以恢复过来。 很多刚从windows转移到linux上来工作的朋友,在用vi/vim编辑文件时,常常...

HJCui
31分钟前
0
0
@Transactional

事务管理是应用系统开发中必不可少的一部分。Spring 为事务管理提供了丰富的功能支持。Spring 事务管理分为编程式和声明式的两种方式。编程式事务指的是通过编码方式实现事务;声明式事务基于...

asdf08442a
35分钟前
2
0
widows下强制解除8080端口占用问题

使用win+R打开命令窗口 输入以下命令查看哪个任务占用了8080端口 netstat -ano |findstr "8080" 然后通过任务id强制关闭占用该端口的进程 tskill 10044 // 自己的试情况而定,这个ID是LISTE...

_Artisan
45分钟前
2
0
productFlavors简单实用

最近项目中,不同环境需要配置的参数越来越多,为了减少修改代码次数。研究了一下productFlavors的使用方式,总结如下 1. as3.0以上版本使用productFlavors时需要指定一个flavorDimensions,...

火云
47分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部