数据绑定底层设计

数据转换(一)

 

在上一章节,我们对SpringMVC参数绑定原理做了详细分析,并介绍了数据绑定的例子及部分源码,本章将深入介绍数据绑定的底层实现及数据转换核心设计。

数据绑定底层设计

首先我们回到上个章节介绍的Demo



@Controller
public class UserController  {

    private String name;

    private Long id;

    private int age;

    public String getName() {
        return name;
    }

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

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

测试类


public class MyBeanFactoryPostProcessor {


    public static void main(String[] args) {

        UserController userController=new UserController();

        DataBinder dataBinder= new DataBinder(userController,"userController");
        // 创建数据源
        Map<String,String> map = new HashMap();

        map.put("name","帅哥");
        map.put("id","123");
        map.put("age","123456");

        PropertyValues propertyValues=new MutablePropertyValues(map);

        dataBinder.bind(propertyValues);

        System.out.println(userController);
    }

}

以及运行结果

其实很简单,无非是将能够映射的参数进行转换并赋值绑定,好了我们开始Debug,

进入doBing

前面两个方法都是检验,跳过进入applyPropertyValues(mpvs);

进入setPropertyValues方法

这里很简单,获取我们的propertyValues,然后实现数据循环绑定调用setPropertyValue(pv);

@Override
  public void setPropertyValue(PropertyValue pv) throws BeansException {
   //这里   PropertyTokenHolder 是内部类,里面风胡子那个了两个属性
   //分别是actualName,canonicalName 其实就是需要绑定的属性
    PropertyTokenHolder tokens = (PropertyTokenHolder) pv.resolvedTokens;
    if (tokens == null) {
      String propertyName = pv.getName();
      AbstractNestablePropertyAccessor nestedPa;
      try {
        nestedPa = getPropertyAccessorForPropertyPath(propertyName);
      }
      catch (NotReadablePropertyException ex) {
        throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName,
            "Nested property in path '" + propertyName + "' does not exist", ex);
      }
      tokens = getPropertyNameTokens(getFinalPath(nestedPa, propertyName));
      if (nestedPa == this) {
        pv.getOriginalPropertyValue().resolvedTokens = tokens;
      }
      nestedPa.setPropertyValue(tokens, pv);
    }
    else {
      setPropertyValue(tokens, pv);
    }
  }

这段代码有一个tokens ,里面有两个属性分别是actualName,canonicalName 其实就是需要绑定的属性,然后在对PropertyValue进行复制,其实前面的部分都是对PropertyValue初始化的操作,我们重点关注这里  nestedPa.setPropertyValue(tokens, pv);点进去

进入 processLocalProperty(tokens, pv);下面我会将解释作为注释写入代码


private void processLocalProperty(PropertyTokenHolder tokens, PropertyValue pv) {
    //这里的PropertyHandler 是对我们的UserController进行封装,里面包含了
    //UserController的所有方法 属性,并且对属性的set、get方法也绑定在一起
    //目的就是在后文数据绑定操作d额时候直接能够找到set get方法
    //结合下面的图一我们可以看到
    PropertyHandler ph = getLocalPropertyHandler(tokens.actualName);
    if (ph == null || !ph.isWritable()) {
      if (pv.isOptional()) {
        if (logger.isDebugEnabled()) {
          logger.debug("Ignoring optional value for property '" + tokens.actualName +
              "' - property not found on bean class [" + getRootClass().getName() + "]");
        }
        return;
      }
      else {
        throw createNotWritablePropertyException(tokens.canonicalName);
      }
    }

    Object oldValue = null;
    try {
    //  上面都不是关键,这里开始 获取我们map设置额值,
      Object originalValue = pv.getValue();
      Object valueToApply = originalValue;
      if (!Boolean.FALSE.equals(pv.conversionNecessary)) {
        if (pv.isConverted()) {
          valueToApply = pv.getConvertedValue();
        }
        else {
          if (isExtractOldValueForEditor() && ph.isReadable()) {
            try {
              oldValue = ph.getValue();
            }
            catch (Exception ex) {
              if (ex instanceof PrivilegedActionException) {
                ex = ((PrivilegedActionException) ex).getException();
              }
              if (logger.isDebugEnabled()) {
                logger.debug("Could not read previous value of property '" +
                    this.nestedPath + tokens.canonicalName + "'", ex);
              }
            }
          }
          //直到代码走到这里,这里就是我们所要提到的数据转换了,
          //在上文的数据绑定,笔者提到绑定之前需要找到对应的转换器
          //而这里就是获取我们的转换器,我们通过图三演示
          valueToApply = convertForProperty(
              tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor());
        }
        pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue);
      }
      ph.setValue(valueToApply);
    }
    catch (TypeMismatchException ex) {
      throw ex;
    }
    catch (InvocationTargetException ex) {
      PropertyChangeEvent propertyChangeEvent = new PropertyChangeEvent(
          getRootInstance(), this.nestedPath + tokens.canonicalName, oldValue, pv.getValue());
      if (ex.getTargetException() instanceof ClassCastException) {
        throw new TypeMismatchException(propertyChangeEvent, ph.getPropertyType(), ex.getTargetException());
      }
      else {
        Throwable cause = ex.getTargetException();
        if (cause instanceof UndeclaredThrowableException) {
          // May happen e.g. with Groovy-generated methods
          cause = cause.getCause();
        }
        throw new MethodInvocationException(propertyChangeEvent, cause);
      }
    }
    catch (Exception ex) {
      PropertyChangeEvent pce = new PropertyChangeEvent(
          getRootInstance(), this.nestedPath + tokens.canonicalName, oldValue, pv.getValue());
      throw new MethodInvocationException(pce, ex);
    }
  }

结合上面代码:图一

图二

图三

首先我们看到这里没执行时是字符串123,执行完后

字符串类型变成了Long类型,这样就简单了,我们得到了long类型的数据类型和值是不是可以直接通过set方法进行属性赋值了呢?答案是肯定的。好了接来下我们看下底层是如何转换的,进入convertForProperty()方法

注意我们传入的参数,有属性名称,有原始值,有要属性设置的值,还有需要转换的类型,好的我们在跟进

再跟

我们可以看到PropertyEditor,这个PropertyEditor其实就是类型转换的方式之一,当然这里PropertyEditor肯定为空,因为我们没有操作过它,我们往下看,上面的代码都不关键,直到将代码停在这里

可以看到此时的editor还是为空,通过方法我们能够知道这个代码就是找到editor,我们进去看看,进入后我们调用方法栈,从

再到


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

    // Simple editors, without parameterization capabilities.
    // The JDK does not contain a default editor for any of these target types.
    this.defaultEditors.put(Charset.class, new CharsetEditor());
    this.defaultEditors.put(Class.class, new ClassEditor());
    this.defaultEditors.put(Class[].class, new ClassArrayEditor());
    this.defaultEditors.put(Currency.class, new CurrencyEditor());
    this.defaultEditors.put(File.class, new FileEditor());
    this.defaultEditors.put(InputStream.class, new InputStreamEditor());
    this.defaultEditors.put(InputSource.class, new InputSourceEditor());
    this.defaultEditors.put(Locale.class, new LocaleEditor());
    this.defaultEditors.put(Path.class, new PathEditor());
    this.defaultEditors.put(Pattern.class, new PatternEditor());
    this.defaultEditors.put(Properties.class, new PropertiesEditor());
    this.defaultEditors.put(Reader.class, new ReaderEditor());
    this.defaultEditors.put(Resource[].class, new ResourceArrayPropertyEditor());
    this.defaultEditors.put(TimeZone.class, new TimeZoneEditor());
    this.defaultEditors.put(URI.class, new URIEditor());
    this.defaultEditors.put(URL.class, new URLEditor());
    this.defaultEditors.put(UUID.class, new UUIDEditor());
    this.defaultEditors.put(ZoneId.class, new ZoneIdEditor());

    // Default instances of collection editors.
    // Can be overridden by registering custom instances of those as custom editors.
    this.defaultEditors.put(Collection.class, new CustomCollectionEditor(Collection.class));
    this.defaultEditors.put(Set.class, new CustomCollectionEditor(Set.class));
    this.defaultEditors.put(SortedSet.class, new CustomCollectionEditor(SortedSet.class));
    this.defaultEditors.put(List.class, new CustomCollectionEditor(List.class));
    this.defaultEditors.put(SortedMap.class, new CustomMapEditor(SortedMap.class));

    // Default editors for primitive arrays.
    this.defaultEditors.put(byte[].class, new ByteArrayPropertyEditor());
    this.defaultEditors.put(char[].class, new CharArrayPropertyEditor());

    // The JDK does not contain a default editor for char!
    this.defaultEditors.put(char.class, new CharacterEditor(false));
    this.defaultEditors.put(Character.class, new CharacterEditor(true));

    // 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));

    // Only register config value editors if explicitly requested.
    if (this.configValueEditorsActive) {
      StringArrayPropertyEditor sae = new StringArrayPropertyEditor();
      this.defaultEditors.put(String[].class, sae);
      this.defaultEditors.put(short[].class, sae);
      this.defaultEditors.put(int[].class, sae);
      this.defaultEditors.put(long[].class, sae);
    }
  }

可以看到这个editor是内建,并且这里就很多很多的转换器,并且都是Key-value的方式,而此时的value相信大家都知道是我们的转换器,那么key是什么呢?key就是我们所需要的转换的类型,如我们上文的例子 id 是Long类型,我们所需要被转换的类型就是Long类型,然么这里读者或许会已经看出editor的短板了,这里我们put了很多转换器,发现我们的源类型默认都是String类型,也就是说我们必须是String类型转换为其他类型,这其实是editor的缺陷,如果我们要从Long类型或者Integer类型转换为String类型,明显editor就无法满足我们的需求,这里就Spring提出了一个新的转换器 Converter<S, T> , Converter<S, T> 就是将我们的原始类型作为泛型的方式,不在和editor一样只支持String类型,好了 Converter<S, T>会在下个章节描述,我们本章重点分析数据绑定,回到我们的IDEA环境,

可以看到通过key value查找的方式,我们就可以获取我们需要的转换器,拿到转换器后就简单了执行doConvertValue()方法,在跟踪

再跟踪

 

public static <T extends Number> T parseNumber(String text, Class<T> targetClass) {
    Assert.notNull(text, "Text must not be null");
    Assert.notNull(targetClass, "Target class must not be null");
    String trimmed = StringUtils.trimAllWhitespace(text);

    if (Byte.class == targetClass) {
      return (T) (isHexNumber(trimmed) ? Byte.decode(trimmed) : Byte.valueOf(trimmed));
    }
    else if (Short.class == targetClass) {
      return (T) (isHexNumber(trimmed) ? Short.decode(trimmed) : Short.valueOf(trimmed));
    }
    else if (Integer.class == targetClass) {
      return (T) (isHexNumber(trimmed) ? Integer.decode(trimmed) : Integer.valueOf(trimmed));
    }
    else if (Long.class == targetClass) {
      return (T) (isHexNumber(trimmed) ? Long.decode(trimmed) : Long.valueOf(trimmed));
    }
    else if (BigInteger.class == targetClass) {
      return (T) (isHexNumber(trimmed) ? decodeBigInteger(trimmed) : new BigInteger(trimmed));
    }
    else if (Float.class == targetClass) {
      return (T) Float.valueOf(trimmed);
    }
    else if (Double.class == targetClass) {
      return (T) Double.valueOf(trimmed);
    }
    else if (BigDecimal.class == targetClass || Number.class == targetClass) {
      return (T) new BigDecimal(trimmed);
    }
    else {
      throw new IllegalArgumentException(
          "Cannot convert String [" + text + "] to target class [" + targetClass.getName() + "]");
    }
  }

这里会找到Long.class ,转换类型最终看结果

获取转换后的数据后最终执行我们的set方法

这样就实现了我们数据绑定,好了本章对数据绑定的源码做了十分详细的分析和介绍,并提出了PropertiesEditor的缺陷,以及Spring为何提出的新的转换器 Converter<S, T>,下个章节将对PropertiesEditor和 Converter<S, T>进行比对。

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值