[spring源碼學習]十、IOC源碼-conversionService


一、代碼示例

  1、我們在之前的Person類里新增一個兩個屬性,分別是客戶的興趣和生日,興趣愛好有很多,我們使用list進行保存,生日使用日期進行保存

public class Person {
    private String name;
    public Date birth;
    public Date getBirth() {
        return birth;
    }

    public void setBirth(Date birth) {
        this.birth = birth;
    }

    //興趣愛好
    public List<String> interests;


    public List<String> getInterests() {
        return interests;
    }

    public void setInterests(List<String> interests) {
        this.interests = interests;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void sayHello(){
        System.out.println("hello "+this.name);
    }

}

  2、在bean里我們注入這兩個參數

    <bean name="person" class="com.zjl.Person">
        <property name="name" value="zhangsan"></property>
        <property name="interests" value="足球,籃球"></property>
        <property name="birth" value="2015-01-01"></property>
    </bean>

  3、測試代碼,我們打印出zhangsan的興趣和生日

public class Test {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
        Person person=(Person)context.getBean("person");
        System.out.println(person.interests);
        System.out.println(person.birth);
    }
}

  4、運行結果,很不幸,我們收到了一個異常信息,提示不能將字符串轉為日期格式

Cannot convert value of type [java.lang.String] to required type [java.util.Date] for property 'birth': no matching editors or conversion strategy found

  5、回看第七章源碼部分,我們在源碼的第10部分有如下代碼,從系統獲取一個conversionService,並將它放入到beanFactory中去,應該是轉化,我們找到conversionService的定義方法:

        //查找是否有id為conversionService的bean,如果有,設置進beanFactory
        if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
                beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
            beanFactory.setConversionService(
                    beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
        }

  6、我們試着寫一個這樣的bean

<bean id="conversionService" class="com.zjl.MyConversionService"></bean>

  7、類的構造如下

public class MyConversionService implements ConversionService {

    @Override
    public boolean canConvert(Class<?> sourceType, Class<?> targetType) {
        //判斷目標類型是否是Date
        if(Date.class.isAssignableFrom(targetType)){
            return true;
        }
        System.out.println(targetType);
        return false;
    }

    @Override
    public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) {

        //判斷目標類型是否是Date
        if(Date.class.isAssignableFrom(targetType.getObjectType())){
            return true;
        }
//        System.out.println(targetType);
        return false;
    }

    @Override
    public <T> T convert(Object source, Class<T> targetType) {
//        System.out.println("convert");
        return null;
    }

    @Override
    public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
        //如果源類型是string,我們直接將他轉化為Date類型
        if(String.class.isAssignableFrom(sourceType.getObjectType())){
            DateFormat format=new SimpleDateFormat("yyyy-MM-dd");
            try {
                return format.parse((String) source);
            } catch (ParseException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
//        System.out.println("convert1");
        return null;
    }

}

  8、打印結果

[足球,籃球]
Thu Jan 01 00:00:00 CST 2015

  到這里,例子基本已經完成了,可是仔細觀察,我們會發現其實還有些不完美的地方:

  1、我們寫入了足球,籃球,作為兩個興趣,可是程序直接將他變成了一個愛好,也就是list.add("足球,籃球"),與我們預想不一致。解決的思路我們可以想象:再注入一個bean,將字符串按照指定字符分割,轉為list

  2、由於spirng默認只能讀取conversionService,我們成功轉化了字符串為日期,如果想完成第一步的轉化就出現了問題,我們不妨將多個conversion方法注入到bean-conversionService中,然后他依次調用和選擇

二、源碼分析

  1、我們看下spring中如何使用conversionService和幫我們實現一些預制的轉化方法的,將我們自己定義的converter也注入進去

   <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <bean class="com.zjl.MyConverter">
            </bean>
        </property>
    </bean>

   2、我們初始化bean的時候,跟蹤代碼到這里,獲取了系統注入的conversionService

        ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
        if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
            TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
            if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
                try {
                    return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
                }
                catch (ConversionFailedException ex) {
                    // fallback to default conversion logic below
                    conversionAttemptEx = ex;
                }
            }
        }

  3、到canConvert為在service中獲取指定源格式和目標格式的converter,判斷是否可以獲取

    public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) {
        Assert.notNull(targetType, "targetType to convert to cannot be null");
        if (sourceType == null) {
            return true;
        }
        GenericConverter converter = getConverter(sourceType, targetType);
        return (converter != null);
    }

  4、在緩存中獲取converter,如果沒有,到set中獲取,保存到緩存中,如果set也沒有獲取,保存為NO_MATCH

    protected GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
        ConverterCacheKey key = new ConverterCacheKey(sourceType, targetType);
        GenericConverter converter = this.converterCache.get(key);
        if (converter != null) {
            return (converter != NO_MATCH ? converter : null);
        }

        converter = this.converters.find(sourceType, targetType);
        if (converter == null) {
            converter = getDefaultConverter(sourceType, targetType);
        }

        if (converter != null) {
            this.converterCache.put(key, converter);
            return converter;
        }

        this.converterCache.put(key, NO_MATCH);
        return null;
    }

  5、找到converter后,調用conversionService.convert(newValue, sourceTypeDesc, typeDescriptor),如果沒有converter就直接拋出錯誤

        GenericConverter converter = getConverter(sourceType, targetType);
        if (converter != null) {
            Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType);
            return handleResult(sourceType, targetType, result);
        }
        return handleConverterNotFound(source, sourceType, targetType);

  6、調用ConversionUtils.invokeConverter,調用converter的convert的方法

    public static Object invokeConverter(GenericConverter converter, Object source, TypeDescriptor sourceType,
            TypeDescriptor targetType) {
        try {
            return converter.convert(source, sourceType, targetType);
        }
        catch (ConversionFailedException ex) {
            throw ex;
        }
        catch (Exception ex) {
            throw new ConversionFailedException(sourceType, targetType, source, ex);
        }
    }

  7、至於convert方法中如何進行轉化就全靠我們自己寫了

三、總結

  對於spring的IOC中注入的參數,雖然都是字符串,但是經過系統提供的接口我們可以將它與bean中字段的各種類型進行適配,適配過程需要定義conversionService,spring提供了默認的實現FactoryBean,他可以以set形式注入自定義的converter,也使用系統默認的轉換器。

  我們來改造我們之前的轉換器,通過源代碼可以看到Converter以泛型中的類型作為是否對此次數據轉換的選擇  

public class MyConverter implements Converter<String,Date> {

    @Override
    public Date convert(String source) {
        DateFormat format=new SimpleDateFormat("yyyy-MM-dd");
        try {
            return format.parse((String) source);
        } catch (ParseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;
    }

}

 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM