天天看點

SpringMvc類型轉換器Spring MVC類型轉換器

Spring MVC類型轉換器

類型轉換器引入

為什麼頁面上輸入”12”,可以指派給Handler方法對應的參數?

這是因為架構内部幫我們做了類型轉換的工作。将String轉換成int

 但預設類型轉換器并不是可以将使用者送出的String,轉換為使用者需要的所有類型。此時 ,就需要自定義類型轉換器了

案例:自定義日期類型轉換器

要求日期格式為:yyyy/MM/dd

SpringMvc類型轉換器Spring MVC類型轉換器

 ---單日期(在Controller定義了一個方法,主要是對參數有要求)

MyCOntroller.java

SpringMvc類型轉換器Spring MVC類型轉換器
package cn.controller;

import java.util.Date;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * 
 * @author 景佩佩
 *
 */
@Controller
public class MyController{
   //處理器方法
    @RequestMapping(value="/first.do")
    public String doFirst(Date birthday,int age) {
        System.out.println(birthday+"===========");
        System.out.println(age+"===========");
        return "/welcome.jsp";
    }
    
    
}      
SpringMvc類型轉換器Spring MVC類型轉換器

index.jsp

SpringMvc類型轉換器Spring MVC類型轉換器
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <base href="<%=basePath%>">
    <style type="text/css">
       form{
        background-color:pink;
         width:500px;
        
       }
    </style>
    <title></title>
  </head>
  
  <body>
  
    <form action="${pageContext.request.contextPath }/first.do" method="post">
    <h1>自定義類型轉換器</h1>
                 出生日期:<input name="birthday"/><br/><br/>
                 年齡:<input name="age"/><br/><br/>
       <input type="submit" value="注冊"/>
    </form>
  </body>
</html>      
SpringMvc類型轉換器Spring MVC類型轉換器

welcome.jsp(歡迎頁面)

SpringMvc類型轉換器Spring MVC類型轉換器
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <base href="<%=basePath%>">
    
    <title>歡迎頁面</title>
  
  </head>
  
  <body>
     <h1>歡迎通路${uname }${param.uage }</h1>
  </body>
</html>      
SpringMvc類型轉換器Spring MVC類型轉換器

定義自己的類型轉換器  繼承一個父接口 Converter<S, T>

SpringMvc類型轉換器Spring MVC類型轉換器
package cn.converters;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.springframework.core.convert.converter.Converter;
/**
 * 
 * @author 景佩佩
 *
 *S:Source 源類型
 *T:Target 目标類型
 */
public class MyDateConverter implements Converter<String, Date> {

    public Date convert(String source) {
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
        return null;
    }

}      
SpringMvc類型轉換器Spring MVC類型轉換器

applicationContext.xml

SpringMvc類型轉換器Spring MVC類型轉換器
SpringMvc類型轉換器Spring MVC類型轉換器

如果換成yyyy-MM-dd格式的

SpringMvc類型轉換器Spring MVC類型轉換器

在applicationContext.xml中

SpringMvc類型轉換器Spring MVC類型轉換器
SpringMvc類型轉換器Spring MVC類型轉換器

 多種日期格式

上述頁面和applicationContext.xml配置的都一樣

SpringMvc類型轉換器Spring MVC類型轉換器
SpringMvc類型轉換器Spring MVC類型轉換器

 MyController.java中也是一樣

SpringMvc類型轉換器Spring MVC類型轉換器

隻需在在類型轉換器中改一下

SpringMvc類型轉換器Spring MVC類型轉換器
package cn.converters;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.regex.Pattern;

import org.springframework.beans.TypeMismatchException;
import org.springframework.core.convert.converter.Converter;
/**
 * 
 * @author 景佩佩
 *
 *S:Source 源類型
 *T:Target 目标類型
 */
public class MyDateConverter implements Converter<String, Date> {

    public Date convert(String source) {
        //SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
        SimpleDateFormat sdf=getDateFormat(source);
        try {
            return sdf.parse(source);
        } catch (ParseException e) {
            //e.printStackTrace();
        }
        return null;
    }

    private SimpleDateFormat getDateFormat(String source){
        //一個字元串和一個特定形式能否比對,正則
        SimpleDateFormat sdf=new SimpleDateFormat();
        if (Pattern.matches("^\\d{4}-\\d{2}-\\d{2}$", source)) {
            sdf=new SimpleDateFormat("yyyy-MM-dd");
        }else if (Pattern.matches("^\\d{4}/\\d{2}/\\d{2}$", source)) {
            sdf=new SimpleDateFormat("yyyy/MM/dd");
        }else if (Pattern.matches("^\\d{4}\\d{2}\\d{2}$", source)) {
            sdf=new SimpleDateFormat("yyyyMMdd");
        }
        return sdf;
    }
    
}      
SpringMvc類型轉換器Spring MVC類型轉換器

效果:

SpringMvc類型轉換器Spring MVC類型轉換器

表單資料填錯後傳回表單頁面

當資料類型轉換發生異常後,需要傳回到表單頁面,讓使用者重寫填寫,但實際情況是發生類型轉化異常,系統會自動跳轉到400頁面。是以,若要在發生類型轉換異常後,跳轉到指定頁面,在需要将異常捕獲,然後通過異常處理器跳轉到指定頁面。

   需要注意的是:SimpleMappingExceptionResolver捕獲的是處理器方法在執行過程中發生的異常,而類型轉換異常發生在處理器方法執行之前。是以使用SimpleMappingExceptionResolver将無法捕獲到類型轉換異常。但注解式異常處理是可以擷取到類型轉換異常的。是以這裡需要使用注解式異常處理。

    當請求參數的值與接收該參數的處理器方法形參類型不比對時,會抛出類型比對有誤異常:TypeMismatchException.

當我們在前台輸入如下資訊 ,年齡為string不能裝配成背景的int類型轉向400錯誤頁面,

面對這種情況我們更想看到的是回到初始頁面

我們就采用異常處理機制,其實再出現類型轉換異常時,請求就不會再進入處理器方法,而是被我們自定的的異常處理方法所捕獲

 在處理器類中加入異常處理方法,完成重定向的功能

在MyController.java中

SpringMvc類型轉換器Spring MVC類型轉換器

關于表單内容填寫錯誤轉向表單本身原理

SpringMvc類型轉換器Spring MVC類型轉換器

資料回顯

在異常處理器中,通過request.getParameter()将使用者輸入的表單原始資料擷取到,直接放入到ModelAndView中的Model中,然後從要轉向的頁面中就可以直接通過EL表達式讀取出來,也就實作了資料回顯

其它代碼都與上述一樣我們會發現一個規律,利于我們進行特定資料異常的定位 是以下面我們使用ex.getMessage().contains(birthday)

隻需在MyController.java

我們會發現一個規律,利于我們進行特定資料異常的定位 是以下面我們使用ex.getMessage().contains(birthday)

SpringMvc類型轉換器Spring MVC類型轉換器

index.jsp

SpringMvc類型轉換器Spring MVC類型轉換器
SpringMvc類型轉換器Spring MVC類型轉換器

1.2     前言

       在以往我們需要SpringMVC為我們自動進行類型轉換的時候都是用的PropertyEditor。通過PropertyEditor的setAsText()方法我們可以實作字元串向特定類型的轉換。但是這裡有一個限制是它隻支援從String類型轉為其他類型。在Spring3中引入了一個Converter接口,它支援從一個Object轉為另一個Object。除了Converter接口之外,實作ConverterFactory接口和GenericConverter接口也可以實作我們自己的類型轉換邏輯。

1.3     Converter接口

       我們先來看一下Converter接口的定義:

Java代碼  

SpringMvc類型轉換器Spring MVC類型轉換器
  1. public interface Converter<S, T> {  
  2.     T convert(S source);  
  3. }  

       我們可以看到這個接口是使用了泛型的,第一個類型表示原類型,第二個類型表示目标類型,然後裡面定義了一個convert方法,将原類型對象作為參數傳入進行轉換之後傳回目标類型對象。當我們需要建立自己的converter的時候就可以實作該接口。下面假設有這樣一個需求,有一個文章實體,在文章中是可以有附件的,而附件我們需要記錄它的請求位址、大小和檔案名,是以這個時候文章應該是包含一個附件清單的。在實作的時候我們的附件是實時上傳的,上傳後由服務端傳回對應的附件請求位址、大小和檔案名,附件資訊不直接存放在資料庫中,而是作為文章的屬性一起存放在Mongodb中。用戶端擷取到這些資訊以後做一個簡單的展示,然後把它們封裝成特定格式的字元串作為隐藏域跟随文章一起送出到服務端。在服務端我們就需要把這些字元串附件資訊轉換為對應的List<Attachment>。是以這個時候我們就建立一個String[]到List<Attachment>的Converter。代碼如下:

Java代碼  

SpringMvc類型轉換器Spring MVC類型轉換器
  1. import java.util.ArrayList;  
  2. import java.util.List;  
  3. import org.springframework.core.convert.converter.Converter;  
  4. import com.tiantian.blog.model.Attachment;  
  5. public class StringArrayToAttachmentList implements Converter<String[], List<Attachment>> {  
  6.     @Override  
  7.     public List<Attachment> convert(String[] source) {  
  8.        if (source == null)  
  9.            return null;  
  10.        List<Attachment> attachs = new ArrayList<Attachment>(source.length);  
  11.        Attachment attach = null;  
  12.        for (String attachStr : source) {  
  13.            //這裡假設我們的Attachment是以“name,requestUrl,size”的形式拼接的。  
  14.            String[] attachInfos = attachStr.split(",");  
  15.            if (attachInfos.length != 3)//當按逗号分隔的數組長度不為3時就抛一個異常,說明非法操作了。  
  16.               throw new RuntimeException();  
  17.            String name = attachInfos[0];  
  18.            String requestUrl = attachInfos[1];  
  19.            int size;  
  20.            try {  
  21.               size = Integer.parseInt(attachInfos[2]);  
  22.            } catch (NumberFormatException e) {  
  23.               throw new RuntimeException();//這裡也要抛一個異常。  
  24.            }  
  25.            attach = new Attachment(name, requestUrl, size);  
  26.            attachs.add(attach);  
  27.        }  
  28.        return attachs;  
  29.     }  
  30. }  

1.4     ConversionService接口

       在定義好Converter之後,就是使用Converter了。為了統一調用Converter進行類型轉換,Spring為我們提供了一個ConversionService接口。通過實作這個接口我們可以實作自己的Converter調用邏輯。我們先來看一下ConversionService接口的定義:

Java代碼  

SpringMvc類型轉換器Spring MVC類型轉換器
  1. public interface ConversionService {  
  2.     boolean canConvert(Class<?> sourceType, Class<?> targetType);  
  3.     <T> T convert(Object source, Class<T> targetType);  
  4.     boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);  
  5.     Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);  
  6. }  

       我們可以看到ConversionService接口裡面定義了兩個canConvert方法和兩個convert方法,canConvert方法用于判斷目前的ConversionService是否能夠對原類型和目标類型進行轉換,convert方法則是用于進行類型轉換的。上面出現的參數類型TypeDescriptor是對于一種類型的封裝,裡面包含該種類型的值、實際類型等等資訊。

在定義了ConversionService之後我們就可以把它定義為一個bean對象,然後指定<mvn:annotation-driven/>的conversion-service屬性為我們自己定義的ConversionService bean對象。如:

Xml代碼  

SpringMvc類型轉換器Spring MVC類型轉換器
  1. <mvc:annotation-driven conversion-service="myConversionService"/>  
  2. <bean id="myConversionService" class="com.tiantian.blog.web.converter.support.MyConversionService"/>  

       這樣當SpringMVC需要進行類型轉換的時候就會調用ConversionService的canConvert和convert方法進行類型轉換。

       一般而言我們在實作ConversionService接口的時候也會實作ConverterRegistry接口。使用ConverterRegistry可以使我們對類型轉換器做一個統一的注冊。ConverterRegistry接口的定義如下:

Java代碼  

SpringMvc類型轉換器Spring MVC類型轉換器
  1. public interface ConverterRegistry {  
  2.     void addConverter(Converter<?, ?> converter);  
  3.     void addConverter(GenericConverter converter);  
  4.     void addConverterFactory(ConverterFactory<?, ?> converterFactory);  
  5.     void removeConvertible(Class<?> sourceType, Class<?> targetType);  
  6. }  

       正如前言所說的,要實作自己的類型轉換邏輯我們可以實作Converter接口、ConverterFactory接口和GenericConverter接口,ConverterRegistry接口就分别為這三種類型提供了對應的注冊方法,至于裡面的邏輯就可以發揮自己的設計能力進行設計實作了。

       對于ConversionService,Spring已經為我們提供了一個實作,它就是GenericConversionService,位于org.springframework.core.convert.support包下面,它實作了ConversionService接口和ConverterRegistry接口。但是不能直接把它作為SpringMVC的ConversionService,因為直接使用時不能往裡面注冊類型轉換器。也就是說不能像下面這樣使用:

Xml代碼  

SpringMvc類型轉換器Spring MVC類型轉換器
  1. <mvc:annotation-driven conversion-service="conversionService"/>  
  2. <bean id="conversionService" class="org.springframework.core.convert.support.GenericConversionService"/>  

       為此我們必須對GenericConversionService做一些封裝,比如說我們可以在自己的ConversionService裡面注入一個GenericConversionService,然後通過自己的ConversionService的屬性接收Converter并把它們注入到GenericConversionService中,之後所有關于ConversionService的方法邏輯都可以調用GenericConversionService對應的邏輯。按照這種思想我們的ConversionService大概是這樣的:

Java代碼  

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

       在上面代碼中,通過converters屬性我們可以接收需要注冊的Converter、ConverterFactory和GenericConverter,在converters屬性設定完成之後afterPropertiesSet方法會被調用,在這個方法裡面我們把接收到的converters都注冊到注入的GenericConversionService中了,之後關于ConversionService的其他操作都是通過這個GenericConversionService來完成的。這個時候我們的SpringMVC檔案可以這樣配置:

Xml代碼  

SpringMvc類型轉換器Spring MVC類型轉換器
  1. <mvc:annotation-driven conversion-service="conversionService"/>  
  2. <bean id="genericConversionService" class="org.springframework.core.convert.support.GenericConversionService"/>  
  3. <bean id="conversionService" class="com.tiantian.blog.web.converter.support.MyConversionService">  
  4.    <property name="converters">  
  5.        <set>  
  6.           <bean class="com.tiantian.blog.web.converter.StringArrayToAttachmentList"/>  
  7.        </set>  
  8.    </property>  
  9. </bean>  

       除了以上這種使用GenericConversionService的思想之外,Spring已經為我們提供了一個既可以使用GenericConversionService,又可以注入Converter的類,那就是ConversionServiceFactoryBean。該類為我們提供了一個可以接收Converter的converters屬性,在它的内部有一個GenericConversionService對象的引用,在對象初始化完成之後它會new一個GenericConversionService對象,并往GenericConversionService中注冊converters屬性指定的Converter和Spring自身已經實作了的預設Converter,之後每次傳回的都是這個GenericConversionService對象。當使用ConversionServiceFactoryBean的時候我們的SpringMVC檔案可以這樣配置:

Xml代碼  

SpringMvc類型轉換器Spring MVC類型轉換器
  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還為我們提供了一個FormattingConversionServiceFactoryBean。當使用FormattingConversionServiceFactoryBean的時候我們的SpringMVC配置檔案的定義應該是這樣:

Xml代碼  

SpringMvc類型轉換器Spring MVC類型轉換器
  1.     <mvc:annotation-driven conversion-service="conversionService"/>  
  2.     <bean id="conversionService"  
  3.           class="org.springframework.format.support.FormattingConversionServiceFactoryBean">  
  4.           <property name="converters">  
  5.              <set>  
  6.                  <bean class="com.tiantian.blog.web.converter.StringArrayToAttachmentList"/>  
  7.              </set>  
  8.           </property>  
  9. </bean>  

       以上介紹的是SpringMVC自動進行類型轉換時需要我們做的操作。如果我們需要在程式裡面手動的來進行類型轉換的話,我們也可以往我們的程式裡面注入一個ConversionService,然後通過ConversionService來進行相應的類型轉換操作,也可以把Converter直接注入到我們的程式中。

1.5     ConverterFactory接口

       ConverterFactory的出現可以讓我們統一管理一些相關聯的Converter。顧名思義,ConverterFactory就是産生Converter的一個工廠,确實ConverterFactory就是用來産生Converter的。我們先來看一下ConverterFactory接口的定義:

Java代碼  

SpringMvc類型轉換器Spring MVC類型轉換器
  1. public interface ConverterFactory<S, R> {  
  2.     <T extends R> Converter<S, T> getConverter(Class<T> targetType);  
  3. }  

       我們可以看到ConverterFactory接口裡面就定義了一個産生Converter的getConverter方法,參數是目标類型的class。我們可以看到ConverterFactory中一共用到了三個泛型,S、R、T,其中S表示原類型,R表示目标類型,T是類型R的一個子類。

考慮這樣一種情況,我們有一個表示使用者狀态的枚舉類型UserStatus,如果要定義一個從String轉為UserStatus的Converter,根據之前Converter接口的說明,我們的StringToUserStatus大概是這個樣子:

Java代碼  

SpringMvc類型轉換器Spring MVC類型轉換器
  1. public class StringToUserStatus implements Converter<String, UserStatus> {  
  2.    @Override  
  3.    public UserStatus convert(String source) {  
  4.        if (source == null) {  
  5.           return null;  
  6.        }  
  7.        return UserStatus.valueOf(source);  
  8.    }  
  9. }  

       如果這個時候有另外一個枚舉類型UserType,那麼我們就需要定義另外一個從String轉為UserType的Converter——StringToUserType,那麼我們的StringToUserType大概是這個樣子:

Java代碼  

SpringMvc類型轉換器Spring MVC類型轉換器
  1. public class StringToUserType implements Converter<String, UserType> {  
  2.    @Override  
  3.    public UserType convert(String source) {  
  4.        if (source == null) {  
  5.           return null;  
  6.        }  
  7.        return UserType.valueOf(source);  
  8.    }  
  9. }  

       如果還有其他枚舉類型需要定義原類型為String的Converter的時候,我們還得像上面那樣定義對應的Converter。有了ConverterFactory之後,這一切都變得非常簡單,因為UserStatus、UserType等其他枚舉類型同屬于枚舉,是以這個時候我們就可以統一定義一個從String到Enum的ConverterFactory,然後從中擷取對應的Converter進行convert操作。Spring官方已經為我們實作了這麼一個StringToEnumConverterFactory:

Java代碼  

SpringMvc類型轉換器Spring MVC類型轉換器
  1. @SuppressWarnings("unchecked")  
  2. final class StringToEnumConverterFactory implements ConverterFactory<String, Enum> {  
  3.     public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {  
  4.        return new StringToEnum(targetType);  
  5.     }  
  6.     private class StringToEnum<T extends Enum> implements Converter<String, T> {  
  7.        private final Class<T> enumType;  
  8.        public StringToEnum(Class<T> enumType) {  
  9.            this.enumType = enumType;  
  10.        }  
  11.        public T convert(String source) {  
  12.            if (source.length() == 0) {  
  13.               // It's an empty enum identifier: reset the enum value to null.  
  14.               return null;  
  15.            }  
  16.            return (T) Enum.valueOf(this.enumType, source.trim());  
  17.        }  
  18.     }  
  19. }  

       這樣,如果是要進行String到UserStatus的轉換,我們就可以通過StringToEnumConverterFactory執行個體的getConverter(UserStatus.class).convert(string)擷取到對應的UserStatus,如果是要轉換為UserType的話就是getConverter(UserType.class).convert(string)。這樣就非常友善,可以很好的支援擴充。

       對于ConverterFactory我們也可以把它當做ConvertionServiceFactoryBean的converters屬性進行注冊,在ConvertionServiceFactoryBean内部進行Converter注入的時候會根據converters屬性具體元素的具體類型進行不同的注冊,對于FormattingConversionServiceFactoryBean也是同樣的方式進行注冊。是以如果我們自己定義了一個StringToEnumConverterFactory,我們可以這樣來進行注冊:

Xml代碼  

SpringMvc類型轉換器Spring MVC類型轉換器
  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接口中最靈活也是最複雜的一個類型轉換接口。像我們之前介紹的Converter接口隻支援從一個原類型轉換為一個目标類型;ConverterFactory接口隻支援從一個原類型轉換為一個目标類型對應的子類型;而GenericConverter接口支援在多個不同的原類型和目标類型之間進行轉換,這也就是GenericConverter接口靈活和複雜的地方。

       我們先來看一下GenericConverter接口的定義:

Java代碼  

SpringMvc類型轉換器Spring MVC類型轉換器
  1. public interface GenericConverter {  
  2.     Set<ConvertiblePair> getConvertibleTypes();  
  3.     Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);  
  4.     public static final class ConvertiblePair {  
  5.        private final Class<?> sourceType;  
  6.        private final Class<?> targetType;  
  7.        public ConvertiblePair(Class<?> sourceType, Class<?> targetType) {  
  8.            Assert.notNull(sourceType, "Source type must not be null");  
  9.            Assert.notNull(targetType, "Target type must not be null");  
  10.            this.sourceType = sourceType;  
  11.            this.targetType = targetType;  
  12.        }  
  13.        public Class<?> getSourceType() {  
  14.            return this.sourceType;  
  15.        }  
  16.        public Class<?> getTargetType() {  
  17.            return this.targetType;  
  18.        }  
  19.     }  
  20. }  

      我們可以看到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可以這樣來寫:

Java代碼  

SpringMvc類型轉換器Spring MVC類型轉換器
  1. public class UserGenericConverter implements GenericConverter {  
  2.     @Autowired  
  3.     private UserService userService;  
  4.     @Override  
  5.     public Object convert(Object source, TypeDescriptor sourceType,  
  6.            TypeDescriptor targetType) {  
  7.        if (source == null || sourceType == TypeDescriptor.NULL || targetType == TypeDescriptor.NULL) {  
  8.            return null;  
  9.        }  
  10.        User user = null;  
  11.        if (sourceType.getType() == Integer.class) {  
  12.            user = userService.findById((Integer) source);//根據id來查找user  
  13.        } else if (sourceType.getType() == String.class) {  
  14.            user = userService.find((String)source);//根據使用者名來查找user  
  15.        }  
  16.        return user;  
  17.     }  
  18.     @Override  
  19.     public Set<ConvertiblePair> getConvertibleTypes() {  
  20.        Set<ConvertiblePair> pairs = new HashSet<ConvertiblePair>();  
  21.        pairs.add(new ConvertiblePair(Integer.class, User.class));  
  22.        pairs.add(new ConvertiblePair(String.class, User.class));  
  23.        return pairs;  
  24.     }  
  25. }  

       我們可以看到在上面定義的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接口的定義:

Java代碼  

SpringMvc類型轉換器Spring MVC類型轉換器
  1. public interface ConditionalGenericConverter extends GenericConverter {  
  2.     boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);  
  3. }  

       顧名思義,從Conditional我們就可以看出來這個接口是用于定義有條件的類型轉換器的,也就是說不是簡單的滿足類型比對就可以使用該類型轉換器進行類型轉換了,必須要滿足某種條件才能使用該類型轉換器。而該類型轉換器的條件控制就是通過ConditionalGenericConverter接口的matches方法來實作的。關于ConditionalGenericConverter的使用Spring内部已經實作了很多,這裡我們來看一個Spring已經實作了的将String以逗号分割轉換為目标類型數組的實作:

Java代碼  

SpringMvc類型轉換器Spring MVC類型轉換器
  1. final class StringToArrayConverter implements ConditionalGenericConverter {  
  2.     private final ConversionService conversionService;  
  3.     public StringToArrayConverter(ConversionService conversionService) {  
  4.        this.conversionService = conversionService;  
  5.     }  
  6.     public Set<ConvertiblePair> getConvertibleTypes() {  
  7.        return Collections.singleton(new ConvertiblePair(String.class, Object[].class));  
  8.     }  
  9.     public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {  
  10.        return this.conversionService.canConvert(sourceType, targetType.getElementTypeDescriptor());  
  11.     }  
  12.     public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {  
  13.        if (source == null) {  
  14.            return null;  
  15.        }       
  16.        String string = (String) source;  
  17.        String[] fields = StringUtils.commaDelimitedListToStringArray(string);  
  18.        Object target = Array.newInstance(targetType.getElementType(), fields.length);  
  19.        for (int i = 0; i < fields.length; i++) {  
  20.            Object sourceElement = fields[i];  
  21.            Object targetElement = this.conversionService.convert(sourceElement, sourceType, targetType.getElementTypeDescriptor());  
  22.            Array.set(target, i, targetElement);  
  23.        }  
  24.        return target;  
  25.     }  
  26. }  

       我們可以看到這個StringToArrayConverter就是實作了ConditionalGenericConverter接口的。根據裡面的matches方法的邏輯我們知道當我們要把一個字元串轉換為一個數組的時候,隻有我們已經定義了一個字元串到這個目标數組元素對應類型的類型轉換器時才可以使用StringToArrayConverter進行類型轉換。也就是說假如我們已經定義了一個String到User的類型轉換器,那麼當我們需要将String轉換為對應的User數組的時候,我們就可以直接使用Spring為我們提供的StringToArrayConverter了。

繼續閱讀