SpringMVC之類型轉換Converter詳解java
本文轉載web
http://www.tuicool.com/articles/uUjaumspring
1.1 目錄
1.1 目錄數據庫
1.2 前言數組
1.3 Converter接口mvc
1.4 ConversionService接口ide
1.5 ConverterFactory接口ui
1.6 GenericConverter接口this
1.6.1 概述spa
1.6.2 ConditionalGenericConverter 接口
1.2 前言
在以往咱們須要SpringMVC爲咱們自動進行類型轉換的時候都是用的PropertyEditor。經過PropertyEditor的setAsText()方法咱們能夠實現字符串向特定類型的轉換。可是這裏有一個限制是它只支持從String類型轉爲其餘類型。在Spring3中引入了一個Converter接口,它支持從一個Object轉爲另外一個Object。除了Converter接口以外,實現ConverterFactory接口和GenericConverter接口也能夠實現咱們本身的類型轉換邏輯。
1.3 Converter接口
咱們先來看一下Converter接口的定義:
- public interface Converter<S, T> {
-
- T convert(S source);
-
- }
咱們能夠看到這個接口是使用了泛型的,第一個類型表示原類型,第二個類型表示目標類型,而後裏面定義了一個convert方法,將原類型對象做爲參數傳入進行轉換以後返回目標類型對象。當咱們須要創建本身的converter的時候就能夠實現該接口。下面假設有這樣一個需求,有一個文章實體,在文章中是能夠有附件的,而附件咱們須要記錄它的請求地址、大小和文件名,因此這個時候文章應該是包含一個附件列表的。在實現的時候咱們的附件是實時上傳的,上傳後由服務端返回對應的附件請求地址、大小和文件名,附件信息不直接存放在數據庫中,而是做爲文章的屬性一塊兒存放在Mongodb中。客戶端獲取到這些信息之後作一個簡單的展現,而後把它們封裝成特定格式的字符串做爲隱藏域跟隨文章一塊兒提交到服務端。在服務端咱們就須要把這些字符串附件信息轉換爲對應的List<Attachment>。因此這個時候咱們就創建一個String[]到List<Attachment>的Converter。代碼以下:
- import java.util.ArrayList;
- import java.util.List;
-
- import org.springframework.core.convert.converter.Converter;
-
- import com.tiantian.blog.model.Attachment;
-
- public class StringArrayToAttachmentList implements Converter<String[], List<Attachment>> {
-
- @Override
- public List<Attachment> convert(String[] source) {
- if (source == null)
- return null;
- List<Attachment> attachs = new ArrayList<Attachment>(source.length);
- Attachment attach = null;
- for (String attachStr : source) {
- //這裏假設咱們的Attachment是以「name,requestUrl,size」的形式拼接的。
- String[] attachInfos = attachStr.split(",");
- if (attachInfos.length != 3)//當按逗號分隔的數組長度不爲3時就拋一個異常,說明非法操做了。
- throw new RuntimeException();
- String name = attachInfos[0];
- String requestUrl = attachInfos[1];
- int size;
- try {
- size = Integer.parseInt(attachInfos[2]);
- } catch (NumberFormatException e) {
- throw new RuntimeException();//這裏也要拋一個異常。
- }
- attach = new Attachment(name, requestUrl, size);
- attachs.add(attach);
- }
- return attachs;
- }
-
- }
1.4 ConversionService接口
在定義好Converter以後,就是使用Converter了。爲了統一調用Converter進行類型轉換,Spring爲咱們提供了一個ConversionService接口。經過實現這個接口咱們能夠實現本身的Converter調用邏輯。咱們先來看一下ConversionService接口的定義:
- 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接口裏面定義了兩個canConvert方法和兩個convert方法,canConvert方法用於判斷當前的ConversionService是否可以對原類型和目標類型進行轉換,convert方法則是用於進行類型轉換的。上面出現的參數類型TypeDescriptor是對於一種類型的封裝,裏面包含該種類型的值、實際類型等等信息。
在定義了ConversionService以後咱們就能夠把它定義爲一個bean對象,而後指定<mvn:annotation-driven/>的conversion-service屬性爲咱們本身定義的ConversionService bean對象。如:
- <mvc:annotation-driven conversion-service="myConversionService"/>
-
- <bean id="myConversionService" class="com.tiantian.blog.web.converter.support.MyConversionService"/>
這樣當SpringMVC須要進行類型轉換的時候就會調用ConversionService的canConvert和convert方法進行類型轉換。
通常而言咱們在實現ConversionService接口的時候也會實現ConverterRegistry接口。使用ConverterRegistry可使咱們對類型轉換器作一個統一的註冊。ConverterRegistry接口的定義以下:
- public interface ConverterRegistry {
-
- void addConverter(Converter<?, ?> converter);
-
- void addConverter(GenericConverter converter);
-
- void addConverterFactory(ConverterFactory<?, ?> converterFactory);
-
- void removeConvertible(Class<?> sourceType, Class<?> targetType);
-
- }
正如前言所說的,要實現本身的類型轉換邏輯咱們能夠實現Converter接口、ConverterFactory接口和GenericConverter接口,ConverterRegistry接口就分別爲這三種類型提供了對應的註冊方法,至於裏面的邏輯就能夠發揮本身的設計能力進行設計實現了。
對於ConversionService,Spring已經爲咱們提供了一個實現,它就是GenericConversionService,位於org.springframework.core.convert.support包下面,它實現了ConversionService接口和ConverterRegistry接口。可是不能直接把它做爲SpringMVC的ConversionService,由於直接使用時不能往裏面註冊類型轉換器。也就是說不能像下面這樣使用:
- <mvc:annotation-driven conversion-service="conversionService"/>
-
- <bean id="conversionService" class="org.springframework.core.convert.support.GenericConversionService"/>
爲此咱們必須對GenericConversionService作一些封裝,好比說咱們能夠在本身的ConversionService裏面注入一個GenericConversionService,而後經過本身的ConversionService的屬性接收Converter並把它們注入到GenericConversionService中,以後全部關於ConversionService的方法邏輯均可以調用GenericConversionService對應的邏輯。按照這種思想咱們的ConversionService大概是這樣的:
- package com.tiantian.blog.web.converter.support;
-
- import java.util.Set;
-
- import javax.annotation.PostConstruct;
-
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.core.convert.ConversionService;
- import org.springframework.core.convert.TypeDescriptor;
- import org.springframework.core.convert.converter.Converter;
- import org.springframework.core.convert.converter.ConverterFactory;
- import org.springframework.core.convert.converter.GenericConverter;
- import org.springframework.core.convert.support.GenericConversionService;
-
- public class MyConversionService implements ConversionService {
-
- @Autowired
- private GenericConversionService conversionService;
- private Set<?> converters;
-
- @PostConstruct
- public void afterPropertiesSet() {
- if (converters != null) {
- for (Object converter : converters) {
- if (converter instanceof Converter<?, ?>) {
- conversionService.addConverter((Converter<?, ?>)converter);
- } else if (converter instanceof ConverterFactory<?, ?>) {
- conversionService.addConverterFactory((ConverterFactory<?, ?>)converter);
- } else if (converter instanceof GenericConverter) {
- conversionService.addConverter((GenericConverter)converter);
- }
- }
- }
- }
-
- @Override
- public boolean canConvert(Class<?> sourceType, Class<?> targetType) {
- return conversionService.canConvert(sourceType, targetType);
- }
-
- @Override
- public boolean canConvert(TypeDescriptor sourceType,
- TypeDescriptor targetType) {
- return conversionService.canConvert(sourceType, targetType);
- }
-
- @Override
- public <T> T convert(Object source, Class<T> targetType) {
- return conversionService.convert(source, targetType);
- }
-
- @Override
- public Object convert(Object source, TypeDescriptor sourceType,
- TypeDescriptor targetType) {
- return conversionService.convert(source, sourceType, targetType);
- }
-
- public Set<?> getConverters() {
- return converters;
- }
-
- public void setConverters(Set<?> converters) {
- this.converters = converters;
- }
-
- }
在上面代碼中,經過converters屬性咱們能夠接收須要註冊的Converter、ConverterFactory和GenericConverter,在converters屬性設置完成以後afterPropertiesSet方法會被調用,在這個方法裏面咱們把接收到的converters都註冊到注入的GenericConversionService中了,以後關於ConversionService的其餘操做都是經過這個GenericConversionService來完成的。這個時候咱們的SpringMVC文件能夠這樣配置:
- <mvc:annotation-driven conversion-service="conversionService"/>
-
- <bean id="genericConversionService" class="org.springframework.core.convert.support.GenericConversionService"/>
-
- <bean id="conversionService" class="com.tiantian.blog.web.converter.support.MyConversionService">
- <property name="converters">
- <set>
- <bean class="com.tiantian.blog.web.converter.StringArrayToAttachmentList"/>
- </set>
- </property>
- </bean>
除了以上這種使用GenericConversionService的思想以外,Spring已經爲咱們提供了一個既可使用GenericConversionService,又能夠注入Converter的類,那就是ConversionServiceFactoryBean。該類爲咱們提供了一個能夠接收Converter的converters屬性,在它的內部有一個GenericConversionService對象的引用,在對象初始化完成以後它會new一個GenericConversionService對象,並往GenericConversionService中註冊converters屬性指定的Converter和Spring自身已經實現了的默認Converter,以後每次返回的都是這個GenericConversionService對象。當使用ConversionServiceFactoryBean的時候咱們的SpringMVC文件能夠這樣配置:
- <mvc:annotation-driven conversion-service="conversionService"/>
- <bean id="conversionService"
- class="org.springframework.context.support.ConversionServiceFactoryBean">
- <property name="converters">
- <list>
- <bean class="com.tiantian.blog.web.converter.StringArrayToAttachmentList"/>
- </list>
- </property>
- </bean>
除了ConversionServiceFactoryBean以外,Spring還爲咱們提供了一個FormattingConversionServiceFactoryBean。當使用FormattingConversionServiceFactoryBean的時候咱們的SpringMVC配置文件的定義應該是這樣:
- <mvc:annotation-driven conversion-service="conversionService"/>
-
- <bean id="conversionService"
- class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
- <property name="converters">
- <set>
- <bean class="com.tiantian.blog.web.converter.StringArrayToAttachmentList"/>
- </set>
- </property>
- </bean>
以上介紹的是SpringMVC自動進行類型轉換時須要咱們作的操做。若是咱們須要在程序裏面手動的來進行類型轉換的話,咱們也能夠往咱們的程序裏面注入一個ConversionService,而後經過ConversionService來進行相應的類型轉換操做,也能夠把Converter直接注入到咱們的程序中。
1.5 ConverterFactory接口
ConverterFactory的出現可讓咱們統一管理一些相關聯的Converter。顧名思義,ConverterFactory就是產生Converter的一個工廠,確實ConverterFactory就是用來產生Converter的。咱們先來看一下ConverterFactory接口的定義:
- public interface ConverterFactory<S, R> {
-
- <T extends R> Converter<S, T> getConverter(Class<T> targetType);
-
- }
咱們能夠看到ConverterFactory接口裏面就定義了一個產生Converter的getConverter方法,參數是目標類型的class。咱們能夠看到ConverterFactory中一共用到了三個泛型,S、R、T,其中S表示原類型,R表示目標類型,T是類型R的一個子類。
考慮這樣一種狀況,咱們有一個表示用戶狀態的枚舉類型UserStatus,若是要定義一個從String轉爲UserStatus的Converter,根據以前Converter接口的說明,咱們的StringToUserStatus大概是這個樣子:
- public class StringToUserStatus implements Converter<String, UserStatus> {
-
- @Override
- public UserStatus convert(String source) {
- if (source == null) {
- return null;
- }
- return UserStatus.valueOf(source);
- }
-
- }
若是這個時候有另一個枚舉類型UserType,那麼咱們就須要定義另一個從String轉爲UserType的Converter——StringToUserType,那麼咱們的StringToUserType大概是這個樣子:
- public class StringToUserType implements Converter<String, UserType> {
-
- @Override
- public UserType convert(String source) {
- if (source == null) {
- return null;
- }
- return UserType.valueOf(source);
- }
-
- }
若是還有其餘枚舉類型須要定義原類型爲String的Converter的時候,咱們還得像上面那樣定義對應的Converter。有了ConverterFactory以後,這一切都變得很是簡單,由於UserStatus、UserType等其餘枚舉類型同屬於枚舉,因此這個時候咱們就能夠統必定義一個從String到Enum的ConverterFactory,而後從中獲取對應的Converter進行convert操做。Spring官方已經爲咱們實現了這麼一個StringToEnumConverterFactory:
- @SuppressWarnings("unchecked")
- final class StringToEnumConverterFactory implements ConverterFactory<String, Enum> {
-
- public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
- return new StringToEnum(targetType);
- }
-
- private class StringToEnum<T extends Enum> implements Converter<String, T> {
-
- private final Class<T> enumType;
-
- public StringToEnum(Class<T> enumType) {
- this.enumType = enumType;
- }
-
- public T convert(String source) {
- if (source.length() == 0) {
- // It's an empty enum identifier: reset the enum value to null.
- return null;
- }
- return (T) Enum.valueOf(this.enumType, source.trim());
- }
- }
-
- }
這樣,若是是要進行String到UserStatus的轉換,咱們就能夠經過StringToEnumConverterFactory實例的getConverter(UserStatus.class).convert(string)獲取到對應的UserStatus,若是是要轉換爲UserType的話就是getConverter(UserType.class).convert(string)。這樣就很是方便,能夠很好的支持擴展。
對於ConverterFactory咱們也能夠把它當作ConvertionServiceFactoryBean的converters屬性進行註冊,在ConvertionServiceFactoryBean內部進行Converter注入的時候會根據converters屬性具體元素的具體類型進行不一樣的註冊,對於FormattingConversionServiceFactoryBean也是一樣的方式進行註冊。因此若是咱們本身定義了一個StringToEnumConverterFactory,咱們能夠這樣來進行註冊:
- <bean id="conversionService"
- class="org.springframework.context.support.ConversionServiceFactoryBean">
- <property name="converters">
- <list>
- <bean class="com.tiantian.blog.web.converter.StringArrayToAttachmentList"/>
- <bean class="com.tiantian.blog.web.converter.StringToEnumConverterFactory"/>
- </list>
- </property>
- </bean>
1.6 GenericConverter接口
1.6.1概述
GenericConverter接口是全部的Converter接口中最靈活也是最複雜的一個類型轉換接口。像咱們以前介紹的Converter接口只支持從一個原類型轉換爲一個目標類型;ConverterFactory接口只支持從一個原類型轉換爲一個目標類型對應的子類型;而GenericConverter接口支持在多個不一樣的原類型和目標類型之間進行轉換,這也就是GenericConverter接口靈活和複雜的地方。
咱們先來看一下GenericConverter接口的定義:
- public interface GenericConverter {
-
- Set<ConvertiblePair> getConvertibleTypes();
-
- Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
-
- public static final class ConvertiblePair {
-
- private final Class<?> sourceType;
-
- private final Class<?> targetType;
-
- public ConvertiblePair(Class<?> sourceType, Class<?> targetType) {
- Assert.notNull(sourceType, "Source type must not be null");
- Assert.notNull(targetType, "Target type must not be null");
- this.sourceType = sourceType;
- this.targetType = targetType;
- }
-
- public Class<?> getSourceType() {
- return this.sourceType;
- }
-
- public Class<?> getTargetType() {
- return this.targetType;
- }
- }
-
- }
咱們能夠看到GenericConverter接口中一共定義了兩個方法,getConvertibleTypes()和convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType)。getConvertibleTypes方法用於返回這個GenericConverter可以轉換的原類型和目標類型的這麼一個組合;convert方法則是用於進行類型轉換的,咱們能夠在這個方法裏面實現咱們本身的轉換邏輯。之因此說GenericConverter是最複雜的是由於它的轉換方法convert的參數類型TypeDescriptor是比較複雜的。TypeDescriptor對類型Type進行了一些封裝,包括value、Field及其對應的真實類型等等,具體的能夠查看API。
關於GenericConverter的使用,這裏也舉一個例子。假設咱們有一項需求是但願能經過user的id或者username直接轉換爲對應的user對象,那麼咱們就能夠針對於id和username來創建一個GenericConverter。這裏假設id是int型,而username是String型的,因此咱們的GenericConverter能夠這樣來寫:
- public class UserGenericConverter implements GenericConverter {
-
- @Autowired
- private UserService userService;
-
- @Override
- public Object convert(Object source, TypeDescriptor sourceType,
- TypeDescriptor targetType) {
- if (source == null || sourceType == TypeDescriptor.NULL || targetType == TypeDescriptor.NULL) {
- return null;
- }
- User user = null;
- if (sourceType.getType() == Integer.class) {
- user = userService.findById((Integer) source);//根據id來查找user
- } else if (sourceType.getType() == String.class) {
- user = userService.find((String)source);//根據用戶名來查找user
- }
- return user;
- }
-
- @Override
- public Set<ConvertiblePair> getConvertibleTypes() {
- Set<ConvertiblePair> pairs = new HashSet<ConvertiblePair>();
- pairs.add(new ConvertiblePair(Integer.class, User.class));
- pairs.add(new ConvertiblePair(String.class, User.class));
- return pairs;
- }
-
- }
咱們能夠看到在上面定義的UserGenericConverter中,咱們在getConvertibleTypes方法中添加了兩組轉換的組合,Integer到User和String到User。而後咱們給UserGenericConverter注入了一個UserService,在convert方法
中咱們簡單的根據原類型是Integer仍是String來判斷傳遞的原數據是id仍是username,並利用UserService對應的方法返回相應的User對象。
GenericConverter接口實現類的註冊方法跟Converter接口和ConverterFactory接口實現類的註冊方法是同樣的,這裏就再也不贅述了。
雖然Converter接口、ConverterFactory接口和GenericConverter接口之間沒有任何的關係,可是Spring內部在註冊Converter實現類和ConverterFactory實現類時是先把它們轉換爲GenericConverter,以後再統一對GenericConverter進行註冊的。也就是說Spring內部會把Converter和ConverterFactory所有轉換爲GenericConverter進行註冊,在Spring註冊的容器中只存在GenericConverter這一種類型轉換器。我想之因此給用戶開放Converter接口和ConverterFactory接口是爲了讓咱們可以更方便的實現本身的類型轉換器。基於此,Spring官方也提倡咱們在進行一些簡單類型轉換器定義時更多的使用Converter接口和ConverterFactory接口,在非必要的狀況下少使用GenericConverter接口。
1.6.2ConditionalGenericConverter 接口
對於GenericConverter接口Spring還爲咱們提供了一個它的子接口,叫作ConditionalGenericConverter,在這個接口中只定義了一個方法:matches方法。咱們一塊兒來看一下ConditionalGenericConverter接口的定義:
- public interface ConditionalGenericConverter extends GenericConverter {
-
- boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
-
- }
顧名思義,從Conditional咱們就能夠看出來這個接口是用於定義有條件的類型轉換器的,也就是說不是簡單的知足類型匹配就可使用該類型轉換器進行類型轉換了,必需要知足某種條件才能使用該類型轉換器。而該類型轉換器的條件控制就是經過ConditionalGenericConverter接口的matches方法來實現的。關於ConditionalGenericConverter的使用Spring內部已經實現了不少,這裏咱們來看一個Spring已經實現了的將String以逗號分割轉換爲目標類型數組的實現:
- final class StringToArrayConverter implements ConditionalGenericConverter {
-
- private final ConversionService conversionService;
-
- public StringToArrayConverter(ConversionService conversionService) {
- this.conversionService = conversionService;
- }
-
- public Set<ConvertiblePair> getConvertibleTypes() {
- return Collections.singleton(new ConvertiblePair(String.class, Object[].class));
- }
-
- public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
- return this.conversionService.canConvert(sourceType, targetType.getElementTypeDescriptor());
- }
-
- public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
- if (source == null) {
- return null;
- }
- String string = (String) source;
- String[] fields = StringUtils.commaDelimitedListToStringArray(string);
- Object target = Array.newInstance(targetType.getElementType(), fields.length);
- for (int i = 0; i < fields.length; i++) {
- Object sourceElement = fields[i];
- Object targetElement = this.conversionService.convert(sourceElement, sourceType, targetType.getElementTypeDescriptor());
- Array.set(target, i, targetElement);
- }
- return target;
- }
-
- }
咱們能夠看到這個StringToArrayConverter就是實現了ConditionalGenericConverter接口的。根據裏面的matches方法的邏輯咱們知道當咱們要把一個字符串轉換爲一個數組的時候,只有咱們已經定義了一個字符串到這個目標數組元素對應類型的類型轉換器時纔可使用StringToArrayConverter進行類型轉換。也就是說假如咱們已經定義了一個String到User的類型轉換器,那麼當咱們須要將String轉換爲對應的User數組的時候,咱們就能夠直接使用Spring爲咱們提供的StringToArrayConverter了。