Spring Boot中如何使用Convert接口實現類型轉換器

使用Convert接口實現類型轉換器

在Spring3中引入瞭一個Converter接口,它支持從一個Object轉為另一個Object。除瞭Converter接口之外,實現ConverterFactory接口和GenericConverter接口也可以實現我們自己的類型轉換邏輯。

Converter接口

首先看看Converter接口的定義

public interface Converter<S, T> {       
    T convert(S source);     
} 

可以看到這個接口是使用瞭泛型的,S表示原類型,T表示目標類型,然後裡面定義瞭一個convert方法,將原類型對象作為參數傳入進行轉換之後返回目標類型對象。

下面在Spring Boot中使用Converter接口來實現將String類型分別轉換為Data,自定義對象和List<自定義對象>。

添加依賴

添加spring-boot-starter-web依賴

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

實體類

1.User類

public class User {
  private long  id;
  //用戶名
  private String name;
  //出生日期
  private Date birth;
  //關聯用戶
  private User linkUser;
  //喜歡的文章
  private List<Article> favArticles=new ArrayList<>();
 //下面省略Getter和Setter方法

2.Article類

public class Article {
  //文章id
  private long artId;
  //文章名
  private String artName;
  
 //下面省略Getter和Setter方法
}

配置類型轉化器

下面三個類都需要添加@Component註解,否則不能生效。並實現Spring提供的org.springframework.core.convert.converter.Converter接口,重寫其中的convert()方法 ,方法中寫自己的轉換邏輯。

1.定義全局日期轉換器

@Component
public class DateConvert implements Converter<String,Date> {
  //日期格式
 SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
  @Override
  public Date convert(String s) {
    if (s!=null&&!"".equals(s)){
      try {
        //解析參數
        Date date=sdf.parse(s);
        return date;
      } catch (ParseException e) {
        e.printStackTrace();
      }
    }
    return null;
  }
}

2.定義全局對象轉換器

這裡使用Jackson的ObjectMapper類的readValue()函數實現將Json字符串反序列化為Java對象

@Component
public class ObjectConvert implements Converter<String,User> {
  @Override
  public User convert(String s) {
    ObjectMapper objectMapper=new ObjectMapper();
    if (s!=null&&!"".equals(s)){
      try {
        User user=objectMapper.readValue(s,User.class);
        return user;
      } catch (JsonProcessingException e) {
        e.printStackTrace();
      }
    }
    return null;
  }
}

3.定義全局List類型轉換器

這裡使用Jackson的ObjectMapper類的readValue()函數實現將Json字符串反序列化為 List

@Component
public class StringToListController implements Converter<String, List<Article>> {
 ObjectMapper objectMapper=new ObjectMapper();
  @Override
  public List<Article> convert(String s) {
    List<Article> list=null;
    try {
       list=objectMapper.readValue(s, new TypeReference<List<Article>>() {
      });
    } catch (JsonProcessingException e) {
      e.printStackTrace();
    }
    return list;
  }
}

控制器

這裡註意使用produces設置返回數據的類型為json,consumes設置提交內容的類型為:application/x-www-form-urlencoded。

application/x-www-form-urlencoded作用:將鍵值對的參數用&連接起來,如果有空格,將空格轉換為+加號;有特殊符號,將特殊符號轉換為ASCII HEX值。

@RestController
public class HelloController {
  @GetMapping("hello")
  public Date getDate(Date birth){
    System.out.println(birth);
    return birth;
  }
  @PostMapping(value="showUser",produces="application/json",
          consumes = "application/x-www-form-urlencoded")
  public User showUser(User user){
    return user;
  }
}

測試

在Postman中進行測試,註意以下設置:POST請求 -> Body -> x-www-form-urlencoded。在Body中輸入參數進行測試。

因為參數中有Json類型參數,如果直接使用Params下進行發送數據,會出現請求參數異常錯誤。

在這裡插入圖片描述

測試結果:

在這裡插入圖片描述

Converter使用及其原理

在Spring MVC開發中,我們可以很方便的使用Converter來實現對請求參數的處理,比如字符串去空,日期格式化等。

配置文件中對Converter的引用

<!-- 屬性編輯器 -->
    <bean id="conversionService"
        class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
        <property name="converters">
            <list>
                <bean class="com.xxx.common.converter.StringTrimConverter" />
                <bean class="com.xxx.common.converter.DateConverter" />
                <bean class="com.xxx.common.converter.DatetimeConverter" />
            </list>
        </property>
    </bean>
<mvc:annotation-driven conversion-service="conversionService">

如上代碼,我們配置瞭三種類型的Converter。

以字符串去空為例

import org.springframework.core.convert.converter.Converter;
/**
 * 去除前後空格
 * @author 
 *
 */
public class StringTrimConverter implements Converter<String, String> {
    public String convert(String source) {
        //如果源字符串不為空則進行轉換
        if(source != null){
            //去除源字符串前後空格
            source = source.trim();
            if(source.equals("")){ 
                source = null;
            }
        }
        return source;
    }
}

配置好以上內容,即可在我們的請求中實現字符串自動去空格。

明白使用其實很簡單,我們可以看下在Spring的底層,具體是如何實現Converter的。我們以字符串去空的代碼為例。

以上代碼,首先實現瞭Converter接口

我們查看Converter接口的源碼

/**
 * A converter converts a source object of type S to a target of type T.
 * Implementations of this interface are thread-safe and can be shared.
 *
 * <p>Implementations may additionally implement {@link ConditionalConverter}.
 *
 * @author Keith Donald
 * @since 3.0
 * @see ConditionalConverter
 * @param <S> The source type
 * @param <T> The target type
 */
public interface Converter<S, T> {
    /**
     * Convert the source of type S to target type T.
     * @param source the source object to convert, which must be an instance of S
     * @return the converted object, which must be an instance of T
     * @throws IllegalArgumentException if the source could not be converted to the desired target type
     */
    T convert(S source);
}

通過JavaDoc我們可以看到,實現該接口,可以使我們將S類型的對象轉換為T類型。那麼對應的我們對於Date類型的轉換,就可寫為如下代碼:

public class DateConverter implements Converter
<bean id="conversionService"
        class="org.springframework.format.support.FormattingConversionServiceFactoryBean">

該類的對象,繼續查看對應改類的源碼,以及對應的JavaDoc。我們可以在該類的Doc中看到如下描述:

* <p>Like all {@code FactoryBean} implementations, this class is suitable for
 * use when configuring a Spring application context using Spring {@code <beans>}
 * XML. When configuring the container with
 * {@link org.springframework.context.annotation.Configuration @Configuration}
 * classes, simply instantiate, configure and return the appropriate
 * {@code FormattingConversionService} object from a
 * {@link org.springframework.context.annotation.Bean @Bean} method.

該類適用於適用XML構建Spring應用。

我們查看對應的成員變量:

public class FormattingConversionServiceFactoryBean
        implements FactoryBean<FormattingConversionService>, EmbeddedValueResolverAware, InitializingBean {
    private Set<?> converters;
    private Set<?> formatters;
    private Set<FormatterRegistrar> formatterRegistrars;
    private boolean registerDefaultFormatters = true;
    private StringValueResolver embeddedValueResolver;
    private FormattingConversionService conversionService;

在配置XML時,我們主要配置瞭集合類的converters,該類比較重要的方法如下:

@Override
    public void afterPropertiesSet() {
        this.conversionService = new DefaultFormattingConversionService(this.embeddedValueResolver, this.registerDefaultFormatters);
        ConversionServiceFactory.registerConverters(this.converters, this.conversionService);
        registerFormatters();
    }

該方法實現瞭對conversionService中增減我們對應的格式化器。

在Spring啟動時,註冊轉換器 時會進入afterPropertiesSet 方法。在該方法中,我們可以看到Spring以HashSet來存儲對應的converters。在ConversionServiceFactory中,判斷不同的轉換器,並進行註冊。

public static void registerConverters(Set<?> converters, ConverterRegistry registry) {
        if (converters != null) {
            for (Object converter : converters) {
                if (converter instanceof GenericConverter) {
                    registry.addConverter((GenericConverter) converter);
                }
                else if (converter instanceof Converter<?, ?>) {
                    registry.addConverter((Converter<?, ?>) converter);
                }
                else if (converter instanceof ConverterFactory<?, ?>) {
                    registry.addConverterFactory((ConverterFactory<?, ?>) converter);
                }
                else {
                    throw new IllegalArgumentException("Each converter object must implement one of the " +
                            "Converter, ConverterFactory, or GenericConverter interfaces");
                }
            }
        }
    }

以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。

推薦閱讀: