转载

MyBatis 的秘密(七)反射

MyBatis 作为一款 ORM 框架,主要通过 XML 定义 Object ,这就难免用到反射,虽然 JDK 自带的反射已经方便使用,但是 MyBatis 依然结合业务功能,将反射功能封装成一个更加易用的包,这个包就在 reflection 中。

在解析 MyBatis 的反色包之前,我们需要先通过需要看看 MyBatis 需要通过反射实现哪些功能。

在 MyBatis 中,可以方便的获取数组属性和对象属性。比如: student.names[0].firstName

因此, MyBatis 将反射获取值分为了两类:

POJO

容器类( Collection )通过下标进行取值,而 POJO 则是通过 getter()/setter() 进行取值

一个 POJO 对象拥有两类属性,对象属性以及类信息,在 MyBatis 中分别通过通过 MetaObject 和 MetaClass 对应上述信息。

明白了上述信息,就能理解下面 MyBatis 反射包中几个关键类的作用:

  • MetaClass : 保存了 POJO 的类相关信息,比如拥有的方法 hasGetter()/hasSetter()
  • MetaObject : 保存了 POJO 对象的相关的信息,比如通过 getter() 获取值,通过 setter() 设置值
  • ObjectWrapper : 用来区别不同的 POJO 获取属性的不同的方式,比如数组通过索引获取: nums[index] , Map 通过 key 获取, POJO 通过 getter() 获取
  • Relector : 这个类便是 MyBatis 的反射底层类,它简单的封装了 JDK 底层的反射,其他类都是调用此类进行反射操作。

接下来开始分析 MyBatis 的源码。

在 relection 包中,包含有 factory,invoker,property,wrapper 4个包,

  • factory : 工厂类,主要用来通过反射创建类
  • invoker : 对 POJO 的各种类的封装以及缓存,方便直接调用
  • property : 设置/获取 POJO 属性的帮助类
  • wrapper : POJO 的包装类,只要需要操作 POJO 的属性,即可通过此类包装后调用

在 MyBatis 中,主要通过 reflection.SystemMetaObject 作为简单工厂使用:

public final class SystemMetaObject {
    // 默认创建POJO的工厂类
    public static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
    // 创建BeanWrapper的工厂类
    public static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
    // null 对应的Object包装类
    public static final MetaObject NULL_META_OBJECT = MetaObject.forObject(NullObject.class, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());

    private SystemMetaObject() {
        // Prevent Instantiation of Static Class
    }
    private static class NullObject {}

    //简单工厂方法 用来创建MetaObject
    public static MetaObject forObject(Object object) {
        return MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
    }
}

可以看到,这里是直接返回的 MetaObject 。

MetaObject

MetaObject 主要有如下方法:

  • getSetterNames() : 获取该 class 所有的 setter name
  • getGetterNames() : 获取该 class 所有的 getter name
  • hasGetter() : 此 class 是否有该属性的 getter()
  • hasSetter() : 此 class 是否有该属性的 setter()
  • getValue() : 获取对应属性对应的值
  • setValue() : 设置此属性为对应的值

从这里可以看出来, MetaObject 基本包含了 MyBatis 需要使用的所有功能,可以说通过此类,基本连接了 XML 与 POJO 。

在 MetaObject 代码中,基本上都是调用的 objectWrapper 的方法,而 MetaObject 主要作用在于根据类型创建对应的 ObjectWrapper ,类似简单工厂,其主要逻辑在构造方法:

private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
    this.originalObject = object;
    this.objectFactory = objectFactory;
    this.objectWrapperFactory = objectWrapperFactory;
    this.reflectorFactory = reflectorFactory;
    //如果原本的对象本身就是ObjectWrapper ,那么直接赋值
    if (object instanceof ObjectWrapper) {
        this.objectWrapper = (ObjectWrapper) object;
    } 
    //如果wrapper工厂能够包装此`object`,则直接交给工厂处理
    else if (objectWrapperFactory.hasWrapperFor(object)) {
        this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
    } 
    //如果对象是Map 则直接使用`MapWrapper`处理
    else if (object instanceof Map) {
        this.objectWrapper = new MapWrapper(this, (Map) object);
    } 
    //如果对象属于collection,则使用`CollectionWrapper`处理
    else if (object instanceof Collection) {
        this.objectWrapper = new CollectionWrapper(this, (Collection) object);
    } 
    //否则,使用`BeanWrapper`包装此对象
    else {
        this.objectWrapper = new BeanWrapper(this, object);
    }
}

这里有个不合理的地方,查看 CollectionWrapper 的源码可以看到, CollectionWrapper 中全是抛出异常。也就是如果传入的是 List 之类的对象,调用任何方法都会抛出异常。

结合 MyBatis 整个源码可以发现, MyBatis 会处理 List , Array 等,然后将其包装到 StrictMap 中:

private Object wrapCollection(final Object object) {
    if (object instanceof Collection) {
      StrictMap<Object> map = new StrictMap<>();
      map.put("collection", object);
      if (object instanceof List) {
        map.put("list", object);
      }
      return map;
    } else if (object != null && object.getClass().isArray()) {
      StrictMap<Object> map = new StrictMap<>();
      map.put("array", object);
      return map;
    }
    return object;
  }

而 Map 中会有逻辑处理 Collection

在 MetaObject 中还有一个重要逻辑,前面说过,可能会解析类似 student.names[0].firstName 的属性,这样就涉及到一个循环处理,并且第一个是 POJO ,第二个是集合。

在 MyBatis 中,通过递归调用来循环解决:

public Object getValue(String name) {
    //创建属性解析
    PropertyTokenizer prop = new PropertyTokenizer(name);
    //如果属性中包含需要解决的子属性
    if (prop.hasNext()) {
        //再次创建 `MetaObject` 
        MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
        if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
            return null;
        } else {
            //递归调用`MetaObject#getValue() 
            return metaValue.getValue(prop.getChildren());
        }
    } else {
        //通过`ObjectWrapper`获取属性
        return objectWrapper.get(prop);
    }
}

其实这样写倒比较难看懂,也不知道为什么不直接使用 foreach ,而且 PropertyTokenizer 也实现了 Iterator 接口

getValue 之所以要放在这一层,便是因为能处理 student.names[0].firstName ,每一层递归都会新建一个 MetaObject 对象,然后根据子对象的类型再次通过对应类型的 ObjectWrapper 去获取值,直到获取到最终类型的值。

看完了 MetaObject ,接下来看 MetaObject 中所包装的 ObjectWrapper

ObjectWrapper 在 MyBatis 的实现主要分两类: BeanWrapper 和 MapWrapper

其中这两个 Wrapper 都继承自 BaseWrapper , BaseWrapper 中包含了一些通用的方法:

  • resolveCollection() : 通过 key 或者 getter() 获取 collection
  • getCollectionValue() : 通过下标,获取数组或容器的指定下标的值
  • setCollectionValue() : 通过下标,设置数组或者容器的值

ObjectWrapper

ObjectWrapper 接口所拥有的方法和 MetaObject 的方法差不多,只不过其实现类分几种。

MapWrapper#get()

@Override
  public Object get(PropertyTokenizer prop) {
    //判断表达式中是否包含类似list[1]符号
    if (prop.getIndex() != null) {
      //如果有,则先从Map中获取此集合
      Object collection = resolveCollection(prop, map);
      //从集合中获取指定下标的元素  
      return getCollectionValue(prop, collection);
    } else {
      //否则,通过使用map的key获取参数   
      return map.get(prop.getName());
    }
  }

BeanWrapper#get()

@Override
public Object get(PropertyTokenizer prop) {
    //判断表达式中是否包含类似list[1]符号
    if (prop.getIndex() != null) {
        //如果有,则先从现在的POJO中获取此集合
        Object collection = resolveCollection(prop, object);
        return getCollectionValue(prop, collection);
    } else {
        //否则,通过`getter`获取属性的值
        return getBeanProperty(prop, object);
    }
}

接下来看通过 getBeanProperty() 获取属性的具体方法:

BeanWrapper#getBeanProperty()

private Object getBeanProperty(PropertyTokenizer prop, Object object) {   
      //获取对应方法的Invoker  
      Invoker method = metaClass.getGetInvoker(prop.getName());
      //通过Invoker调用对应的方法
      return method.invoke(object, NO_ARGUMENTS);

  }

可以看到, BeanWrapper 底层有一部分是通过 MetaClass 获取的信息

接下来看 MetaCalss 的方法: MetaClass 可以类比 Java 中的 Class ,主要保存了 Class 的各种信息,按道理来说应该做一个缓存将 MetaClass 缓存起来,但是在 MyBatis 中缓存的是 Refector ,差别不大、

MetaClass

MetaClass 主要包含以下方法:

  • findProperty() : 查找属性,主要用于查找映射数据库的字段和 POJO 的属性
  • getSetterType() : 获取对应 setter 需要的字段类型
  • getGetterType() : 获取对应 getter 返回的字段类型
  • hasSetter() : 是否存在对应属性的 setter
  • hasGetter() : 是否存在对应属性的 getter
  • get*Invoker() : 获取对应方法的 Invoker ,获取后可以直接调用

可以看到,都是涉及到 class 的一些属性。

而 MetaClass 主要功能为分析 PropertyTokenizer 然后底层调用 Reflector 。

比如:

public boolean hasSetter(String name) {
    PropertyTokenizer prop = new PropertyTokenizer(name);
    //判断是否有子节点需要解析
    if (prop.hasNext()) {
       //首先判断本身是否有getter,只有本身存在getter,才能获取到子节点 
      if (reflector.hasSetter(prop.getName())) {
        //创建子节点`MetaClass`   
        MetaClass metaProp = metaClassForProperty(prop.getName());
        //递归处理子节点
        return metaProp.hasSetter(prop.getChildren());
      } else {
        return false;
      }
    } else {
      //通过reflector获取setter  
      return reflector.hasSetter(prop.getName());
    }
  }

Reflector

Reflector 主要通过简单工厂创建:

DefaultReflectorFactory#findForClass()

@Override
  public Reflector findForClass(Class<?> type) {
    if (classCacheEnabled) {
      // synchronized (type) removed see issue #461
      return reflectorMap.computeIfAbsent(type, Reflector::new);
    } else {
      return new Reflector(type);
    }
  }

主要是为了能够缓存已经解析的信息

Reflector 主要成员变量如下:

//类型信息
private final Class<?> type;
//可读的属性(有getter)
private final String[] readablePropertyNames;
//可写的属性(有setter)
private final String[] writablePropertyNames;
//set对应的Invoker
private final Map<String, Invoker> setMethods = new HashMap<>();
//get对应的Invoker
private final Map<String, Invoker> getMethods = new HashMap<>();
//属性setter需要的类型
private final Map<String, Class<?>> setTypes = new HashMap<>();
//属性getter返回的类型
private final Map<String, Class<?>> getTypes = new HashMap<>();
//默认构造方法
private Constructor<?> defaultConstructor;

对于 Reflector ,主要便是通过反射处理对应的 Class ,然后将获取的属性填充到上面的成员变量中。方法不一一分析,这里举例分析一个:

Reflector#getClassMethods()

private Method[] getClassMethods(Class<?> clazz) {
    Map<String, Method> uniqueMethods = new HashMap<>();
    Class<?> currentClass = clazz;
    //循环处理当前类,然后处理其父类,不处理`Object`
    while (currentClass != null && currentClass != Object.class) {
        //首先将当前类的所有方法加入到容器中
        //方法中主要为Method生成一个唯一的签名
        addUniqueMethods(uniqueMethods, currentClass.getDeclaredMethods());


        //将当前类对应实现的接口添加到容器中 
        Class<?>[] interfaces = currentClass.getInterfaces();
        for (Class<?> anInterface : interfaces) {
            addUniqueMethods(uniqueMethods, anInterface.getMethods());
        }
        //处理父类
        currentClass = currentClass.getSuperclass();
    }


    Collection<Method> methods = uniqueMethods.values();

    return methods.toArray(new Method[0]);
}

这个方法是比较简单的方法,但是 Reflector 的逻辑远远不是这么简单,这其中会涉及到很多细节的语法处理,比如在子类重写父类方法中,返回类型可能不同,这个时候应该以子类的为准等等。。

到这里, MyBatis 的反射类基本分析完毕,其实看起来这么复杂,但是只要理解了 MyBatis 对反射的需求,看起来就不难明白了。

在整个解析过程中,起着最大的作用的类是: PropertyTokenizer

public class PropertyTokenizer implements Iterator<PropertyTokenizer> {
  //以下属性以`student.names[0].firstName` 为例

  //student  
  private String name;
  //names  
  private final String indexedName;
  //0 
  private String index;
  //names
  private final String children;

  public PropertyTokenizer(String fullname) {
     //通过点判断是否有子表达式
    int delim = fullname.indexOf('.');
    if (delim > -1) {
      //当前表达式  
      name = fullname.substring(0, delim);
      //找到子表达式  
      children = fullname.substring(delim + 1);
    } else {
      name = fullname;
      children = null;
    }
    indexedName = name;
    //通过[找到索引取值表达式  
    delim = name.indexOf('[');
    if (delim > -1) {
      index = name.substring(delim + 1, name.length() - 1);
      name = name.substring(0, delim);
    }
  }
}

这个类就是分析 student.names[0].firstName 这个取值的关键类,并且这个类实现了 Iterator 接口,因此可以直接使用 foreach 便利,但是 MyBatis 的代码并没有这样做,而是使用了递归。

总结

这里再次复习一下, MyBatis 的反射使用入口是 SystemMetaObject ,它包含了整个反射包的基本配置。

在 MyBatis 的代码中,很少能看到静态类,静态工具方法等,基本都是通过对象“注入”到对应的其他对象中。这样当需要修改实现的时候,就能很好的维护。

在 MyBatis 中,首先使用 MetaObject , MetaObject 内部充当了一个简单工厂方法,用来分辨是处理 Map 还是处理 List 还是 Bean ,

ObjectWrapper 根据不同的实现,使用不同的方式获取底层的值,对于 Bean 来说,使用的是 MetaClass ,

MetaClass 包含了对应 Bean 的 Class 的各种元属性。其中 MetaClass 底层使用的 Reflector 对象

Reflector 对象便是真正的反射实现者,其内部根据传入的 Class ,生成了各种信息,并且由于 Class 是通用的,因此 MyBatis 使用 ReflectorFactory 将其缓存起来。

原文  http://dengchengchao.com/?p=1209
正文到此结束
Loading...