转载

MyBatis 的秘密(七)反射

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

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

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

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

POJO

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

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

明白了上述信息,就能理解下面 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 需要使用的所有功能,可以说通过此类,基本连接了 XMLPOJO

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

ObjectWrapperMyBatis 的实现主要分两类: BeanWrapperMapWrapper

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

  • 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 中,首先使用 MetaObjectMetaObject 内部充当了一个简单工厂方法,用来分辨是处理 Map 还是处理 List 还是 Bean

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

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

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

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