本文探讨在须要了解一个开源项目时,如何快速的理清开源项目的代码逻辑!html
如下是我的认为行之有效的方法:java
本文以Mybatis为例来进行演示!sql
程序界有个老传统,学习新技术时都是从「Hello World」开始的!不管是学习新语言时,打印「Hello World」;仍是学习新框架时编写个demo!那为何这里的「跑起来」要打个引号呢?mybatis
实际上,当你想要阅读一个开源项目的源码时,绝大部分状况下,你已经可以使用这个开源项目了!因此这里的“跑起来”就不是写个「Hello World」,也不是能跑起来的程序了!而是能__在你的脑子里「跑起来」__!什么意思?app
Mybatis你会用了吧?那么请问Mybatis是如何执行的呢?仔细想一想,你可否用完整的语句把它描述出来?框架
这里是Mybatis的官方入门文章!你是如何看这篇文章的?读一遍就好了吗?仍是跟着文章跑一遍就够了吗?从这篇文章里你能得到多少信息?ide
咱们来理一下:学习
安装ui
从 XML 中构建 SqlSessionFactorythis
不使用 XML 构建 SqlSessionFactory
从 SqlSessionFactory 中获取 SqlSession
探究已映射的 SQL 语句
做用域(Scope)和生命周期
回答出了上面这些问题!你也就基本能在脑子里把Mybatis「跑起来」了!以后,你才能正真的开始阅读源码!
当你能把一个开源项目「跑起来」后,实际上你就有了对开源项目最初步的了解了!就像「__书的索引__」同样!基于这个索引,咱们一步步的进行拆解,来细化出下一层的结构和流程,期间可能须要深刻技术细节,考量实现,考虑是否有更好的实现方案!也就是说后面的三步并非线性的,而是__不断交替执行__的一个过程!最终就造成一个完整的源码执行流程!
继续经过Mybatis来演示(限于篇幅,我只演示一个大概流程)!咱们如今已经有了一个大概的流程了:
虽然说每一个点均可以往下细化,可是也分个轻重缓急!
很明显,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」!上面的流程也就细化成了:
SqlSession则是真正执行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中:
在这里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); }
这里干了什么?
@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); }
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!后面具体怎么实现的就自行查看吧!
如今咱们的流程大概是这样的一个过程:
SqlSession则是真正执行sql的类
SqlSession获取对应的Mapper实例
Mapper实例来执行相应的sql
如今咱们大概知道了:
那么,
这个问题列表能够很长,能够按我的须要去思考并尝试回答!可能最终这些问题已经和开源项目自己没有什么关系了!可是你思考后的收获要比看源码自己要多得多!
一轮结束后,能够再次进行:
不断的拆解->深刻->改进,最终你能__经过一个开源项目,学习到远比开源项目自己多得多的知识__!
最重要的是,你的流程是完整的。不管是最初的大体流程:
仍是到最终深刻的细枝末节,都是个完整的流程!
这样的好处是,你的时间能自由控制:
你均可以从以前的流程中快速进行下去!
而不像debug那样的方式,须要一会儿花费很长的时间去一步步的理流程,费时费力、收效很小,并且若是中断了就很难继续了!
本文经过梳理Mybatis源码的一个简单流程,来说述一个我的认为比较好的阅读源码的方式,并阐述此方法与传统debug方式相比的优点。
__公众号__:ivaneye