如何快速阅读源码

本文探讨在需要了解一个开源项目时,如何快速的理清开源项目的代码逻辑!

以下是个人认为行之有效的方法:

  • 先「跑起来」
  • 自顶向下拆解
  • 深入细节
  • 延伸改进

本文以Mybatis为例来进行演示!

先“跑起来”

程序界有个老传统,学习新技术时都是从「Hello World」开始的!无论是学习新语言时,打印「Hello World」;还是学习新框架时编写个demo!那为什么这里的「跑起来」要打个引号呢?

实际上,当你想要阅读一个开源项目的源码时,绝大部分情况下,你已经能够使用这个开源项目了!所以这里的“跑起来”就不是写个「Hello World」,也不是能跑起来的程序了!而是能__在你的脑子里「跑起来」__!什么意思?

Mybatis你会用了吧?那么请问Mybatis是如何执行的呢?仔细想想,你能否用完整的语句把它描述出来?

这里是Mybatis的官方入门文章!你是如何看这篇文章的?读一遍就行了吗?还是跟着文章跑一遍就够了吗?从这篇文章里你能获得多少信息?

我们来理一下:

  • 安装

    • 如何在项目中引入Mybatis?
    • Mybatis的groupId是什么?artifactId又是什么?目前最新版本是多少?
  • 从 XML 中构建 SqlSessionFactory
    • SqlSessionFactoryBuilder可以通过xml或者Configuration来构建SqlSessionFactory,那是如何构建的呢?
    • xml配置了哪些信息?既然使用了xml,那肯定有xml解析,用什么方式解析的?
    • xml里的标签都是什么意思:configuration,environments,transactionManager,dataSource,mappers。以及这些标签的属性分别是什么意思?
    • SqlSessionFactory的作用是什么?
  • 不使用 XML 构建 SqlSessionFactory
    • BlogDataSourceFactory,DataSource,TransactionFactory,Environment,Configuration这些类的作用是什么?
    • *Mapper的作用是什么?
    • 为什么提供基于XML和Java的两种配置方式?这两种配置方式的优缺点是什么?
  • 从 SqlSessionFactory 中获取 SqlSession
    • SqlSession的作用是什么?
    • selectOne和getMapper的执行方式有什么区别?
  • 探究已映射的 SQL 语句
    • *Mapper.xml的配置是什么?
    • 命名空间,id的作用是什么?
    • *Mapper.xml是如何和*Mapper.java进行匹配的?
    • 匹配规则是什么?
    • 基于注解的映射配置如何使用?
    • 为什么提供基于XML和基于注解的两种映射配置?有什么优劣?
  • 作用域(Scope)和生命周期
    • SqlSessionFactoryBuilder应该在哪个作用域使用?为什么?
    • SqlSessionFactory应该在哪个作用域使用?为什么?
    • SqlSession应该在哪个作用域使用?为什么?
    • Mapper实例应该在哪个作用域使用?为什么?

回答出了上面这些问题!你也就基本能在脑子里把Mybatis「跑起来」了!之后,你才能正真的开始阅读源码!

当你能把一个开源项目「跑起来」后,实际上你就有了对开源项目最初步的了解了!就像「书的索引」一样!基于这个索引,我们一步步的进行拆解,来细化出下一层的结构和流程,期间可能需要深入技术细节,考量实现,考虑是否有更好的实现方案!也就是说后面的三步并不是线性的,而是__不断交替执行__的一个过程!最终就形成一个完整的源码执行流程!

自顶向下拆解

继续通过Mybatis来演示(限于篇幅,我只演示一个大概流程)!我们现在已经有了一个大概的流程了:

  • SqlSessionFactoryBuilder通过xml或者Configuration构建出SqlSessionFactory
  • 可以从SqlSessionFactory中获取SqlSession
  • SqlSession则是真正执行sql的类

虽说每个点都可以往下细化,但是也分个轻重缓急!

  • 我们是先了解怎么构建SqlSessionFactory呢?
  • 还是了解如何获取SqlSession呢?
  • 还是了解SqlSession如何执行sql的呢?

很明显,SqlSession去执行 sql才是Mybatis的核心!我们先从这个点入手!

首先,你当然得先下载Mybatis的源码了(请自行下载)!

我们直接去看SqlSession!它是个接口,里面有一堆执行sql的方法!

这里只列出了一部分方法:

public interface SqlSession extends Closeable {

  <T> T selectOne(String statement);

  <E> List<E> selectList(String statement);

  <K, V> Map<K, V> selectMap(String statement, String mapKey);

  <T> Cursor<T> selectCursor(String statement);

  void select(String statement, Object parameter, ResultHandler handler);

  int insert(String statement);

  int update(String statement);

  int delete(String statement);

  void commit();

  void rollback();

  List<BatchResult> flushStatements();

  <T> T getMapper(Class<T> type);

  ...
}

SqlSession就是通过这些方法来执行sql的!我们直接看我们常用的,也是Mybatis推荐的用法,就是基于Mapper的执行!也就是说「SqlSession通过Mapper来执行具体的sql」!上面的流程也就细化成了:

  • SqlSessionFactoryBuilder通过xml或者Configuration构建出SqlSessionFactory
  • 可以从SqlSessionFactory中获取SqlSession
  • SqlSession则是真正执行sql的类
    • SqlSession获取对应的Mapper实例
    • Mapper实例来执行相应的sql

那SqlSession是如何获取Mapper的呢?Mapper又是如何执行sql的呢?

深入细节

我们来看SqlSession的实现!SqlSession有两个实现类SqlSessionManager和DefaultSqlSession!通过IDE的引用功能可以查看两个类的使用情况。你会发现SqlSessionManager实际并没有使用!而DefaultSqlSession是通过DefaultSqlSessionFactory构建的!所以我们来看DefaultSqlSession是如何构建Mapper的!

@Override
public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
}

它直接委托给了Configuration的getMapper方法!

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
}

Configuration又委托给了MapperRegistry类的getMapper方法!

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory =
                    (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type
                     + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}

在MapperRegistry类的getMapper中:

  • 通过type从knownMappers中获取对应的MapperProxyFactory实例
  • 如果不存在则抛出异常
  • 如果存在则调用mapperProxyFactory.newInstance(sqlSession)创建对应的Mapper

在这里knowMappers是什么?MapperProxyFactory又是什么?mapperProxyFactory.newInstance(sqlSession)具体做了什么?

其实很简单,knowMappers是个Map,里面包含了class与对应的MapperProxyFactory的对应关系!MapperProxyFactory通过newInstance来构建对应的Mapper(实际上是Mapper的代理)!

快接近真相了,看mapperProxyFactory.newInstance(sqlSession)里的代码:

public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession,
                                        mapperInterface, methodCache);
    return newInstance(mapperProxy);
}

protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(),
                                      new Class[] { mapperInterface },
                                      mapperProxy);
}

这里干了什么?

  • 通过sqlSession,mapperInterface和methodCache构建了一个MapperProxy对象
  • 然后通过Java的动态代理,来生成了Mapper的代理类
  • 将Mapper方法的执行都委托给了MapperProxy去执行
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
}
  • 如果是Object里的方法则直接执行
  • 否则执行MapperMethod的execute方法
public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
          if (method.returnsOptional() &&
              (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = OptionalUtil.ofNullable(result);
          }
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    return result;
  }

最终实际还是委托给了sqlSession去执行具体的sql!后面具体怎么实现的就自行查看吧!

延伸改进

现在我们的流程大概是这样的一个过程:

  • SqlSessionFactoryBuilder通过xml或者Configuration构建出SqlSessionFactory
  • 可以从SqlSessionFactory中获取SqlSession
  • SqlSession则是真正执行sql的类
    • SqlSession获取对应的Mapper实例

      • DefaultSqlSession.getMapper
      • Configuration.getMapper
      • MapperRegistry.getMapper
      • mapperProxyFactory.newInstance(sqlSession)
      • 通过sqlSession,mapperInterface和methodCache构建了一个MapperProxy对象
      • 然后通过Java的动态代理,来生成了Mapper的代理类
    • Mapper实例来执行相应的sql
      • 将Mapper方法的执行都委托给了MapperProxy去执行
      • 如果是Object里的方法则直接执行
      • 否则执行MapperMethod的execute方法
      • 最终还是委托给sqlSession去执行sql

现在我们大概知道了:

  • 为什么Mapper是个接口了
  • Mybatis基于这个接口做了什么

那么,

  • 什么是动态代理(基础哦)?
  • 为什么使用动态代理来处理?
  • 基于动态代理有什么优点?又有什么缺点?
  • 除了动态代理,还有其它什么实现方式吗?比如说cglib?
  • 如果是其它语言的话,有没有什么好的实现方式呢?
  • ......

这个问题列表可以很长,可以按个人需要去思考并尝试回答!可能最终这些问题已经和开源项目本身没有什么关系了!但是你思考后的收获要比看源码本身要多得多!

再循环

一轮结束后,可以再次进行:

  • 自顶向下拆解
  • 深入细节
  • 延伸改进

不断的拆解->深入->改进,最终你能__通过一个开源项目,学习到远比开源项目本身多得多的知识__!

最重要的是,你的流程是完整的。无论是最初的大致流程:

  • SqlSessionFactoryBuilder通过xml或者Configuration构建出SqlSessionFactory
  • 可以从SqlSessionFactory中获取SqlSession
  • SqlSession则是真正执行sql的类

还是到最终深入的细枝末节,都是个完整的流程!

这样的好处是,你的时间能自由控制:

  • 你是要花个半天时间,了解大致流程
  • 还是花个几天理解细节流程
  • 还是花个几周,几个月来深入思考,不断延伸

    你都可以从之前的流程中快速进行下去!

而不像debug那样的方式,需要一下子花费很长的时间去一步步的理流程,费时费力、收效很小,而且如果中断了就很难继续了!

总结

本文通过梳理Mybatis源码的一个简单流程,来讲述一个个人认为比较好的阅读源码的方式,并阐述此方法与传统debug方式相比的优势。



公众号:ivaneye

原文地址:https://www.cnblogs.com/ivaneye/p/8732453.html

时间: 2024-10-25 18:00:15

如何快速阅读源码的相关文章

Spring源码解析——如何阅读源码

最近没什么实质性的工作,正好有点时间,就想学学别人的代码.也看过一点源码,算是有了点阅读的经验,于是下定决心看下spring这种大型的项目的源码,学学它的设计思想. 手码不易,转载请注明:xingoo 这篇博文你可以了解到: 1 Spring jar包以及源码使用 2 简单的spring运行示例 3 利用断点调试程序,如何快速的阅读程序[快捷键等的使用] 这次阅读的源码比较老了,是3.0.5版本的,由于正好手头有这个版本的源码,而且平时基本上也是用的这个版本Spring,因此后文的分析也都是针对

如何阅读源码,如何提高阅读源码的效率

就我自己阅读安卓源码的经验,本人研究过 settings,launcher,Sysupdate ,framework /services ,recovery的部分源码,并且成功修改google留下来的bug. 如果就是熟悉代码,不带问题的去看,看的会比较累,但是仍然是有方法可以借鉴的,其实这个方法,也是生活经验得来的,大家都会的. 就是从整体到局部,由总而分. 比如:你想了解recovery的代码,就应该先了解这个Recovery的运作流程,网上有很多人总结了,总概括图,先有个大概的了解: 第二

Linux 下阅读源码工具(Vim + ctags+Cscope)

0. 写在前面的废话 开发环境迁移到了Ubuntu下,所有windows下好用的工具都要找个替代品. windows下一直用 souce Insight 来阅读源码,需要在Ubuntu下找个替代品. 上网看了看,貌似Vim + Ctags + Cscope不错,安装来试试 o(∩∩)o 1. 安装 在我用的ubuntu中,这三个软件都不是自带的,需要手动安装,很简单用apt-get命令安装就好了 $ sudo apt-get install vim $ sudo apt-get install

大神如何阅读源码

1.腾讯IMWEB负责人说: 首先,搞清楚自己要读懂他们的原因和动机. 其次,可以先看下这些优秀框架或者库的设计文档和架构图,这样会让你宏观上对一些概念有些认识. 然后,从你最感兴趣的一个点,开始设置断点,跟进去看发生了哪些事情. 和架构设计哪一块是match的. 有人补充:最快,最易懂方法.断点单步调试. 如:jquery中 $.fn.show 源码是如何实现的. 自己写个 $('#test').show(),打上断点.单步调试.那么你可以看到jquery中每一步发生了什么事情.分析即可. 2

Spring源码解析——如何阅读源码(转)

最近没什么实质性的工作,正好有点时间,就想学学别人的代码.也看过一点源码,算是有了点阅读的经验,于是下定决心看下spring这种大型的项目的源码,学学它的设计思想. 手码不易,转载请注明:xingoo 这篇博文你可以了解到: 1 Spring jar包以及源码使用 2 简单的spring运行示例 3 利用断点调试程序,如何快速的阅读程序[快捷键等的使用] 这次阅读的源码比较老了,是3.0.5版本的,由于正好手头有这个版本的源码,而且平时基本上也是用的这个版本Spring,因此后文的分析也都是针对

有哪些你不知道的阅读源码的技巧

1. 先看官方文档和架构图 优秀的开源组件官方都会维护文档和架构图,这份架构图上或许有一些最重要的组件之间的关联关系.或许哪些功能的调用流程.或许有一些别的东西,但是相信我,这些东西一定都是从总体来描述这个项目的,这个一定是你要阅读源码时第一个要看的 2. 再看项目的组织结构 下载下来代码之后,不要急着开始.先看一下各个包名和包里的类名,对照着文档和类名先简单猜一下各个类的大致作用 3. 找到启动demo,把项目跑起来 阅读源码不仅仅是阅读,要让项目跑起来,去调试它,去观察和改变它的运行路线 4

【源码阅读】为什么需要阅读源码

为什么要看源码 我们在做项目的时候一般会遇到下面的问题: (1)不知道如何去设计.比如刚入职场时,来一个需求需做概要设计,不知如何下手,不得不去看当前系统类似需求是如何设计的,然后仿照去设计. (2)设计的时候,考虑问题不周全.相比职场新手,这类人对一个需求依靠自己的经验已经能够拿出一个概要设计,但是设计中经常会遗漏一些异常细节,比如使用多线程有界队列执行任务,遇到机器宕机了,如果队列里面的任务不存盘的话,那么机器下次启动的时候这些任务就丢失了. 对于这些问题,说到底主要还是因为经验不够,而经验

Linux 平台下阅读源码的工具链

原文:http://blog.jobbole.com/101322/ 前言 看源代码是一个程序员必须经历的事情,也是可以提升能力的一个捷径.个人认为: 要完全掌握一个软件的方法只有阅读源码. 在Windows下有sourceinsight这个源码阅读软件(虽然我没用过,但是网上评价还不错),由于我是个Linuxer,并不喜欢用Windows,所以自然是选择在Linux下阅读源码的工具了. 下面我将逐一介绍在Linux下阅读源码的工具. vim + ctags + cscope 源码阅读三剑客.v

使用 vim + ctags + cscope + taglist 阅读源码

转自:http://my.oschina.net/u/554995/blog/59927 最近,准备跟学长一起往 linux kernel 的门里瞧瞧里面的世界,虽然我们知道门就在那,但我们还得找到合适的角度才会看得更舒服,对吧^_^ . 阅读源码的工具有很多,而且如今的集成开发环境(IDE)也很强大,但对于经常使用vim编辑器的程序员来说,对vim的强大绝对是“不抛弃,不放弃”的,况且我们只要安装一些插件配合vim的工作一样能达到IDE的效果,好了,废话少说.浏览了很多有关的网页资源后,发现有