数据转换(一)
在上一章节,我们对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>进行比对。