PropertyTokenizer
好了,到这里,终于完成了在settings元素的解析工作中涉及到知识点的学习工作了。
现在我们继续回到XMLConfigBuilder的settingsAsProperties方法中:
校验
setting子元素配置的属性名称在Configuration对象中是否有对应的setter方法定义:
// 获取Configuration类的描述对象
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
// 检查Settings节点下的配置是否被支持
for (Object key : props.keySet()) {
if (!metaConfig.hasSetter(String.valueOf(key))) {
// 校验 setting 配置的属性是否支持
throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");
}
}
前面我们已经知道MetaClass用于保存指定类的可读/可写的属性、方法以及构造器等信息,他的hasSetter方法接受一个String类型的name参数:
/**
* 判断当前Reflector维护的类中是否指定名称的属性的setter方法
*
* @param name 属性描述符
* @return 是否有指定属性的setter方法
*/
public boolean hasSetter(String name) {
// 包装属性描述符
PropertyTokenizer prop = new PropertyTokenizer(name);
if (prop.hasNext()) {
// 嵌套的属性描述符
if (reflector.hasSetter(prop.getName())) {
// 递归生成属性类型的MetaClass对象,并进行setter方法的判断
MetaClass metaProp = metaClassForProperty(prop.getName());
return metaProp.hasSetter(prop.getChildren());
} else {
// 未包含该属性,返回false
return false;
}
} else {
// 简单的属性描述符
// 直接判断是否有指定属性的setter方法
return reflector.hasSetter(prop.getName());
}
}
name参数是一个属性名称描述语句,他支持级联多层的属性嵌套,多层属性描述使用.作为分隔符,比如:panda.name.first表示取当前对象的panda属性的name属性的first属性。
同时name参数还可以表达对集合内具体某一元素的引用,比如:names[0].first和names[key].first这种语法也是被支持的。
hasSetter方法的逻辑也比较简单,在将属性名称描述符name包装成PropertyTokenizer之后,如果该属性名称描述符不包含多层引用的话(不含.),直接返回当前reflector对象中是否含有名称为name的属性的setter方法
,如果该属性名称描述符包含多层引用的话(含有.符号),则按照.做为分隔符,生成新的MetaClass对象,递归调用hasSetter方法完成属性的判断,直到中间某一属性不存在或属性描述符被处理完成。
比如:假设name属性的值为:panda.name.first:
因为panda.name.first中包含了.,他表示一个多层的属性调用,因此hasSetter方法会尝试从当前MetaClass维护的Reflector对象中,
判断其维护的类型是否有panda属性,如果没有直接返回false,
如果有,则调用metaClassForProperty方法为panda属性生成一个MetaClass对象,并将name.first作为name参数继续执行hasSetter方法的调用,直到解析到first属性。
因为first属性不包含.所以hasSetter方法会直接判断Reflector对象中是否有该属性的setter方法。
metaClassForProperty方法的实现比较简单,他的实现依赖于Reflector对象的getGetterType方法:
/**
* 获取指定属性的类型元数据
*
* @param name 指定字段
* @return 指定字段的类型元数据
*/
public MetaClass metaClassForProperty(String name) {
// 从反射元数据中获取该名称的getter方法的类型
Class<?> propType = reflector.getGetterType(name);
// 生成元数据
return MetaClass.forClass(propType, reflectorFactory);
}
Reflector对象的getGetterType方法:
/**
* 获取指定名称的属性的可读类型,该类型取决于指定属性的getter方法和属性定义本身
*
* @param propertyName 属性名称
* @return 指定名称的属性的可读类型
*/
public Class<?> getGetterType(String propertyName) {
// 获取属性类型
Class<?> clazz = getTypes.get(propertyName);
if (clazz == null) {
throw new ReflectionException("There is no getter for property named '" + propertyName + "' in '" + type + "'");
}
// 返回
return clazz;
}
整两个方法的实现都比较简单,就不赘述了,我们继续看关于属性描述符的解析工作。
属性描述符的解析操作并不复杂,它借助于PropertyTokenizer来实现,PropertyTokenizer是一个标准的迭代器实现。
迭代器是一种常见的行为性设计模式,他的作用是可以顺序的访问一个聚合对象中的各种元素,而又不暴露该对象的内部标识。 迭代器又被称为游标模式,游标是处理数据的一种方法,为了查看或者处理结果集中的数据,游标提供了在结果集中一次一行或者多行前进/后退的能力。
PropertyTokenizer实现了JAVA自带的迭代器接口定义 Iterator<E>,Iterator接口定义了四个方法:
hasNext:判断是否有下一个元素next:获取下一个元素remove:移除当前元素forEachRemaining:按照指定的方法处理剩余的元素
PropertyTokenizer完成了对前三个方法的实现,同时定义了四个属性并为其提供了对应的getter方法:
/**
* 属性名称:
* 比如names[0].first中的names
* 比如names.first中的names
*/
private String name;
/**
* 包含索引的名称,
* 比如:names[0].first中的names[0]
* 比如:names.first中的names
*/
private final String indexedName;
/**
* 索引值
* 比如:names[0].first中的0
* 比如:names.first中的null
*/
private String index;
/**
* 子属性
* 比如:names[0].first中的first
* 比如:panda.name.first中的name.first
*/
private final String children;
这四个属性的赋值操作是在PropertyTokenizer的构造方法中完成的,PropertyTokenizer的构造方法接收一个String类型的参数fullname,fullname是一个属性名称描述符.
他支持两种语法,一种是通过.分隔符表示多层属性的引用:name.first,另一种是通过[key]来表示集合中角标或者key值为key的元素:names[0].first。
/**
* 生成PropertyTokenizer对象
*
* @param fullname 完整的属性名称描述符
*/
public PropertyTokenizer(String fullname) {
// 假设入参为names[0].first
// 获取第一个属性分隔符的位置
int delim = fullname.indexOf('.');
if (delim > -1) {
// 有属性分隔符,表示是一个嵌套的多层属性
// 获取属性分隔符前面的内容作为属性,此处是names[0]
name = fullname.substring(0, delim);
// 属性分隔符后面的内容,则作为子属性保存起来,此处是first
children = fullname.substring(delim + 1);
} else {
// 当前不存属性分隔符,直接赋值即可
name = fullname;
children = null;
}
// 处理names[0].first这种包含了索引的场景
// 包含索引内容的属性名称此处是names[0]
indexedName = name;
// 获取属性名称中[ 符号的位置
delim = name.indexOf('[');
if (delim > -1) {
// 包含索引引用
// 获取索引的值,表示从names[1]中获取1。
index = name.substring(delim + 1, name.length() - 1);
// 重新赋值,将names[0]转为names
name = name.substring(0, delim);
}
}
构造方法里的逻辑也比较简单,分为两步,第一步解析.,第二步解析[:
第一步: 首先处理fullname为name和children属性赋值:
- 如果传到构造方法的
fullname包含了.,则表示他描述的是一个嵌套的多层属性的引用,对于这种场景,PropertyTokenizer会获取第一个.前面的部分作为name属性,并把.后面的内容赋值为children属性。 - 如果传到构造方法的
fullname不包含.,则表示他是一个简单的属性描述符,直接把fullname赋值给name属性,children属性赋值为null。
完成属性name和children的赋值操作之后,将上一步获取到的name属性的值赋值给indexedName属性,然后处理``
第二步:解析[,重置name属性,并完成index属性的赋值:
- 如果上一步获取到的
name属性中包含了字符[,则获取从[到name属性的倒数第二个字符之间的内容赋值给index属性,这里截止到倒数第二位,是默认最后一位的字符为]。 - 如果
name属性中不包含字符[,不进行任何操作。
完成了属性的赋值操作之后,再看PropertyTokenizer实现的Iterator的方法:
hasNext:判断当前PropertyTokenizer中是否有children的值。
@Override
public boolean hasNext() {
// 是否有children数据
return children != null;
}
next:通过children生成一个新的PropertyTokenizer对象
@Override
public PropertyTokenizer next() {
// 通过child生成新的PropertyTokenizer
return new PropertyTokenizer(children);
}
remove:不被支持
@Override
public void remove() {
throw new UnsupportedOperationException("Remove is not supported, as it has no meaning in the context of properties.");
}
在大致了解了PropertyTokenizer对象之后,我们回到XmlConfigBuilder的settingsAsProperties方法上来,此时我们基本已经完成了该方法的解析操作,现在该方法中唯一剩下的疑点就是Configuration都提供了那些属性的setter方法,
这个问题,我们后面在解析settingsElement会详细的介绍。
关注我,一起学习更多知识
