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

  1. 调用 MetaClass.hasGetter 方法时,首先通过 PropertyTokenizer 解析表达式,将表达式分割成(name=fieldA,index = 0, indexName=fieldA[0], children=lists);
  2. 依次进入方法:hasGetter -> metaClassForProperty -> getGetterType,在 getGetterType 中,字段 fieldA 具有下标且是集合类型,因此可以调用 getGenericGetterType ,得到的 returnType 为 List<B> 对应的 ParameterizedType 对象;
  3. 继续执行 getGetterType 方法,其 returnType 为 List<B> 对应的 ParameterizedType,因此可以得出需要返回的 type = B.class
  4. 继续向下层解析 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 源码学习-反射工具(Reflector)

Mybatis 源码学习-反射工具(MetaClass)

Mybatis 源码学习-反射工具(TypeParameterResolver)

Mybatis 源码学习-反射工具(ObjectWrapper & MetaObject)

Mybatis 源码学习-反射工具(ObjectWrapper & MetaObject)

Mybatis 源码学习-类型转换(TypeHandler)