Spring 類型轉(zhuǎn)換

Spring 類型轉(zhuǎn)換的實(shí)現(xiàn)

spring 有新老兩套實(shí)現(xiàn)

  1. 基于 JavaBeans 接口的類型轉(zhuǎn)換實(shí)現(xiàn),java.beans.PropertyEditor接口擴(kuò)展
  2. spring 3.0 通用類型轉(zhuǎn)換,

使用場(chǎng)景

場(chǎng)景 基于JavaBeans Spring 3.0+
數(shù)據(jù)綁定 DataBinder y y實(shí)現(xiàn) PropertyEditorRegistry
BeanWrapper y y實(shí)現(xiàn)ConfigurablePropertyAccessor#setConversionService
Bean 屬性轉(zhuǎn)換 y y
外部化屬性類型轉(zhuǎn)換 n(springboot會(huì)用到) y

DataBinder

DataBinder

DataBinder實(shí)現(xiàn)了 PropertyEditorRegistry 和 TypeConverter 接口,分別支持兩種實(shí)現(xiàn)庄拇。

BeanWrapper

BeanWrapper 通常不會(huì)直接使用揪漩,一般是框架通過 DataBinder 或者 BeanFactory 使用。

ps: 在 BeanFactory 中 bean 創(chuàng)建的過程會(huì)通過 BeanWrapper 創(chuàng)建對(duì)象未舟,可以查看 #doCreateBea -> #populateBean ->

// 通過 PropertyValues 設(shè)置屬性
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
...
  if (pvs != null) {
    applyPropertyValues(beanName, mbd, bw, pvs);
  }
...
}

protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) {
...
  try {
    bw.setPropertyValues(mpvs);
    return;
  }
...
}

ps: DataBinder 也是通過 apply 的方式把 propertyValues 設(shè)置到bean上文狱。

protected void doBind(MutablePropertyValues mpvs) {
    checkAllowedFields(mpvs);
    checkRequiredFields(mpvs);
    applyPropertyValues(mpvs);
}
protected void applyPropertyValues(MutablePropertyValues mpvs) {
    try {
        // Bind request parameters onto target object.
        getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
    }
    catch (PropertyBatchUpdateException ex) {
        // Use bind error processor to create FieldErrors.
        for (PropertyAccessException pae : ex.getPropertyAccessExceptions()) {
            getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult());
        }
    }
}
///
BeanWrapper

BeanWrapper 的父接口 ConfigurablePropertyAccessor 在3.0的時(shí)候增加了 setConversionService 方法設(shè)置服務(wù)類粥鞋,并且通過方法setExtractOldValueForEditor設(shè)置在設(shè)置 property editor 時(shí)是否抽取舊值。


ConfigurablePropertyAccessor

父類 PropertyEditorRegistrySupport 保存 ConversionService 變量瞄崇,并且實(shí)現(xiàn)了 PropertyEditorRegistry 接口呻粹,說明支持新舊兩種實(shí)現(xiàn),是功能的一種整合苏研。


PropertyEditorRegistrySupport

基于 Javabeans 接口的類型轉(zhuǎn)換

核心職責(zé):將String 類型的內(nèi)容轉(zhuǎn)化為目標(biāo)類型的對(duì)象
擴(kuò)展原理:

  • spirng 框架將文本內(nèi)容傳遞到 PropertyEditor 實(shí)現(xiàn)的 setAsText 方法
  • PropertyEditor#setAsText方法實(shí)現(xiàn)將 String 轉(zhuǎn)化為目標(biāo)類型對(duì)象
  • 將目標(biāo)類型的對(duì)象傳入PropertyEditor#setValue(Object)方法
  • PropertyEditor#setValue(Object)方法實(shí)現(xiàn)需要臨時(shí)存儲(chǔ)傳入對(duì)象尚猿,有臨時(shí)存儲(chǔ)中間轉(zhuǎn)換對(duì)象的能力
  • Spring 框架通過PropertyEditor#getValue 獲取類型轉(zhuǎn)換后的對(duì)象

ps : demo 代碼:conversion.String2PropertiesEditor


public class String2PropertiesEditor extends PropertyEditorSupport {

  /**
   * 原始值
   */
  String text;
  @Override
  public void setAsText(String text) throws IllegalArgumentException {
    this.text = text;
    Properties properties = new Properties();

    try {
      properties.load(new StringReader(text));
    } catch (IOException e) {
      throw new IllegalArgumentException(e);
    }

    setValue(properties);
  }

  @Override
  public String getAsText() {
    return this.text;
  }
}


public class PropertyEditorDemo {

  public static void main(String[] args) {
    String text = "name=why";

    PropertyEditor propertyEditor = new String2PropertiesEditor();
    propertyEditor.setAsText(text);
    // 獲取轉(zhuǎn)換后的 property 對(duì)象
    System.out.println("propertyEditor.getValue() = " + propertyEditor.getValue());
    // 獲取原始 text
    System.out.println("propertyEditor.getAsText() = " + propertyEditor.getAsText());

  }

}

自定義 PropertyEditor 擴(kuò)展,添加到 spring 框架中

Spring 內(nèi)建 PropertyEditor 內(nèi)建擴(kuò)展都在 org.springframework.beans.propertyeditors 包下楣富,下面看下如何自定義。

  • 擴(kuò)展模式:擴(kuò)展 PropertyEditorSupport
  • 實(shí)現(xiàn) org.springframework.beans.PropertyEditorRegistrar
    • 實(shí)現(xiàn) registerCustomEditors
    • 將 PropertyEditorRegistor 實(shí)現(xiàn) 注冊(cè)為 Spring bean
  • 向 PropertyEditorRegistry 注冊(cè)自定義 PropertyEditor 實(shí)現(xiàn)
    • 通用類型實(shí)現(xiàn) registerCustomEditor(class,PropertyEditor)
    • Java Bean 屬性類型實(shí)現(xiàn):熱狗is投入CustomEditor(class伴榔,PropertyEditor)
public class DIYPropertyEditorRegistrar implements PropertyEditorRegistrar {

  @Override
  public void registerCustomEditors(PropertyEditorRegistry registry) {
    // 1 類型轉(zhuǎn)換器
    String2PropertiesEditor propertyEditor = new String2PropertiesEditor();
    // 2 注冊(cè)屬性轉(zhuǎn)換
    registry.registerCustomEditor(User.class, "context", propertyEditor);
    // 3 DIYPropertyEditorRegistrar 定義為 spring bean 對(duì)象
    //
  }
}

xml 配置如下:


  <bean class="conversion.DIYPropertyEditorRegistrar"/>
  <bean id="user" class="pojo.User">
    <property name="id" value="1"/>
    <property name="context">
      <value>
        id=1
        name=why
      </value>
    </property>
  </bean>

demo 程序

  public static void main(String[] args) {
    ConfigurableApplicationContext context = new ClassPathXmlApplicationContext(
        "META-INF/conversion/property-editor.xml");
    User user = context.getBean("user", User.class);
    // context 屬性會(huì)注入
    System.out.println(user);
    context.close();
  }

Spring PropertyEditor的設(shè)計(jì)缺陷

  • 違反單一職責(zé)原則
    PropertyEditor 接口的職責(zé)太多纹蝴,除了類型轉(zhuǎn)換,還有事件和GUI程序的交互邏輯
  • PropertyEditor 實(shí)現(xiàn)類型局限
    源類型只能為 String 類型
  • 缺少類型強(qiáng)類型轉(zhuǎn)換
    除了實(shí)現(xiàn)類的名稱可以表達(dá)語義(如CharsetEditor)踪少,實(shí)現(xiàn)類無法感知目標(biāo)轉(zhuǎn)換類型塘安。setValue(Object value)可以設(shè)置任意類型的對(duì)象,同樣public Object getValue() 返回的是 Object 類型援奢,也不知道具體是什么類型兼犯。

Spring 3 通用類型轉(zhuǎn)換接口

  • 增加類型轉(zhuǎn)換接口 Converter<S,T>,實(shí)現(xiàn)要求線程安全。
    • 核心方法 T convert(S s);
    • 局限性
      • 缺少前置判斷切黔。如果判斷convert方法的入?yún)⑹欠裰С诸愋娃D(zhuǎn)換砸脊,不支持的時(shí)候返回null,也可以實(shí)現(xiàn)類似的功能纬霞。但是接口的職責(zé)就不單一了凌埂,而且返回值有二義性。小比較來說由 ConditionalConverter 實(shí)現(xiàn)職責(zé)更合理诗芜。
      • 僅支持一對(duì)一轉(zhuǎn)換瞳抓。由 GenericConverter 代替,復(fù)合類型的實(shí)現(xiàn)伏恐。

Converter 相較 PropertyEditor 增加了原和目標(biāo)的泛型孩哑,支持更廣泛的類型轉(zhuǎn)換。因?yàn)槭欠盒痛滂耄瑫?huì)有泛型擦寫的問題横蜒,在運(yùn)行時(shí)我們并不一定明確的知道具體的類型,所以有了 GenericConverter 秤掌。

  • GenericConverter

    • 核心方法 convert(Object, TypeDescriptor,TypeDescriptor)
    • 配對(duì)類型 GenericConverter.ConvertiblePair愁铺,保存原和目標(biāo)的一對(duì)值。支持多個(gè)鍵值對(duì)闻鉴。
    • TypeDescriptor茵乱,類型的描述,不只是class孟岛,還有原生類型瓶竭,也可能是泛型,比較復(fù)雜渠羞。
  • ConditionalConverter斤贰。可以讓Converter, GenericConverter次询,ConverterFactory 根據(jù)條件執(zhí)行荧恍。boolean matches(TypeDescriptor sourceType, TypeDe scriptor targetType) 方法參數(shù)使用的是 TypeDescriptor 更加抽象,可以更好的描述類型屯吊。

spring 內(nèi)建類型轉(zhuǎn)換器

轉(zhuǎn)換場(chǎng)景 實(shí)現(xiàn)類
時(shí)間/日期 format.datetime
joda 時(shí)間/日期 format.datetime.joda
java8 時(shí)間/日期 format.datetime.standard
通用實(shí)現(xiàn) core.convert.support

可以看下每個(gè)包里的實(shí)現(xiàn)類送巡。如:StringToArrayConverter。

GenericConverter 接口

核心要素 說明
使用場(chǎng)景 可用于復(fù)合類型轉(zhuǎn)換場(chǎng)景盒卸,如 Collection骗爆,Map,數(shù)組等
轉(zhuǎn)換范圍 Set<ConvertiblePair> getConvertibleTypes();
配對(duì)類型 GenericConverter.ConvertiblePair
轉(zhuǎn)換方法 Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
類型描述 org.springframework.core.convert.TypeDescriptor

GenericConverter 會(huì)和 Converter 配合使用蔽介。如果轉(zhuǎn)換的是集合摘投,集合中的元素會(huì)使用 Converter 轉(zhuǎn)換煮寡。看一下 CollectionToArrayConverter 的實(shí)現(xiàn)犀呼。

// Collection 支持泛型幸撕,保存數(shù)據(jù)都是用的 Object[]。所以這里原核目標(biāo)都是object
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
    return Collections.singleton(new ConvertiblePair(Collection.class, Object[].class));
}

public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
    if (source == null) {
        return null;
    }
        // 類型轉(zhuǎn)換
    Collection<?> sourceCollection = (Collection<?>) source;
    TypeDescriptor targetElementType = targetType.getElementTypeDescriptor();
    Assert.state(targetElementType != null, "No target element type");
    Object array = Array.newInstance(targetElementType.getType(), sourceCollection.size());
    int i = 0;
    for (Object sourceElement : sourceCollection) {
                // 每個(gè)元素調(diào)用單獨(dú)轉(zhuǎn)換圆凰,convert 方法用到了 Converter 接口的實(shí)現(xiàn)
        Object targetElement = this.conversionService.convert(sourceElement,
                sourceType.elementTypeDescriptor(sourceElement), targetElementType);
        Array.set(array, i++, targetElement);
    }
    return array;
}

優(yōu)化 GenericConverter 接口

  • 局限性

    • 缺少S和T的前置判斷杈帐,這一點(diǎn)和Converter一樣
    • 單一類型轉(zhuǎn)換復(fù)雜。從 CollectionToArrayConverter 可以看到每個(gè)元素逐個(gè)轉(zhuǎn)換专钉,轉(zhuǎn)換依賴 Converter挑童。
  • 優(yōu)化,條件話的接口 ConditionalGenericConverter

    • 接口繼承 GenericConverter, ConditionalConverter
    • spring 內(nèi)部實(shí)現(xiàn)都是實(shí)現(xiàn)此接口跃须,如CollectionToArrayConverter
    • 如果我們要擴(kuò)展站叼,也要實(shí)現(xiàn)此接口。

統(tǒng)一類型轉(zhuǎn)換服務(wù)

org.springframework.core.convert.ConversionService菇民,主要的實(shí)現(xiàn)類繼承關(guān)系如下圖:


ConversionService
  • GenericConversionService
    通用模板實(shí)現(xiàn)尽楔,不內(nèi)置轉(zhuǎn)化器實(shí)現(xiàn)。
public class GenericConversionService implements ConfigurableConversionService {
        // 轉(zhuǎn)化器集合
    private final Converters converters = new Converters();
        // 保存轉(zhuǎn)化器容器緩存
    private final Map<ConverterCacheKey, GenericConverter> converterCache = new ConcurrentReferenceHashMap<>(64);

}

GenericConversionService 實(shí)現(xiàn)了 ConfigurableConversionService 接口第练,說明是可配置的阔馋,converters 保存轉(zhuǎn)化器,模板方法都提供了所有的操作方法娇掏。

  • DefaultConversionService
    基礎(chǔ)實(shí)現(xiàn)呕寝,內(nèi)置常用轉(zhuǎn)化器實(shí)現(xiàn)。
public class DefaultConversionService extends GenericConversionService {
       // 單例
       private static volatile DefaultConversionService sharedInstance;
    public DefaultConversionService() {
               // 構(gòu)造方法添加轉(zhuǎn)化器
        addDefaultConverters(this);
    }
    public static void addDefaultConverters(ConverterRegistry converterRegistry) {
        addScalarConverters(converterRegistry);
        addCollectionConverters(converterRegistry);

        converterRegistry.addConverter(new ByteBufferConverter((ConversionService) converterRegistry));
        converterRegistry.addConverter(new StringToTimeZoneConverter());
        converterRegistry.addConverter(new ZoneIdToTimeZoneConverter());
        converterRegistry.addConverter(new ZonedDateTimeToCalendarConverter());

        converterRegistry.addConverter(new ObjectToObjectConverter());
        converterRegistry.addConverter(new IdToEntityConverter((ConversionService) converterRegistry));
        converterRegistry.addConverter(new FallbackObjectToStringConverter());
        converterRegistry.addConverter(new ObjectToOptionalConverter((ConversionService) converterRegistry));
    }
}

DefaultConversionService 繼承自 GenericConversionService 婴梧,使用繼承自模板類的能力添加單個(gè)對(duì)象和集合對(duì)象的轉(zhuǎn)化器下梢,實(shí)現(xiàn)了 sharedInstance 單例方式。

  • FormattingConversionService
    通用FOrmatter+GenericConversionService實(shí)現(xiàn)塞蹭,不內(nèi)置轉(zhuǎn)化器實(shí)現(xiàn)孽江。增加對(duì) spring 內(nèi)部實(shí)現(xiàn)的支持,如:PrinterConverter番电,ParserConverter岗屏。

  • DefaultFormattingConversionService
    DefaultConversionService +格式化實(shí)現(xiàn),如JSR-354 Money漱办,Currency担汤,JSR-310 Date-Time。
    spring web 實(shí)現(xiàn)了 WebConversionService 洼冻,擴(kuò)展了更多的類型支持,基本上大同小異隅很。

總結(jié)

bean 創(chuàng)建的過程和TypeConversionService結(jié)合的主體流程:

  • 容器初始化時(shí)保存conversionService對(duì)象撞牢,AbstractApplicationContext#finishBeanFactoryInitialization率碾,查找CONVERSION_SERVICE_BEAN_NAME,并設(shè)置到 beanFactory
  • bean創(chuàng)建過程:
  • BeanDefination屋彪,通過xml或者掃描的方式
  • bean 實(shí)例會(huì)轉(zhuǎn)換成 BeanWrapper所宰,
  • AbstractBeanFactory#initBeanWrapper 設(shè)置BeanWrapper的 ConversionService 對(duì)象
  • 數(shù)據(jù)來源是 PropertiesValues
  • bw.setPropertyValues(mpvs);
  • TypeConverter#converIfNecessnary
  • TypeConverterDelegate#converIfNecessnary
  • PropertyEditor 或者 ConversionService

Spring 類型轉(zhuǎn)化接口有哪些?

  • 單一類型轉(zhuǎn)化:org.springframework.core.convert.converter.Converter
  • 通用類型轉(zhuǎn)化:org.springframework.core.convert.converter.GenericConverter
  • 條件轉(zhuǎn)化:org.springframework.core.convert.converter.ConditionalConverter
  • 綜合類型轉(zhuǎn)化接口org.springframework.core.convert.converter.ConditionalGenericConverter
  • BeanWrapper使用的類型轉(zhuǎn)換實(shí)現(xiàn):


    beanWrapper

BeanWrapper 繼承了TypeConverterSupport畜挥,在初始化的時(shí)候會(huì)給typeConverterDelegate賦值仔粥。


setWrappedInstance

this 是當(dāng)前BeanWrapper,因?yàn)槔^承了PropertyEditorRegistrySupport蟹但,所以有 PropertyEditor 和 ConversionService 信息躯泰。
這樣 BeanWrapper 和 PropertyEditor 和 ConversionService 就關(guān)聯(lián)起來了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末华糖,一起剝皮案震驚了整個(gè)濱河市麦向,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌客叉,老刑警劉巖诵竭,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異兼搏,居然都是意外死亡卵慰,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門佛呻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來裳朋,“玉大人,你說我怎么就攤上這事件相≡倥ぃ” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵夜矗,是天一觀的道長(zhǎng)泛范。 經(jīng)常有香客問我,道長(zhǎng)紊撕,這世上最難降的妖魔是什么罢荡? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮对扶,結(jié)果婚禮上区赵,老公的妹妹穿的比我還像新娘。我一直安慰自己浪南,他們只是感情好笼才,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著络凿,像睡著了一般骡送。 火紅的嫁衣襯著肌膚如雪昂羡。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天摔踱,我揣著相機(jī)與錄音虐先,去河邊找鬼。 笑死派敷,一個(gè)胖子當(dāng)著我的面吹牛蛹批,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播篮愉,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼腐芍,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了潜支?” 一聲冷哼從身側(cè)響起甸赃,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎冗酿,沒想到半個(gè)月后埠对,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡裁替,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年项玛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片弱判。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡襟沮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出昌腰,到底是詐尸還是另有隱情开伏,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布遭商,位于F島的核電站固灵,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏劫流。R本人自食惡果不足惜巫玻,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望祠汇。 院中可真熱鬧仍秤,春花似錦、人聲如沸可很。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽我抠。三九已至姜骡,卻和暖如春导坟,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背圈澈。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留尘惧,地道東北人康栈。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像喷橙,于是被迫代替她去往敵國(guó)和親啥么。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353