程序员人生 网站导航

SpringMVC之类型转换Converter

栏目:综合技术时间:2015-01-16 08:12:44

SpringMVC之类型转换Converter

1.1     目录

1.1      目录

1.2      前言

1.3      Converter接口

1.4      ConversionService接口

1.5      ConverterFactory接口

1.6      GenericConverter接口

1.6.1     概述

1.6.2     ConditionalGenericConverter 接口

 

1.2     前言

       在以往我们需要SpringMVC为我们自动进行类型转换的时候都是用的PropertyEditor。通过PropertyEditor的setAsText()方法我们可以实现字符串向特定类型的转换。但是这里有1个限制是它只支持从String类型转为其他类型。在Spring3中引入了1个Converter接口,它支持从1个Object转为另外一个Object。除Converter接口以外,实现ConverterFactory接口和GenericConverter接口也能够实现我们自己的类型转换逻辑。

1.3     Converter接口

       我们先来看1下Converter接口的定义:

Java代码 收藏代码
  1. public interface Converter<S, T> {  
  2.      
  3.     T convert(S source);  
  4.    
  5. }  

 

       我们可以看到这个接口是使用了泛型的,第1个类型表示原类型,第2个类型表示目标类型,然后里面定义了1个convert方法,将原类型对象作为参数传入进行转换以后返回目标类型对象。当我们需要建立自己的converter的时候就能够实现该接口。下面假定有这样1个需求,有1个文章实体,在文章中是可以有附件的,而附件我们需要记录它的要求地址、大小和文件名,所以这个时候文章应当是包括1个附件列表的。在实现的时候我们的附件是实时上传的,上传后由服务端返回对应的附件要求地址、大小和文件名,附件信息不直接寄存在数据库中,而是作为文章的属性1起寄存在Mongodb中。客户端获得到这些信息以后做1个简单的展现,然后把它们封装成特定格式的字符串作为隐藏域跟随文章1起提交到服务端。在服务端我们就需要把这些字符串附件信息转换为对应的List<Attachment>。所以这个时候我们就建立1个String[]到List<Attachment>的Converter。代码以下:

Java代码 收藏代码
  1. import java.util.ArrayList;  
  2. import java.util.List;  
  3.    
  4. import org.springframework.core.convert.converter.Converter;  
  5.    
  6. import com.tiantian.blog.model.Attachment;  
  7.    
  8. public class StringArrayToAttachmentList implements Converter<String[], List<Attachment>> {  
  9.    
  10.     @Override  
  11.     public List<Attachment> convert(String[] source) {  
  12.        if (source == null)  
  13.            return null;  
  14.        List<Attachment> attachs = new ArrayList<Attachment>(source.length);  
  15.        Attachment attach = null;  
  16.        for (String attachStr : source) {  
  17.            //这里假定我们的Attachment是以“name,requestUrl,size”的情势拼接的。  
  18.            String[] attachInfos = attachStr.split(",");  
  19.            if (attachInfos.length != 3)//当按逗号分隔的数组长度不为3时就抛1个异常,说明非法操作了。  
  20.               throw new RuntimeException();  
  21.            String name = attachInfos[0];  
  22.            String requestUrl = attachInfos[1];  
  23.            int size;  
  24.            try {  
  25.               size = Integer.parseInt(attachInfos[2]);  
  26.            } catch (NumberFormatException e) {  
  27.               throw new RuntimeException();//这里也要抛1个异常。  
  28.            }  
  29.            attach = new Attachment(name, requestUrl, size);  
  30.            attachs.add(attach);  
  31.        }  
  32.        return attachs;  
  33.     }  
  34.    
  35. }  

 

 

1.4     ConversionService接口

       在定义好Converter以后,就是使用Converter了。为了统1调用Converter进行类型转换,Spring为我们提供了1个ConversionService接口。通过实现这个接口我们可以实现自己的Converter调用逻辑。我们先来看1下ConversionService接口的定义:

Java代码 收藏代码
  1. public interface ConversionService {  
  2.    
  3.     boolean canConvert(Class<?> sourceType, Class<?> targetType);  
  4.    
  5.     <T> T convert(Object source, Class<T> targetType);  
  6.      
  7.     boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);  
  8.    
  9.     Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);  
  10.    
  11. }  

 

       我们可以看到ConversionService接口里面定义了两个canConvert方法和两个convert方法,canConvert方法用于判断当前的ConversionService是不是能够对原类型和目标类型进行转换,convert方法则是用于进行类型转换的。上面出现的参数类型TypeDescriptor是对1种类型的封装,里面包括该种类型的值、实际类型等等信息。

在定义了ConversionService以后我们就能够把它定义为1个bean对象,然后指定<mvn:annotation-driven/>的conversion-service属性为我们自己定义的ConversionService bean对象。如:

Xml代码 收藏代码
  1. <mvc:annotation-driven conversion-service="myConversionService"/>  
  2.   
  3. <bean id="myConversionService" class="com.tiantian.blog.web.converter.support.MyConversionService"/>  

 

       这样当SpringMVC需要进行类型转换的时候就会调用ConversionService的canConvert和convert方法进行类型转换。

       1般而言我们在实现ConversionService接口的时候也会实现ConverterRegistry接口。使用ConverterRegistry可使我们对类型转换器做1个统1的注册。ConverterRegistry接口的定义以下:

Java代码 收藏代码
  1. public interface ConverterRegistry {  
  2.      
  3.     void addConverter(Converter<?, ?> converter);  
  4.    
  5.     void addConverter(GenericConverter converter);  
  6.    
  7.     void addConverterFactory(ConverterFactory<?, ?> converterFactory);  
  8.    
  9.     void removeConvertible(Class<?> sourceType, Class<?> targetType);  
  10.    
  11. }  

 

       正如前言所说的,要实现自己的类型转换逻辑我们可以实现Converter接口、ConverterFactory接口和GenericConverter接口,ConverterRegistry接口就分别为这3种类型提供了对应的注册方法,至于里面的逻辑就能够发挥自己的设计能力进行设计实现了。

       对ConversionService,Spring已为我们提供了1个实现,它就是GenericConversionService,位于org.springframework.core.convert.support包下面,它实现了ConversionService接口和ConverterRegistry接口。但是不能直接把它作为SpringMVC的ConversionService,由于直接使用时不能往里面注册类型转换器。也就是说不能像下面这样使用:

Xml代码 收藏代码
  1. <mvc:annotation-driven conversion-service="conversionService"/>  
  2.   
  3. <bean id="conversionService" class="org.springframework.core.convert.support.GenericConversionService"/>  

 

       为此我们必须对GenericConversionService做1些封装,比如说我们可以在自己的ConversionService里面注入1个GenericConversionService,然后通过自己的ConversionService的属性接收Converter并把它们注入到GenericConversionService中,以后所有关于ConversionService的方法逻辑都可以调用GenericConversionService对应的逻辑。依照这类思想我们的ConversionService大概是这样的:

Java代码 收藏代码
  1. package com.tiantian.blog.web.converter.support;  
  2.    
  3. import java.util.Set;  
  4.    
  5. import javax.annotation.PostConstruct;  
  6.    
  7. import org.springframework.beans.factory.annotation.Autowired;  
  8. import org.springframework.core.convert.ConversionService;  
  9. import org.springframework.core.convert.TypeDescriptor;  
  10. import org.springframework.core.convert.converter.Converter;  
  11. import org.springframework.core.convert.converter.ConverterFactory;  
  12. import org.springframework.core.convert.converter.GenericConverter;  
  13. import org.springframework.core.convert.support.GenericConversionService;  
  14.    
  15. public class MyConversionService implements ConversionService {  
  16.    
  17.     @Autowired  
  18.     private GenericConversionService conversionService;  
  19.     private Set<?> converters;  
  20.      
  21.     @PostConstruct  
  22.     public void afterPropertiesSet() {  
  23.        if (converters != null) {  
  24.            for (Object converter : converters) {  
  25.               if (converter instanceof Converter<?, ?>) {  
  26.                   conversionService.addConverter((Converter<?, ?>)converter);  
  27.               } else if (converter instanceof ConverterFactory<?, ?>) {  
  28.                   conversionService.addConverterFactory((ConverterFactory<?, ?>)converter);  
  29.               } else if (converter instanceof GenericConverter) {  
  30.                   conversionService.addConverter((GenericConverter)converter);  
  31.               }  
  32.            }  
  33.        }  
  34.     }  
  35.      
  36.     @Override  
  37.     public boolean canConvert(Class<?> sourceType, Class<?> targetType) {  
  38.        return conversionService.canConvert(sourceType, targetType);  
  39.     }  
  40.    
  41.     @Override  
  42.     public boolean canConvert(TypeDescriptor sourceType,  
  43.            TypeDescriptor targetType) {  
  44.        return conversionService.canConvert(sourceType, targetType);  
  45.     }  
  46.    
  47.     @Override  
  48.     public <T> T convert(Object source, Class<T> targetType) {  
  49.        return conversionService.convert(source, targetType);  
  50.     }  
  51.    
  52.     @Override  
  53.     public Object convert(Object source, TypeDescriptor sourceType,  
  54.            TypeDescriptor targetType) {  
  55.        return conversionService.convert(source, sourceType, targetType);  
  56.     }  
  57.    
  58.     public Set<?> getConverters() {  
  59.        return converters;  
  60.     }  
  61.    
  62.     public void setConverters(Set<?> converters) {  
  63.        this.converters = converters;  
  64.     }  
  65.    
  66. }  

 

       在上面代码中,通过converters属性我们可以接收需要注册的Converter、ConverterFactory和GenericConverter,在converters属性设置完成以后afterPropertiesSet方法会被调用,在这个方法里面我们把接收到的converters都注册到注入的GenericConversionService中了,以后关于ConversionService的其他操作都是通过这个GenericConversionService来完成的。这个时候我们的SpringMVC文件可以这样配置:

Xml代码 收藏代码
  1. <mvc:annotation-driven conversion-service="conversionService"/>  
  2.   
  3. <bean id="genericConversionService" class="org.springframework.core.convert.support.GenericConversionService"/>  
  4.   
  5. <bean id="conversionService" class="com.tiantian.blog.web.converter.support.MyConversionService">  
  6.    <property name="converters">  
  7.        <set>  
  8.           <bean class="com.tiantian.blog.web.converter.StringArrayToAttachmentList"/>  
  9.        </set>  
  10.    </property>  
  11. </bean>  

 

       除以上这类使用GenericConversionService的思想以外,Spring已为我们提供了1个既可使用GenericConversionService,又可以注入Converter的类,那就是ConversionServiceFactoryBean。该类为我们提供了1个可以接收Converter的converters属性,在它的内部有1个GenericConversionService对象的援用,在对象初始化完成以后它会new1个GenericConversionService对象,并往GenericConversionService中注册converters属性指定的Converter和Spring本身已实现了的默许Converter,以后每次返回的都是这个GenericConversionService对象。当使用ConversionServiceFactoryBean的时候我们的SpringMVC文件可以这样配置:

Xml代码 收藏代码
  1.    <mvc:annotation-driven conversion-service="conversionService"/>  
  2. <bean id="conversionService"  
  3.   class="org.springframework.context.support.ConversionServiceFactoryBean">  
  4.     <property name="converters">  
  5.         <list>  
  6.             <bean class="com.tiantian.blog.web.converter.StringArrayToAttachmentList"/>  
  7.         </list>  
  8.     </property>  
  9. </bean>  

 

 

       除ConversionServiceFactoryBean以外,Spring还为我们提供了1个FormattingConversionServiceFactoryBean。当使用FormattingConversionServiceFactoryBean的时候我们的SpringMVC配置文件的定义应当是这样:

Xml代码 收藏代码
  1.     <mvc:annotation-driven conversion-service="conversionService"/>  
  2.    
  3.     <bean id="conversionService"  
  4.           class="org.springframework.format.support.FormattingConversionServiceFactoryBean">  
  5.           <property name="converters">  
  6.              <set>  
  7.                  <bean class="com.tiantian.blog.web.converter.StringArrayToAttachmentList"/>  
  8.              </set>  
  9.           </property>  
  10. </bean>  

 

       以上介绍的是SpringMVC自动进行类型转换时需要我们做的操作。如果我们需要在程序里面手动的来进行类型转换的话,我们也能够往我们的程序里面注入1个ConversionService,然后通过ConversionService来进行相应的类型转换操作,也能够把Converter直接注入到我们的程序中。

1.5     ConverterFactory接口

       ConverterFactory的出现可让我们统1管理1些相干联的Converter。顾名思义,ConverterFactory就是产生Converter的1个工厂,确切ConverterFactory就是用来产生Converter的。我们先来看1下ConverterFactory接口的定义:

Java代码 收藏代码
  1. public interface ConverterFactory<S, R> {  
  2.      
  3.     <T extends R> Converter<S, T> getConverter(Class<T> targetType);  
  4.    
  5. }  

 

       我们可以看到ConverterFactory接口里面就定义了1个产生Converter的getConverter方法,参数是目标类型的class。我们可以看到ConverterFactory中1共用到了3个泛型,S、R、T,其中S表示原类型,R表示目标类型,T是类型R的1个子类。

斟酌这样1种情况,我们有1个表示用户状态的枚举类型UserStatus,如果要定义1个从String转为UserStatus的Converter,根据之前Converter接口的说明,我们的StringToUserStatus大概是这个模样:

Java代码 收藏代码
  1. public class StringToUserStatus implements Converter<String, UserStatus> {  
  2.   
  3.    @Override  
  4.    public UserStatus convert(String source) {  
  5.        if (source == null) {  
  6.           return null;  
  7.        }  
  8.        return UserStatus.valueOf(source);  
  9.    }  
  10.     
  11. }  

 

       如果这个时候有另外1个枚举类型UserType,那末我们就需要定义另外1个从String转为UserType的Converter――StringToUserType,那末我们的StringToUserType大概是这个模样:

Java代码 收藏代码
  1. public class StringToUserType implements Converter<String, UserType> {  
  2.   
  3.    @Override  
  4.    public UserType convert(String source) {  
  5.        if (source == null) {  
  6.           return null;  
  7.        }  
  8.        return UserType.valueOf(source);  
  9.    }  
  10.     
  11. }  

 

       如果还有其他枚举类型需要定义原类型为String的Converter的时候,我们还得像上面那样定义对应的Converter。有了ConverterFactory以后,这1切都变得非常简单,由于UserStatus、UserType等其他枚举类型同属于枚举,所以这个时候我们就能够统1定义1个从String到Enum的ConverterFactory,然后从中获得对应的Converter进行convert操作。Spring官方已为我们实现了这么1个StringToEnumConverterFactory:

Java代码 收藏代码
  1. @SuppressWarnings("unchecked")  
  2. final class StringToEnumConverterFactory implements ConverterFactory<String, Enum> {  
  3.    
  4.     public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {  
  5.        return new StringToEnum(targetType);  
  6.     }  
  7.    
  8.     private class StringToEnum<T extends Enum> implements Converter<String, T> {  
  9.    
  10.        private final Class<T> enumType;  
  11.    
  12.        public StringToEnum(Class<T> enumType) {  
  13.            this.enumType = enumType;  
  14.        }  
  15.    
  16.        public T convert(String source) {  
  17.            if (source.length() == 0) {  
  18.               // It's an empty enum identifier: reset the enum value to null.  
  19.               return null;  
  20.            }  
  21.            return (T) Enum.valueOf(this.enumType, source.trim());  
  22.        }  
  23.     }  
  24.    
  25. }  

 

       这样,如果是要进行String到UserStatus的转换,我们就能够通过StringToEnumConverterFactory实例的getConverter(UserStatus.class).convert(string)获得到对应的UserStatus,如果是要转换为UserType的话就是getConverter(UserType.class).convert(string)。这样就非常方便,可以很好的支持扩大。

       对ConverterFactory我们也能够把它当作ConvertionServiceFactoryBean的converters属性进行注册,在ConvertionServiceFactoryBean内部进行Converter注入的时候会根据converters属性具体元素的具体类型进行不同的注册,对FormattingConversionServiceFactoryBean也是一样的方式进行注册。所以如果我们自己定义了1个StringToEnumConverterFactory,我们可以这样来进行注册:

Xml代码 收藏代码
  1. <bean id="conversionService"  
  2.   class="org.springframework.context.support.ConversionServiceFactoryBean">  
  3.     <property name="converters">  
  4.         <list>  
  5.             <bean class="com.tiantian.blog.web.converter.StringArrayToAttachmentList"/>  
  6.             <bean class="com.tiantian.blog.web.converter.StringToEnumConverterFactory"/>  
  7.         </list>  
  8.     </property>  
  9. </bean>  

 

1.6     GenericConverter接口

1.6.1概述

GenericConverter接口是所有的Converter接口中最灵活也是最复杂的1个类型转换接口。像我们之前介绍的Converter接口只支持从1个原类型转换为1个目标类型;ConverterFactory接口只支持从1个原类型转换为1个目标类型对应的子类型;而GenericConverter接口支持在多个不同的原类型和目标类型之间进行转换,这也就是GenericConverter接口灵活和复杂的地方。

       我们先来看1下GenericConverter接口的定义:

Java代码 收藏代码
  1. public interface GenericConverter {  
  2.      
  3.     Set<ConvertiblePair> getConvertibleTypes();  
  4.    
  5.     Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);  
  6.    
  7.     public static final class ConvertiblePair {  
  8.    
  9.        private final Class<?> sourceType;  
  10.    
  11.        private final Class<?> targetType;  
  12.    
  13.        public ConvertiblePair(Class<?> sourceType, Class<?> targetType) {  
  14.            Assert.notNull(sourceType, "Source type must not be null");  
  15.            Assert.notNull(targetType, "Target type must not be null");  
  16.            this.sourceType = sourceType;  
  17.            this.targetType = targetType;  
  18.        }  
  19.    
  20.        public Class<?> getSourceType() {  
  21.            return this.sourceType;  
  22.        }  
  23.    
  24.        public Class<?> getTargetType() {  
  25.            return this.targetType;  
  26.        }  
  27.     }  
  28.    
  29. }  

 

      我们可以看到GenericConverter接口中1共定义了两个方法,getConvertibleTypes()和convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType)。getConvertibleTypes方法用于返回这个GenericConverter能够转换的原类型和目标类型的这么1个组合;convert方法则是用于进行类型转换的,我们可以在这个方法里面实现我们自己的转换逻辑。之所以说GenericConverter是最复杂的是由于它的转换方法convert的参数类型TypeDescriptor是比较复杂的。TypeDescriptor对类型Type进行了1些封装,包括value、Field及其对应的真实类型等等,具体的可以查看API。

       关于GenericConverter的使用,这里也举1个例子。假定我们有1项需求是希望能通过user的id或username直接转换为对应的user对象,那末我们就能够针对id和username来建立1个GenericConverter。这里假定id是int型,而username是String型的,所以我们的GenericConverter可以这样来写:

Java代码 收藏代码
  1. public class UserGenericConverter implements GenericConverter {  
  2.    
  3.     @Autowired  
  4.     private UserService userService;  
  5.      
  6.     @Override  
  7.     public Object convert(Object source, TypeDescriptor sourceType,  
  8.            TypeDescriptor targetType) {  
  9.        if (source == null || sourceType == TypeDescriptor.NULL || targetType == TypeDescriptor.NULL) {  
  10.            return null;  
  11.        }  
  12.        User user = null;  
  13.        if (sourceType.getType() == Integer.class) {  
  14.            user = userService.findById((Integer) source);//根据id来查找user  
  15.        } else if (sourceType.getType() == String.class) {  
  16.            user = userService.find((String)source);//根据用户名来查找user  
  17.        }  
  18.        return user;  
  19.     }  
  20.    
  21.     @Override  
  22.     public Set<ConvertiblePair> getConvertibleTypes() {  
  23.        Set<ConvertiblePair> pairs = new HashSet<ConvertiblePair>();  
  24.        pairs.add(new ConvertiblePair(Integer.class, User.class));  
  25.        pairs.add(new ConvertiblePair(String.class, User.class));  
  26.        return pairs;  
  27.     }  
  28.    
  29. }  

 

       我们可以看到在上面定义的UserGenericConverter中,我们在getConvertibleTypes方法中添加了两组转换的组合,Integer到User和String到User。然后我们给UserGenericConverter注入了1个UserService,在convert方法

中我们简单的根据原类型是Integer还是String来判断传递的原数据是id还是username,并利用UserService对应的方法返回相应的User对象。

       GenericConverter接口实现类的注册方法跟Converter接口和ConverterFactory接口实现类的注册方法是1样的,这里就不再赘述了。

       虽然Converter接口、ConverterFactory接口和GenericConverter接口之间没有任何的关系,但是Spring内部在注册Converter实现类和ConverterFactory实现类时是先把它们转换为GenericConverter,以后再统1对GenericConverter进行注册

------分隔线----------------------------
------分隔线----------------------------

最新技术推荐