本文探討在須要瞭解一個開源項目時,如何快速的理清開源項目的代碼邏輯!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