mybatis源码- 反射模块一(跟着MyBatis学反射):类级别信息的封装

反射就是在运行的状态中, 对于任意的一个实体类, 都能知道这个类的所有属性和方法。 并将其封装成一个个对象, 对通过这些对象来实现对应实体类的创建, 以及访问该类的方法和属性。

在我们创建了一个Java类之后, 编译出的.class文件在虚拟机中加载, 都会在JVM中创建一个Class对象,通过该对象来创建这个类的所有对象。

在 Mybatis 中, 有对应的反射模块, 本文就是探究 mybatis 是如何进行反射的。 mybatis 中的反射主要与JavaBean相关。

1 JavaBean 规范

JavaBean 具有如下特征:

  1. 所有的属性都是私有的(通过 getter和setter 访问)
  2. 拥有公有的无参数构造函数
  3. 提供 setter/getter
  4. 实现 Serializable 接口

2 Reflector和ReflectorFactory

mybatis 这种框架, 出于性能等方面的考虑, 必然不是等到使用的时候再去解析XML/再去解析反射类。

mybatis 为每一个类提供了反射器类(Reflector), 该类中存储了反射需要使用的类的元信息。

2.1 Reflector 属性

2.1.1 属性

Reflector

从类的属性中, 我们可以看出:

  1. 一个反射器(Reflector)对应着一个 Class对象。
  2. 记录了默认构造函数
  3. 其余的是属性及其setter|getter相关

setter&getter

对于一个属性(没错, 属性, 只有有 setter|getter 才能被称之为属性)

  1. 如果是可读的(有getter方法)则Reflector会将其及其方法处理后放入对应的集合中;
  2. 如果是可写的(有setter方法), 则Reflector会将其及其方法处理后放入对应的可写相关的集合中。
  3. 最后使用 Map<String, String> caseInsensitivePropertyMap 来记录所有的属性。

2.1.2 Invoker 接口

在存储方法的时候, Reflector 使用的是 Map<String, Invoker>。 而不是 Map<String, Method>

该接口的定义也很简单

/**
 * Invoker: 与方法的 invoke 相关
 * @author Clinton Begin
 */
public interface Invoker {
  // 调用方法
  Object invoke(Object target, Object[] args) throws IllegalAccessException, InvocationTargetException;

  // 获取类型
  Class<?> getType();
}

定义了方法的调用和获取类型。
Invoker层次

  • MethodInvoker: 方法的Invoker
  • GetFieldInvoker: 如果没有setter, 则使用该方法, 通过Filed类直接设置成员变量的值
  • SetFieldInvoker: 如果没有getter, 则使用该方法, 通过Field类直接读取成员变量的值

通过该封装之后, 本来需要声明 Map<String, Method>Map<String, Field> 表示的, 只需要使用 Map<String, Invoker> 即可表示。

2.2 Reflector 对外提供的方法

Reflector 对外提供的方法主要与构造函数和属性相关。

方法

构造函数:根据 Class 对象,设置 Reflector 相应的成员变量。

  public Reflector(Class<?> clazz) 

检查是否拥有了访问的权限:除了访问公有的变量, 还能访问 default , protected 和p rivate 变量

   public static boolean canControlMemberAccessible() 

查找是否有相应的属性

  public String   findPropertyName(String name)

获取默认的构造函数:说实话, 不清楚为啥要有这个方法, 不是可以通过 Class.newInstance() 进行创建吗?

  public Constructor<?> getDefaultConstructor()

getter相关的方法

  public String[] getGetablePropertyNames() // 获取所有的可读属性
  public Invoker getGetInvoker(String propertyName)// 获取所有可读属性的 Invoker
  public Class<?> getGetterType(String propertyName)// 获取对应属性的类型
  public boolean hasGetter(String propertyName)// 对应属性是否有相应的getter

对应的也有 setter 相关的方法

  public String[] getSetablePropertyNames() // 获取所有的可读属性
  public Invoker getSetInvoker(String propertyName)// 获取所有可读属性的 Invoker
  public Class<?> getSetterType(String propertyName)// 获取对应属性的类型
  public boolean hasSetter(String propertyName)// 对应属性是否有相应的 setter

2.3 Reflector 私有方法

2.3.1 方法相关

每个 Relector 对应缓存一个类的元反射信息, 通过 Map 进行缓存, 后续我们在使用时就不需要再去遍历查找, 可通过键查找即可。

因此, 就涉及到几个方法

获取方法签名: 根据函数名称、参数和返回值类型来取得签名, 保证方法的唯一性

 private String getSignature(Method method)

该方法获取每个方法的签名。 获取得到的签名

返回值类型#方法名:参数1,参数2,参数3…

很显然, 签名的目的是唯一性。 那使用语言本身的特性来保证唯一性是最好的:

  1. 方法名不一致, 则方法就不一致
  2. 返回值不一致或者不是其子类, 则方法不一致
  3. 参数数量, 参数类型顺序不一致方法也会不一样

因此, 以上的签名方式可以保证方法的唯一性。

获取类的所有方法

  private Method[] getClassMethods(Class<?> cls)

注意, 由于在获取方法时, 通过调用当前类及其除 Object 之外的所有父类的 getDeclaredMethods 方法及 getInterfaces() 方法, 因此, 其获取到的方法是该类及其父类的所有方法。

由此, 产生了一个问题, 如果子类重写了父类中的方法, 如果返回值相同, 则可以通过键重复来去掉。 但是, 如果方法返回值是父类相同实体方法返回值类型的子类, 则就会导致两个方法是同一个方法, 但是签名不同。 因此, 需要解决此类冲突。

解决方法冲突:getter方法冲突解决

  private void resolveGetterConflicts(Map<String, List<Method>> conflictingGetters)

到了此步骤的时候, 属性已经以 propName->List<Method> 的形式存在于内存中。 此时, 需要Map<String, List>转换为Map<String, Invoker>

该方法只需要明白下面两个条件就能很清晰了:

  1. 返回值类型不同

返回值类型不同, 则哪个方法的返回值是另一个方法返回值类型子类, 就把 propName 指向该方法包装成的 Invoker。 这个很好理解, 毕竟重新(override)重写时, 重写方法的返回值类型可以是被重写方法的子类。

  1. 返回值类型相同

按理来说不会出现这种情况, 因为在获取方法的时候已经使用签名去除掉了, 因此此时可以抛出异常。 但是有一种特殊的情况(这个卡了我一段时间):

public boolean isBool() {return true;}// 方法1
public boolean getBool() {return false;}// 方法2

以上情况在 JavaBean 规范中是允许的(但是, 其实方法2几乎大家都不会这么用)。 因此, mybatis 通过以下的方式进行了过滤。

    if (!boolean.class.equals(candidateType)) {
        throw new ReflectionException(
                "Illegal overloaded getter method with ambiguous type for property "
                        + propName + " in class " + winner.getDeclaringClass()
                        + ". This breaks the JavaBeans specification and can cause unpredictable results.");
    } else if (candidate.getName().startsWith("is")) {
        winner = candidate;
    }

也就是说, mybatis 承认的是方法1这种。 方法2的忽略掉。

解决方法冲突:setter方法冲突解决

刚开始, 我也想不明白, 为什么setter也会出现冲突。毕竟没有返回值类型, 也没有上面的 boolean 特殊情况。 最后发现了, 还有一个情况, 泛型!!

 void setId(T id);// 父类
  public void setId(String id) {// 子类
    // Do nothing
  }
  

显然, 遇到此类情况, 显然, 子类中的方法才是我们想要的:

    if (paramType1.isAssignableFrom(paramType2)) {
      return setter2;
    } else if (paramType2.isAssignableFrom(paramType1)) {
      return setter1;
    }

参数中, 父类方法泛型经过类型擦除后, 变成了 Object。 因此, 通过以上的方法, 那个是子类, 我们就获取哪一个。

3 ReflectorFactory

看名称, 工厂方法, 是为了创建和缓存 Reflector 的。
ReflectorFactory

只有三个方法: 是否缓存, 设置要不要缓存, 根据类型查找 Reflector 对象(找不到则创建)。

其与 Reflector 的关系
关系

mybatis 为我们提供了该方法的默认实现 DefaultReflectorFactory。 该类的实现很简单, 就是通过ConcurrentMap<Class<?>, Reflector>Reflector进行缓存。

4 MetaClass

MetaClass 通过与属性工具类的结合, 实现了对复杂表达式的解析,实现了获取指定描述信息的功能。

4.1 成员变量

MetaClass成员

MetaClass 有两个成员变量, 分别是 ReflectorFactoryReflector

4.2 创建

MetaClass 的构造函数是私有的。

  /**
   * MetaClass 构造函数
   */
  private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) {
    this.reflectorFactory = reflectorFactory;
    this.reflector = reflectorFactory.findForClass(type);
  }

但是, 其提供了两个创建的方法。 这两个方法也是通过该方法进行创建对象的。 该方法通过 Class 对象进行了 Reflector 对象的创建, 并赋值给成员变量。

  /**
   * 跟上面的是一样的
   */
  public static MetaClass forClass(Class<?> type, ReflectorFactory reflectorFactory) {
    return new MetaClass(type, reflectorFactory);
  }

通过属性进行创建

  /**
   * 通过属性名称, 获取属性的 MetaClass
   */
  public MetaClass metaClassForProperty(String name) {
    Class<?> propType = reflector.getGetterType(name);
    return MetaClass.forClass(propType, reflectorFactory);
  }

4.3 方法

该类中, 最重要的方法是:

    /**
     * 解析属性表达式
     * 会去寻找reflector中是否有对应的的属性
     * @param name
     * @param builder
     * @return
     */
  private StringBuilder buildProperty(String name, StringBuilder builder) {
    // 解析属性表达式
    PropertyTokenizer prop = new PropertyTokenizer(name);
    // 是否有子表达式
    if (prop.hasNext()) {
      // 查找对应的属性
      String propertyName = reflector.findPropertyName(prop.getName());
      if (propertyName != null) {
        // 追加属性名
        builder.append(propertyName);
        builder.append(".");
        // 创建对应的 MetaClass 对象
        MetaClass metaProp = metaClassForProperty(propertyName);
        // 解析子表达式, 递归
        metaProp.buildProperty(prop.getChildren(), builder);
      }
    } else {
      // 根据名称查找属性
      String propertyName = reflector.findPropertyName(name);
      if (propertyName != null) {
        builder.append(propertyName);
      }
    }
    return builder;
  }

理解了这个方法(递归, 该类中有很多类似的), 就可以很好的对这个类进行理解, 以查找(richType.richProperty)为例:

  1. 通过 PropertyTokenizer 对表达式进行解析, 得到当前的 name=richType, children=richProperty
  2. 从 reflector 中查找该 richType 属性
  3. 将 richType 添加到 builder 中
  4. 使用 metaClassForProperty 创建 richType 的 MetaClass
  5. 递归调用自身来处理子表达式

退出的条件就是没有子表达式。 这个就是为了, 我们类中有成员变量是类, 我们可以通过其找到他们的所有类及其属性。

注意, 在此过程中, ReflectorFactory 一直是同一个, 而其内部缓存了多个 Reflector 对象。

5 总结

类的关系:

关系

Reflector 实现了实体类元信息的封装, 但对类中的成员变量是类的情况没有进行处理。 而 MetaClass 通过 ReflectorFactory 类型的成员变量, 实现了实体类中成员变量是类情况的处理。从而结合属性工具类实现了对复杂表达式的处理。


一起学 mybatis

你想不想来学习 mybatis? 学习其使用和源码呢?那么, 在博客园关注我吧!!

我自己打算把这个源码系列更新完毕, 同时会更新相应的注释。快去我的github star 吧!!
项目

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Mybatis是一个轻量级的Java持久层开源框架,它封装了JDBC操作数据库的底层细节,提供了一个简单易用的数据库访问方式。 Mybatis源码分为核心模块和附加模块两部分,核心模块主要包括配置解析、SQL解析、SQL执行等功能,附加模块包括连接池、缓存、事务管理等功能。 在Mybatis源码中,配置解析是其中的关键部分。通过解析mybatis-config.xml配置文件,可以获取到数据库连接信息、映射器配置、插件配置等。在配置解析过程中,Mybatis会对配置文件进行校验,确保配置的正确性。 SQL解析是Mybatis的另一个重要功能。Mybatis通过解析Mapper接口中的注解或XML配置文件中的SQL语句,将SQL语句解析为ParameterMapping、BoundSql等对象,并将其封装成一个MappedStatement对象,供后续的SQL执行使用。 SQL执行是Mybatis的核心功能之一。在SQL执行阶段,Mybatis会根据MappedStatement中的信息,获取数据库连接,并执行对应的SQL语句。在执行过程中,Mybatis会通过TypeHandler对参数进行型转换,并使用ResultSetHandler将查询结果封装成Java对象。 除了核心模块Mybatis源码还包括了连接池、缓存、事务管理等附加模块的实现。连接池模块负责管理数据库连接的获取和释放,缓存模块负责缓存查询结果以提高性能,而事务管理模块则负责管理数据库的事务处理。 总之,Mybatis源码解析涉及多个关键模块的实现,包括配置解析、SQL解析、SQL执行、连接池、缓存、事务管理等。通过了解这些模块的实现原理,我们可以更好地理解和使用Mybatis框架。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值