spring(五):set注入(下)

PropertyEditorSupport

当标签为value,我们并不想只是简单的给String类型赋值。柿子先挑软的捏,我们先给int类型赋值。我们写了一个测试用例:

public class CustomNumberEditorTest {

    @Test
    public void testConvertString(){
        CustomNumberEditor editor = new CustomNumberEditor(Integer.class,true);
        editor.setAsText("3");
        Object value = editor.getValue();
        Assert.assertTrue(value instanceof Integer);
        Assert.assertEquals(3,((Integer) editor.getValue()).intValue());

        editor.setAsText("");
        Assert.assertTrue(editor.getValue() == null);
        try {
            editor.setAsText("3.1");
        }catch (IllegalArgumentException e){
            return;
        }
        Assert.fail();

    }
}

这里使用了CustomNumberEditor类,它继承的java.beans下面的PropertyEditorSupport,我们通过重写它的setAsText,通过getValue便会把String类型转换成整型。下面给出CustomNumberEditor代码:

public class CustomNumberEditor extends PropertyEditorSupport {

   private final Class<? extends Number> numberClass;

   private final NumberFormat numberFormat;

   private final boolean allowEmpty;


   /**
    * 构造方法
    * @param numberClass 数据类型
    * @param allowEmpty 是否允许为空
    * @throws IllegalArgumentException
    */
   public CustomNumberEditor(Class<? extends Number> numberClass , boolean allowEmpty)throws IllegalArgumentException{
       this(numberClass,null,allowEmpty);
   }

   /**
    * 构造方法
    * @param numberClass 数据类型
    * @param numberFormat 数据格式
    * @param allowEmpty 是否为空
    * @throws IllegalArgumentException
    */
   public CustomNumberEditor(Class<? extends Number> numberClass, NumberFormat numberFormat ,
                             boolean allowEmpty)throws IllegalArgumentException{

       if (numberClass == null || !Number.class.isAssignableFrom(numberClass)){
           throw new IllegalArgumentException("Property class must be a subclass of Number");
       }
       this.numberClass = numberClass;
       this.numberFormat = numberFormat;
       this.allowEmpty = allowEmpty;
   }

   /**
    * 设置要转换的字符串
    * @param text
    * @throws IllegalArgumentException
    */
   @Override
   public void setAsText(String text) throws IllegalArgumentException {
       if (this.allowEmpty && !StringUtils.hasText(text)){
           setValue(null);
       }else if (this.numberFormat != null){
           setValue(NumberUtils.parseNumber(text, this.numberClass, this.numberFormat));
       }
       else {
           setValue(NumberUtils.parseNumber(text, this.numberClass));
       }
   }
}

此时我们便通过了上述的测试用例。接下来我们实现自动注入boolean类型的数据,当输入on/off,注入true/false;当输入yes/no,注入true/false等。按照惯例我们先给出测试用例:

public class CustomBooleaneditorTest {

    @Test
    public void testConvertStringToBoolean(){
        CustomBooleanEditor editor = new CustomBooleanEditor(true);

        editor.setAsText("true");
        Assert.assertEquals(true, ((Boolean)editor.getValue()).booleanValue());
        editor.setAsText("false");
        Assert.assertEquals(false, ((Boolean)editor.getValue()).booleanValue());

        editor.setAsText("on");
        Assert.assertEquals(true, ((Boolean)editor.getValue()).booleanValue());
        editor.setAsText("off");
        Assert.assertEquals(false, ((Boolean)editor.getValue()).booleanValue());


        editor.setAsText("yes");
        Assert.assertEquals(true, ((Boolean)editor.getValue()).booleanValue());
        editor.setAsText("no");
        Assert.assertEquals(false, ((Boolean)editor.getValue()).booleanValue());


        try{
            editor.setAsText("aabbcc");
        }catch(IllegalArgumentException e){
            return;
        }
        Assert.fail();
    }
}

同样我们新建一个类,并继承PropertyEditorSupport重写setAsText方法,我们给出具体的代码示例:

public class CustomBooleanEditor extends PropertyEditorSupport{

    public static final String VALUE_TRUE = "true";
    public static final String VALUE_FALSE = "false";

    public static final String VALUE_ON = "on";
    public static final String VALUE_OFF = "off";

    public static final String VALUE_YES = "yes";
    public static final String VALUE_NO = "no";

    public static final String VALUE_1 = "1";
    public static final String VALUE_0 = "0";


    private final boolean allowEmpty;


    public CustomBooleanEditor(boolean allowEmpty) {
        this.allowEmpty = allowEmpty;
    }


    /**
     * 设置要转换的字符串
     * @param text
     * @throws IllegalArgumentException
     */
    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        String input = (text != null ? text.trim() : null);
        if (this.allowEmpty && !StringUtils.hasLength(input)) {
            // Treat empty String as null value.
            setValue(null);
        }
        else if ((VALUE_TRUE.equalsIgnoreCase(input) || VALUE_ON.equalsIgnoreCase(input) ||
                VALUE_YES.equalsIgnoreCase(input) || VALUE_1.equals(input))) {
            setValue(Boolean.TRUE);
        }
        else if ((VALUE_FALSE.equalsIgnoreCase(input) || VALUE_OFF.equalsIgnoreCase(input) ||
                VALUE_NO.equalsIgnoreCase(input) || VALUE_0.equals(input))) {
            setValue(Boolean.FALSE);
        }
        else {
            throw new IllegalArgumentException("Invalid boolean value [" + text + "]");
        }
    }
}

此时测试用例通过我们便完成了字符串转换为整型和布尔类型。但是事先我们并不知道每个属性是什么类型的,所以我们还需要一个能自动帮助我们把字符串转换成属性类型的功能。我们先写一个测试用例来描述我们的目的:

public class TypeConverterTest {

    @Test
    public void testConvertStringToInt(){
        TypeConverter converter = new SimpleTypeConverter();
        Integer i = converter.convertIfNecessary("3",Integer.class);
        Assert.assertEquals(3,i.intValue());
        try {
            converter.convertIfNecessary("3.1",Integer.class);
        }catch (TypeMismatchException e){
            return;
        }
        fail();
    }

    @Test
    public void testConverStringToBoolean(){
        TypeConverter converter = new SimpleTypeConverter();
        Boolean b = converter.convertIfNecessary("true",Boolean.class);
        try {
            converter.convertIfNecessary("xxxxxx",Boolean.class);
        }catch (TypeMismatchException e){
            return;
        }
        fail();
    }
}

通过TypeConverter自动自动帮助我们转换成对应的类型。TypeConverter是一个简单的接口:

public interface TypeConverter {

    <T> T convertIfNecessary(Object value,Class<T> requiredType)throws TypeMismatchException;

}

我们来写一个它的具体实现:

public class SimpleTypeConverter implements TypeConverter {

    private Map<Class<?> , PropertyEditor> defaultEditors;

    public SimpleTypeConverter(){
    }

    /**
     * 把value转化成具体的实体数据
     * @param value
     * @param requiredType
     * @param <T>
     * @return
     * @throws TypeMismatchException
     */
    @Override
    public <T> T convertIfNecessary(Object value, Class<T> requiredType) throws TypeMismatchException {
        if (ClassUtils.isAssignableValue(requiredType,value)){
            return (T)value;
        }else {
            if (value instanceof String ){
                //如果是字符串类型,则根据类型查找要转换成什么数据。
                PropertyEditor editor = findDefaultEditor(requiredType);
                try {
                    editor.setAsText((String)value);
                }catch (IllegalArgumentException e){
                    throw new TypeMismatchException(value,requiredType);
                }
                return (T)editor.getValue();
            }else {
                throw new RuntimeException("Todo : can't convert value for " + value +
                        " class :" +requiredType);
            }
        }
    }

    /**
     * 查询要使用哪个PropertyEditor
     * @param requiredType
     * @return
     */
    private PropertyEditor findDefaultEditor(Class<?> requiredType){
        PropertyEditor editor = this.getDefaultEditor(requiredType);
        if (editor == null){
            throw new RuntimeException("Editor for "+ requiredType + " has not been implemented" );
        }
        return editor;
    }

    /**
     * 根据class返回PropertyEditor
     * @param requiredType
     * @return
     */
    public PropertyEditor getDefaultEditor(Class<?> requiredType) {

        if (this.defaultEditors == null) {
            createDefaultEditors();
        }
        return this.defaultEditors.get(requiredType);
    }

    private void createDefaultEditors() {
        this.defaultEditors = new HashMap<>(64);

        // Spring's CustomBooleanEditor accepts more flag values than the JDK's default editor.
        this.defaultEditors.put(boolean.class, new CustomBooleanEditor(false));
        this.defaultEditors.put(Boolean.class, new CustomBooleanEditor(true));

        // The JDK does not contain default editors for number wrapper types!
        // Override JDK primitive number editors with our own CustomNumberEditor.
        /*this.defaultEditors.put(byte.class, new CustomNumberEditor(Byte.class, false));
        this.defaultEditors.put(Byte.class, new CustomNumberEditor(Byte.class, true));
        this.defaultEditors.put(short.class, new CustomNumberEditor(Short.class, false));
        this.defaultEditors.put(Short.class, new CustomNumberEditor(Short.class, true));*/
        this.defaultEditors.put(int.class, new CustomNumberEditor(Integer.class, false));
        this.defaultEditors.put(Integer.class, new CustomNumberEditor(Integer.class, true));
        /*this.defaultEditors.put(long.class, new CustomNumberEditor(Long.class, false));
        this.defaultEditors.put(Long.class, new CustomNumberEditor(Long.class, true));
        this.defaultEditors.put(float.class, new CustomNumberEditor(Float.class, false));
        this.defaultEditors.put(Float.class, new CustomNumberEditor(Float.class, true));
        this.defaultEditors.put(double.class, new CustomNumberEditor(Double.class, false));
        this.defaultEditors.put(Double.class, new CustomNumberEditor(Double.class, true));
        this.defaultEditors.put(BigDecimal.class, new CustomNumberEditor(BigDecimal.class, true));
        this.defaultEditors.put(BigInteger.class, new CustomNumberEditor(BigInteger.class, true));*/


    }

}

此时我们已经完成了字符串到类型的自动转换,我们还需要做的就是把这一部分内容与已有的代码进行整合。思考一下,应该在哪一步进行整合呢?我们想一下在set注入(上)中增加了给bean设置属性的方法。我们现在来更新一下这个方法:

   protected void populateBean(BeanDefinition bd,Object bean){
        //获取beanDefintion的value
        List<PropertyValue> pvs = bd.getPropertyValues();
        if (pvs == null || pvs.isEmpty()){
            return;
        }
        BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this);
        SimpleTypeConverter converter = new SimpleTypeConverter();

        try {
            for (PropertyValue pv : pvs){
                String propertyName = pv.getName();
                Object originalValue = pv.getValue();
                //获取具体的实体或值
                Object resolvedValue = valueResolver.resolveValueIfNecessary(originalValue);
                //利用java自带的bean方法,对bean实体属性进行赋值。
                BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass());
                PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
                for (PropertyDescriptor pd : pds){
                    if (pd.getName().equals(propertyName)){
                        Object convertedValue = converter.convertIfNecessary(resolvedValue,pd.getPropertyType());
                        pd.getWriteMethod().invoke(bean,convertedValue);
                        break;
                    }
                }
            }
        }catch (Exception e){
            throw new BeanCreationException("Failed to obtain BeanInfo for class ["+bd.getBeanClassName()+"]");
        }
    }

此时我们的便完成了set注入。具体内容可查看litespring_05


                                                                                                生活要多点不自量力

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,123评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,031评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,723评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,357评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,412评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,760评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,904评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,672评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,118评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,456评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,599评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,264评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,857评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,731评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,956评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,286评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,465评论 2 348

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,090评论 1 32
  • 第5章 引用类型(返回首页) 本章内容 使用对象 创建并操作数组 理解基本的JavaScript类型 使用基本类型...
    大学一百阅读 3,216评论 0 4
  • 这是16年5月份编辑的一份比较杂乱适合自己观看的学习记录文档,今天18年5月份再次想写文章,发现简书还为我保存起的...
    Jenaral阅读 2,737评论 2 9
  • 1. 简介 1.1 什么是 MyBatis ? MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的...
    笨鸟慢飞阅读 5,453评论 0 4
  •   引用类型的值(对象)是引用类型的一个实例。   在 ECMAscript 中,引用类型是一种数据结构,用于将数...
    霜天晓阅读 1,042评论 0 1