zoukankan      html  css  js  c++  java
  • 如何快速阅读源码

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

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

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

    本文以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

  • 相关阅读:
    Asp.net Treeview 客户端选中效果实现 (初级)
    MYSQL生成日历表,通常在做报表的时候需要用来生成一个临时表,用来左连接等。
    写了一个抽奖类,感觉还不错,可以适合各种变化
    将系统的内部类:HttpValueCollection 移到自己的系统中,使其能方便的解析id=1&name=张三&sex=男这样的字符串参数 querystring
    指定某个文件的创建 修改 访问时间
    Reqeust["keyname"] 的读取顺序
    pku1463 Strategic game
    pku1947 Rebuilding Roads
    pku1848 Tree
    pku1056 IMMEDIATE DECODABILITY
  • 原文地址:https://www.cnblogs.com/ivaneye/p/8732453.html
Copyright © 2011-2022 走看看