Mybatis 源码学习-反射工具(MetaClass)
Posted 凉茶方便面
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Mybatis 源码学习-反射工具(MetaClass)相关的知识,希望对你有一定的参考价值。
历史文章:
Mybatis 源码学习(5)-反射工具(Property 工具)
MetaClass 可以被用来解析任意 Class 对象的方法和字段,它对外提供了:findProperty、hasSetter、hasGetter、getSetterType、getGetterType 等方法判断属性以及对应的 get、set 方法是否存在。在 MetaClass 内部,使用了 Reflector 和 PropertyTokenizer 实现上述解析功能。在使用 Reflector 时,还允许指定 ReflectorFactory,默认的 DefaultReflectorFactory 允许对 Reflector 进行缓存。
MetaClass 的内部字段如下:
// 创建 Reflector 的工厂,默认的 DefaultReflectorFactory 可以提供 Reflector 的缓存
private final ReflectorFactory reflectorFactory;
// 记录 Class 的元信息(创建 MetaClass 会指定 Class 对象,Reflector 是对该 Class 对象的元信息解析)
private final Reflector reflector;
MetaClass 提供了工厂方法,并且不允许外部直接构造 MetaClass 对象,它的工厂方法要求指定 Class 对象以便初始化 Reflector 字段。
// 使用 private 修饰构造器,避免外部直接创建该对象
private MetaClass(Class<?> type, ReflectorFactory reflectorFactory)
this.reflectorFactory = reflectorFactory;
// 使用 reflectorFactory 创建 reflector 对象,DefaultReflectorFactory 提供缓存功能
this.reflector = reflectorFactory.findForClass(type);
// 使用工厂方法创建 MetaClass
public static MetaClass forClass(Class<?> type, ReflectorFactory reflectorFactory)
return new MetaClass(type, reflectorFactory);
MetaClass 中比较重要的方法是 findProperty
,它能够解析对应的属性,同时能够构造.
分割的属性字符串。它的实现是通过 buildProperty 方法实现的,而 buildProperty 内部会调用 PropertyTokenizer 做属性字符串解析。
public String findProperty(String name)
// 由 buildProperty 做具体的实现
StringBuilder prop = buildProperty(name, new StringBuilder());
return prop.length() > 0 ? prop.toString() : null;
private StringBuilder buildProperty(String name, StringBuilder builder)
// 解析属性表达式(允许”.”分割)
PropertyTokenizer prop = new PropertyTokenizer(name);
// 是否有子表达式
if (prop.hasNext())
// 获取当前 PropertyTokenizer.name 表达式对应的属性名
String propertyName = reflector.findPropertyName(prop.getName());
if (propertyName != null)
// 组合属性名
builder.append(propertyName);
builder.append(".");
// 创建当前属性对应的 MetaClass 对象
MetaClass metaProp = metaClassForProperty(propertyName);
// 继续解析 PropertyTokenizer.children 对应的属性信息(递归调用)
metaProp.buildProperty(prop.getChildren(), builder);
else // 递归出口
// 获取最终的属性名称
String propertyName = reflector.findPropertyName(name);
if (propertyName != null)
builder.append(propertyName);
return builder;
public MetaClass metaClassForProperty(String name)
// 查找指定属性对应的 Class 对象
Class<?> propType = reflector.getGetterType(name);
// 为该属性创建对应的 MetaClass 对象
return MetaClass.forClass(propType, reflectorFactory);
需要注意到是,findProperty 只能根据.
进行属性处理,无法处理带有下标的属性。如,存在如下两个类 ClassA 和 ClassB,对于待解析的属性 b.lists
,findProperty 可以正常解析,但是对于待解析属性 listB[0].lists
,findProperty 是无法正常解析的,它会被解析为 listB.
。
b.lists
的解析过程:PropertyTokenizer 将表达式分割为 name=b 和 children=lists,然后使用 builder 记录 b,并使用 metaClassForProperty() 方法创建 b 的原始类 ClassB,对应的 MetaClass。之后采用递归调用的方式,使用 ClassB 的 MetaClass.buildProperty 方法解析子表达式 lists,此时已经再无子表达式,因此添加至 builder 后返回字符串b.lists
。
hasSetter()、hasGetter() 两个方法比较类似,它们负责判断表达式是否包含对应的属性,这里以略复杂的 hasGetter() 方法为例。比较特殊的是这两个方法会在 Reflector 的 setMethods 和 setMethods 集合中搜索属性,但是这两个集合包含了 set 方法、get 方法以及字段对应的集合(Reflector 处理字段时,将 Fields 也加入了 setMethods/setMethods 集合),因此这两个方法不仅会判断 set/get 方法,还会判断对象域。
public boolean hasGetter(String name)
// 解析属性表达式
PropertyTokenizer prop = new PropertyTokenizer(name);
// 存在子表达式
if (prop.hasNext())
// PropertyTokenizer.name 指定的属性有 getter 方法,才能处理子表达式
if (reflector.hasGetter(prop.getName()))
// 该方法 metaClassForProperty(PropertyTokenizer) 是 metaClassForProperty(String)
// 的重载,它不仅可以直接解析表达式,还可以解析泛型参数
MetaClass metaProp = metaClassForProperty(prop);
// 递归处理子表达式
return metaProp.hasGetter(prop.getChildren());
else
return false; // 递归结束
else
return reflector.hasGetter(prop.getName()); // 递归结束
metaClassForProperty(PropertyTokenizer) 会调用 getGetterType 方法继续解析 PropertyTokenizer 处理的结果,它不仅会解析字段类型,还按照需要解析泛型参数的类型。
private MetaClass metaClassForProperty(PropertyTokenizer prop)
// 获取表达式表示的属性类型
Class<?> propType = getGetterType(prop);
// 解析表达式表示的 MetaClass
return MetaClass.forClass(propType, reflectorFactory);
// 解析属性对应的类型信息,如果是带下标的集合类型,则会解析为实际类型
private Class<?> getGetterType(PropertyTokenizer prop)
// 获取属性的类型信息
Class<?> type = reflector.getGetterType(prop.getName());
// 如果存在下标(用 [] 指定下标),并且是集合类型 Collection 的子类
if (prop.getIndex() != null && Collection.class.isAssignableFrom(type))
// 解析属性的类型
Type returnType = getGenericGetterType(prop.getName());
// 如果是带有泛型参数的集合类型
if (returnType instanceof ParameterizedType)
// 获取泛型的实际类型列表
Type[] actualTypeArguments = ((ParameterizedType) returnType).getActualTypeArguments();
// 仅包含 1 个泛型参数
if (actualTypeArguments != null && actualTypeArguments.length == 1)
returnType = actualTypeArguments[0];
// 将泛型参数的类型作为实际的返回值类型
if (returnType instanceof Class)
type = (Class<?>) returnType;
else if (returnType instanceof ParameterizedType)
// 如果泛型参数还是带有泛型参数的类型,则直接返回泛型参数的实际类型,而不继续向下解析
// 如,解析字段 List<List<B>> b,解析表达式是 b[0],会解析出 List 是泛型类型,且具有唯一的泛型参数 List<B>
// 此时 List<B> 是 ParameterizedType,则不继续向下层解析,直接返回 List.class 作为 b[0] 的类型
type = (Class<?>) ((ParameterizedType) returnType).getRawType();
return type;
// 通过反射解析 Getter 方法的返回类型
private Type getGenericGetterType(String propertyName)
try
// 在 Reflector.getMethods 集合中获取 Getter 对应的 Invoker,Invoker 中包含字段的 Method 或 Field 对象
Invoker invoker = reflector.getGetInvoker(propertyName);
if (invoker instanceof MethodInvoker) // 如果是 Getter 方法
// 通过反射,直接获取 MethodInvoker.method 代表的 Method 对象,并返回该方法的返回值类型
Field _method = MethodInvoker.class.getDeclaredField("method");
_method.setAccessible(true);
Method method = (Method) _method.get(invoker);
// 解析方法的返回值类型,包含参数的实际类型和泛型参数的类型
return TypeParameterResolver.resolveReturnType(method, reflector.getType());
else if (invoker instanceof GetFieldInvoker) // 如果是对象的字段
// 通过反射,直接获取 GetFieldInvoker.field 代表的 Field 对象,并返回该字段的类型
Field _field = GetFieldInvoker.class.getDeclaredField("field");
_field.setAccessible(true);
Field field = (Field) _field.get(invoker);
// 解析字段类型,包含字段的实际类型和泛型参数的类型
return TypeParameterResolver.resolveFieldType(field, reflector.getType());
catch (NoSuchFieldException e)
catch (IllegalAccessException e)
return null;
举例来看,如要解析如下类的 MetaClass:
public static class B
private List<String> lists;
public static class A
private List<B> fieldA;
private List fieldB;
解析表达式:fieldA[0].lists
- 调用 MetaClass.hasGetter 方法时,首先通过 PropertyTokenizer 解析表达式,将表达式分割成(name=fieldA,index = 0, indexName=fieldA[0], children=lists);
- 依次进入方法:hasGetter -> metaClassForProperty -> getGetterType,在 getGetterType 中,字段 fieldA 具有下标且是集合类型,因此可以调用 getGenericGetterType ,得到的 returnType 为
List<B>
对应的 ParameterizedType 对象; - 继续执行 getGetterType 方法,其 returnType 为
List<B>
对应的 ParameterizedType,因此可以得出需要返回的 type =B.class
; - 继续向下层解析
B.lists
是否存在。
需要注意的是,对于字段 fieldB,在第 2 步解析出 returnType 为 List 对应的 Class 对象,此时 getGetterType 返回值是 java.util.List 类型。因此 getGetterType 在这里会有区别,当解析 List<B>
时,得到的返回值是 B.class
,当解析 List 时,得到的返回值是 java.util.List。
与 has 方法对应的还有 getSetterType、getGetterType 两个方法,它们和 hasSetter、hasGetter 方法的执行过程类似,都是调用 PropertyTokenizer 解析表达式,并使用 metaClassForProperty 解析子表达式。
public Class<?> getGetterType(String name)
// 解析表达式
PropertyTokenizer prop = new PropertyTokenizer(name);
if (prop.hasNext())
// 构建属性对应的 MetaClass
MetaClass metaProp = metaClassForProperty(prop);
// 处理属性对应的子表达式
return metaProp.getGetterType(prop.getChildren());
// 如果不存在子表达式,则直接返回当前属性对应的类型
return getGetterType(prop);
MetaClass 中除了上述方法外,还包含 getGetInvoker、getSetInvoker、getGetterNames、getSetterNames、hasDefaultConstructor 等方法,这些方法都只是封装了 Reflector 做的实现。
总结
MetaClass 可以解析表达式中属性对应的类型,能够用于解析字符串类型的表达式。但是目前来看,MetaClass 的 getGetterType 方法能够处理具有下标的表达式,但是 getSetterType 不能够正常处理下标,而且 getGetterType 仅能处理单个泛型参数的集合类,如 List<String>
,不能处理多个参数的集合类,如 MyList<A, B> extends ArrayList,getGetterType
无法处理集合类:MyList<String, Integer>
。
参考文档:《Mybatis 技术内幕》
本文的基本脉络参考自《Mybatis 技术内幕》,编写文章的原因是希望能够系统地学习 Mybatis 的源码,但是如果仅阅读源码或者仅从官方文档很难去系统地学习,因此希望参考现成的文档,按照文章的脉络逐步学习。
欢迎关注我的公众号:我的搬砖日记,我会定时分享自己的学习历程。
以上是关于Mybatis 源码学习-反射工具(MetaClass)的主要内容,如果未能解决你的问题,请参考以下文章
Mybatis 源码学习-反射工具(TypeParameterResolver)
Mybatis 源码学习-反射工具(ObjectWrapper & MetaObject)