略談型態轉換器


從 Spring 3.x 之後,提供了 org.springframework.core.convert 定義了通用的型態轉換介面等機制,在 Spring MVC 中,可做為 PropertyEditor 的替代機制。

型態轉換的邏輯,主要可實作 Converter 介面來達成:

package org.springframework.core.convert.converter;

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

S 是來源型態,T 是目標型態,例如,若想將 String 轉換為 LocalDateTime

package cc.openhome.aspect;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;

import org.springframework.core.convert.converter.Converter;

public class StringToLocalDateTimeConvertor implements Converter<String, LocalDateTime> {
    @Override
    public LocalDateTime convert(String millis) {
        System.out.println("converter");
        return Instant.ofEpochMilli(Long.parseLong(millis))
                      .atZone(ZoneId.of("Asia/Taipei"))
                      .toLocalDateTime();
    }
}

WebDataBinder 內部會使用一些 Converter 來進行型態轉換,例如,若請求參數值實際上代表某個整數值,在處理器的參數為 Long 的話,就會自動將之剖析為 Long(透過 StringToNumberConverterFactory);如果要加入自定義的 Converter 實例,可以在 WebConfig 中定義:

... 略
public class WebConfig implements WebMvcConfigurer, ApplicationContextAware {
    ...

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new StringToLocalDateTimeConvertor());
    }
}

這麼一來,若有個控制器中有處理器被標示為:

@PostMapping("do_foo")
protected String doFoo(@RequestParam("millis") LocalDateTime localDateTime) {
    /// ...做點事
}

就會使用以上定義的 LocalDateTimeConvertor 將字串轉換為 LocalDateTime 實例。

如上所述,Spring 內建了一些轉換器,例如,Spring 可使用請求參數值中的 , 來切割請求參數值,視你處理器上的參數而定,若為 String[] millis,就會使用 StringToArrayConvertermillis=1234,5678 轉換為陣列,若為 List<String> millis,就會使用 StringToCollectionConverter 轉換。

PropertyEditor 仍然適用於簡單的場合,然而,使用 Converter 的好處在於可以彼此銜接,如果你定義了上面的 StringToLocalDateTimeConvertor,而處理器參數為 List<LocalDateTime> millismillis=1234,5678 這樣的請求參數,就會被轉換為 List<LocalDateTime> 實例。

如果請求參數已經被 PropertyEditor 編輯過了,那麼就不會套用 Converter 的機制。