2.4.7.标准的属性名称解析器 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
会详细的介绍。