Mybatis 源码学习(五) —— reflection 包(一)

Mybatis 系列文章导航

祝大家 2021 年快乐!

 这篇文章卡了很久,一直不知道该怎么去写(其实就是懒),因为这个包是一个非常重要的基础包,所以希望能将本文章写的好一点,让大家能更好的理解 Mybatis 的设计思想。

  限于写者的水平问题,所以不一定能正确的理解背后的设计思想,如果有更好的理解,你可以写在评论区,谢谢。

阅前思考

 包名称为 reflection 那么可以猜测这是一个和反射有关的包。反射是 Java 的一个强大的功能,可以不通过 new 的方式生成对象。

reflection 的使用场景

 要知道在调用映射接口文件中方法的时候,如果有返回值的话,那么就需要将数据库中返回的数据转换为指定类型对象,所以这个时候肯定就需要使用到反射。因为生成目标类型的对象操作对象的属性都需要使用到反射。

reflection 包结构分析

  • reflection
    • factory (生成对象)
    • invoker (包装了类中的各个方法,eg. getXXX、setXXX)
    • property (对象属性相关工具类)
    • wrapper (对象的包装,可以直接操作对象的属性)

 可以看到,在 reflection 包下依然分了一些子包。使包结构更加的清晰,在写业务代码的时候我们也应该要注意这些,防止后面交由新人来维护的时候需要花大量的时间来进行功能梳理。

reflection 包分析

 由于 reflection 包下分了几个子包,所以根据我们之前的想法,根据外部依赖的程度来决定包的分析顺序。

property 子包

  • property
    • PropertyCopier.java (对象属性复制)
    • PropertyNamer.java (属性名称相关操作)
    • PropertyTokenizer.java (属性分词器)

 由于 PropertyCopier 和 PropertyNamer 的方法都很简单,所以就不在这里分析了,我们主要分析 PropertyTokenizer 的功能。

PropertyTokenizer 功能详解

 该类是用来进行分词的。例如对字符串 ‘users[0].name’ 进行分词。分词后就能得到如下结果。

PropertyTokenizer解析结果

 PropertyTokenizer 实现了 Iterator 接口,所以它是可以进行迭代的,因为 children 也需要进一步的分词。

invoker 子包

包结构图如下:
invoker-package

 invoker 子包中最重要的类就是 Invoker 类,其他的类都是它的实现类。

public interface Invoker {
  
  /**
   * 类似通过反射调用方法 method.invoke(Object, Object...)
   * 
   * @param target 要操作的对象
   * @param args 方法的请求参数
   * @return 方法的返回值
   */
  Object invoke(Object target, Object[] args) throws IllegalAccessException, InvocationTargetException;

  Class<?> getType();
}

 在前面提到了 reflection 包的作用之一就是操作对象的属性,而 Invoker 的作用就是将反射的方法进行封装。在 Mybatis,通过反射设置对象属性值首先是调用 get/set 方法,如果属性没有 get/set 方法,才是直接操作 Field 来设置属性值。由于有多种实现方式,所以就将实现方式进行抽象,提取出了公共的 Invoker 接口。

 通过浏览 Invoker 实现类的源码,就可以知道,MethodInvoker 就是用来调用 get/setXXX 方法的。GetInvoker 和 SetInvoker 就是当如果一个属性没有 get 方法时或没有 set 方法时,直接通过反射操作其 Field 来实现功能的。

 如果只是想实现功能的话,这样其实就够了,但是 Mybatis 是逻辑特别严谨的框架,如果在出现一些特殊的情况时,就希望能将异常提示出来。AmbiguousMethodInvoker 的作用就是如果发现有多个方法都是一个属性的 get/set 方法时,Mybatis 并不能判断使用哪一个方法,所以就将异常信息封装到该类中,在调用的时候才会出触发这个异常信息。至于为什么不是立马抛出提示重复,只能说各有优劣吧。

factory 子包

包结构图如下:
factory-package

 由于这个包中只有两个类,并且其中一个还是另一个的实现类,所以只要看了 ObjectFactory 中声明的方法就可以知道这包的具体作用。

public interface ObjectFactory {

  default void setProperties(Properties properties) {
  }

  /**
   * 使用默认的无参构造器构造器创建一个对象
   *
   * @param type 对象所属的 Class
   * @return T 类型的对象
   */
  <T> T create(Class<T> type);

  /**
   * 用指定的类构造器创建一个对象
   *
   * @param type                对象所属的 Class
   * @param constructorArgTypes 构造器的参数类型列表
   * @param constructorArgs     传入构造器的参数列表
   * @return T 类型的对象
   */
  <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs);

  /**
   * 传入一个 Class ,判断它是否是一个集合类型
   *
   * @param type 对象所属的 Class
   * @return Class 类型是否一个集合
   */
  <T> boolean isCollection(Class<T> type);

}

 方法不多,而且从方法名称就可以知道具体的实现是啥,就是将反射生成对象的步骤单独封装为一个工具类。而且既然只有一个默认的实现类,为啥还需要抽象一个接口出来呢?当然是希望能更加的灵活,因为我们可以在生成对象之前有一些特殊的处理。例如:更换生成对象的类型,在生成对象后,可以为对象的属性设置默认值,等等。

wrapper 子包

包结构图如下:
wrapper-package

 通过观察包结构,我们可以知道,整个包的核心是 ObjectWrapper 接口,其他的类要么使它的实现类要么就是它的工厂类。

public interface ObjectWrapper {
  /*
    get/set 方法
  */
  Object get(PropertyTokenizer prop);
  void set(PropertyTokenizer prop, Object value);
  
  /*
    格式化属性名称
  */
  String findProperty(String name, boolean useCamelCaseMapping);
  
  /*
    获取所有可以 get 或者 set 的属性名称
  */
  String[] getGetterNames();
  String[] getSetterNames();
  
  /*
    获取 get 得到的类型或者 set 需要传递的类型
  */
  Class<?> getSetterType(String name);
  Class<?> getGetterType(String name);
  
  /*
    属性是否可以 get/set
  */
  boolean hasSetter(String name);
  boolean hasGetter(String name);
  
  /*
    实例化属性
  */
  MetaObject instantiatePropertyValue(String name, PropertyTokenizer prop,
    ObjectFactory objectFactory);
  
  /*
    Collection 的相关操作
  */
  boolean isCollection();
  void add(Object element);
  <E> void addAll(List<E> element);

}

 ObjectWrapper 就是对象包装器,将对象的一些可操作的属性直接暴露出来,而不是在需要的时候写反射来操作属性或其他操作(如果是 Collection 或者 Map 则不通过反射)。将操作属性的重复代码给封装起来了,变成了一个更简单易用的接口。

 再观察 ObjectWrapper 的实现类,可以发现根据实现类的作用,有 BeanWrapper、CollectionWrapper、MapWrapper。根据不同对象,底层的实现方法也是不同的,并且子类并不会真正的实现所有方法,有些方法是不支持的,例如:CollectionWrapper 是对 Collection 实现类的封装,是没有 get/set 方法的。

 由于 CollectionWrapper 和 MapWrapper 的实现是比较简单的,所以只需要分析 BeanWrapper 就能明白背后的实现原理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: MyBatis 是一个开源的持久层框架,可以方便地将 SQL 语句和 Java 对象进行映射。如果您想要学习 MyBatis 源码,可以按照以下步骤进行: 1. 了解 MyBatis 的架构和设计原理。可以阅读官方文档和相关书籍,例如《MyBatis 技术内幕》。 2. 下载 MyBatis 的源代码,并导入到 IDE 中。MyBatis 使用 Maven 进行构建,您可以使用 IDE 的 Maven 插件来下载依赖项。 3. 查看 MyBatis 的源代码结构。MyBatis 的主要代码在 `mybatis-3` 模块中,括 `src/main/java` 和 `src/main/resources` 目录。其中,`src/main/java` 目录含了 MyBatis 的核心代码,例如 `org.apache.ibatis.session.SqlSession` 类;`src/main/resources` 目录含了 MyBatis 的配置文件和映射文件。 4. 阅读 MyBatis 的源代码。可以从 MyBatis 的入口处 `org.apache.ibatis.session.SqlSessionFactoryBuilder` 开始,深入了解 MyBatis 的初始化流程、SQL 语句的执行流程、映射文件的解析和缓存等。 5. 调试 MyBatis 的源代码。可以使用 IDE 的调试功能,对 MyBatis 进行单步调试,观察代码的执行流程,加深对 MyBatis 的理解。 6. 学习 MyBatis 的单元测试。MyBatis 的单元测试位于 `src/test/java` 目录中,可以通过单元测试来了解 MyBatis 的各个功能点的使用方法和测试用例。 7. 参与 MyBatis 的开发。如果您对 MyBatis 源码有深入的了解,并希望为 MyBatis 做出贡献,可以参与 MyBatis 的开发,贡献代码和文档,提交 issue 和 PR。MyBatis 的开发社区非常活跃,可以在官方网站和 GitHub 上找到相关信息。 希望这些步骤对您学习 MyBatis 源码有所帮助。 ### 回答2: MyBatis是一个开源的Java持久层框架,通过操作对象与数据库关系映射来提供数据持久化的功能。了解MyBatis源码学习和使用该框架的重要一步。 首先,MyBatis源码结构比较清晰,主要分为核心模块和附属模块。核心模块括XML配置解析、SQL语句解析、参数处理、数据库连接管理等功能的实现,是实现MyBatis基本功能的核心部分。附属模块括缓存、事务、插件等额外功能的实现,可以根据需要进行扩展和配置。 学习MyBatis源码可以从以下几个方面入手: 1. 配置文件解析:MyBatis通过XML配置文件来进行相关的配置,了解配置文件的解析过程可以帮助理解MyBatis的初始化过程和各项配置的作用。 2. SQL语句解析与执行:MyBatis将SQL语句封装成MappedStatement对象进行管理,了解MappedStatement的生成过程,以及SQL语句的解析、参数处理和执行过程,可以深入了解MyBatis的SQL执行原理。 3. 会话管理和事务处理:MyBatis采用SqlSessionFactory和SqlSession来管理数据库连接和事务,在MyBatis源码中可以学习到如何管理数据库连接池、事务的提交和回滚等核心功能的实现。 4. 缓存机制:MyBatis提供了一级缓存和二级缓存的功能,了解缓存的生成和更新过程,以及缓存的命中和失效原理,可以提高数据库查询性能。 总之,通过学习MyBatis源码,可以加深对该框架的理解,掌握其内部实现原理,有助于在使用时更加灵活和高效地进行开发。同时,也为以后解决一些特殊问题提供了更多的思路和方法。 ### 回答3: MyBatis是一个优秀的持久层框架,学习源码有助于理解其底层原理和设计思想。 首先,可以从MyBatis的入口开始学习,即SqlSessionFactoryBuilder类。该类负责解析配置文件、创建Configuration对象,并通过Configuration对象创建SqlSessionFactory实例。 接下来,可以学习Configuration类,该类负责管理整个MyBatis的配置信息。其中括了数据库连接信息、映射文件信息、缓存信息等。在该类内部,会调用XMLMapperBuilder类解析映射文件,在解析映射文件过程中,会创建MappedStatement对象,该对象表示一条SQL语句的映射信息。 学习MappedStatement对象可以了解MyBatis的SQL语句解析过程。该对象含了SQL语句的相关信息,括参数映射关系、返回结果映射关系等。在执行SQL语句时,会使用ParameterHandler类处理参数,通过ResultSetHandler类处理查询结果。 同时,学习到Executor接口及其实现类,可以了解MyBatis的执行过程。Executor负责执行SQL语句,其中括了写操作的update方法和读操作的query方法。在执行过程中,会通过StatementHandler类创建PreparedStatement对象,并通过ResultSetHandler类处理执行结果。 最后,还可以学习MyBatis的事务处理和缓存机制。Transaction接口及其实现类负责事务管理,通过JDBC的事务机制实现了事务的提交和回滚。而Cache接口及其实现类负责缓存查询结果,在查询时会先从缓存中查找结果。 总结来说,通过学习MyBatis源码可以深入理解其底层原理和设计思想。从SqlSessionFactory的创建开始,到Configuration的配置解析、MappedStatement的创建,再到Executor的执行过程和Transaction的事务管理,以及Cache的缓存机制,逐步掌握MyBatis的各个组件和它们之间的交互关系。这对于我们使用MyBatis开发项目,解决问题和优化性能都具有积极的意义。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值