前面幾篇文章都在詳細分析mapper的加載過程,但是始終沒有看到sql的解析過程,今天來詳細分析下。
解析sql的位置
前面分析到不管是通過註解還是通過xml方式生成mapper,最終都是調用MapperBuilderAssistant類的addMappedStatement方法,這個方法接受的其中一個SqlSource參數,SqlSource類中就是XML文件或者註解方法中映射語句的實現。
那麼SqlSource對象是在哪裡創建的呢?
在通過註解實現mapper的流程中是在MapperAnnotationBuilder類的parseStatement方法中對SqlSource進行初始化,初始化代碼如下圖:
通過xml文件實現mapper的流程中是在XMLStatementBuilder類的parseStatementNode方法中對SqlSource進行初始化,初始化代碼如下圖:
可以看到創建SqlSource對象都是通過LanguageDriver實現的,翻譯過來叫做語言驅動,它是一個接口,通過上面源碼可以看出來我們可以自己實現這個接口,並且可以指定使用哪個語言驅動。今天我們只關注mybatis自帶的一個實現XMLLanguageDriver。
XMLLanguageDriver介紹
XMLLanguageDriver類有重載了兩個createSqlSource方法,主要區別在於第二個參數script,從前面兩張源碼圖中可以知道接受XNode類型的script是在解析xml時使用,接受String類型的script是在解析註解時使用,今天只解析接受XNode類型的方法。
這個方法比較簡單只有兩步:初始化一個XMLScriptBuilder對象,執行XMLScriptBuilder對象的parseScriptNode方法。所以重點來到XMLScriptBuilder這個類。
XMLScriptBuilder詳解
XMLScriptBuilder類部分源碼如下圖:
XMLScriptBuilder的初始化比較簡單,要記住XNode context對應的是xml中的一個select、update等節點,在最後調用了initNodeHandlerMap方法設置了select、update等節點子節點對應的處理器。
接著是parseScriptNode方法,可以看到parseScriptNode方法調用了parseDynamicTags方法生成了一個MixedSqlNode對象,然後根據屬性isDynamic判斷創建對應的SqlSource對象。
所以最終要看parseScriptNode方法,同時可以判斷isDynamic這個屬性肯定也會在這個方法中發生變化,parseScriptNode方法的源碼如下圖:
parseDynamicTags方法解析節點下面所有子節點進行遍歷,如果節點是文本節點這解析裡面的內容生成SqlNode(這裡是TextSqlNode或者StaticTextSqlNode)對象放到contents集合中。
如果是腳本節點比如where、if等就調用初始化時保存的節點處理器,如上圖的ForEachHandler、IfHandler處理器,這些對象的處理方法handleNode的第一行代碼又在調用parseDynamicTags方法,就像是一種遞歸。所以我們可以得出xml中的where、if、foreach這些節點時可以彼此包含的,解析時再進行遞歸解析。
當然要想調用parseDynamicTags方法,這些對象都是屬於當前類XMLScriptBuilder的內部類。
大的方向梳理了我們再來看這個方法到底在幹什麼,首先這個方法會收集SqlNode對象放到contents集合中,最後把contents作為參數生成MixedSqlNode對象。在處理的過程中如果遇到if、foreach等節點還會把contents傳遞進去,從上面的圖中可以看到ForEachHandler、IfHandler處理器也會調用parseDynamicTags方法生成MixedSqlNode然後再生成對應的SqlNode放到contents中。
所以最終來到兩個關鍵類MixedSqlNode、SqlNode,當然SqlNode肯定有各種子類。
MixedSqlNode與SqlNode
那麼MixedSqlNode與SqlNode是什麼樣子的呢?又是如何組合的?具體源碼如下圖:
可以看到SqlNode是一個接口,而MixedSqlNode只是他的一種實現類,同時來貼出來了靜態文本處理的類和if節點對應的IfSqlNode類,還有一些其他比如WhereSqlNode、ForEachSqlNode等就不再列出來了。
每一種實現類的初始化都比較簡單,比如StaticTextSqlNode是保存一段文本,IfSqlNode保存了if節點的test屬性對應的值和從if節點下解析出來的MixedSqlNode節點。
而他們有一個由SqlNode規定的apply方法,這才是他們正真的作用所在,比如MixedSqlNode是遍歷所有節點執行對應的apply方法,StaticTextSqlNode就只是把對應sql拼接到後面,IfSqlNode是在進行判斷後調用MixedSqlNode去執行if節點下所有的節點。
至於參數DynamicContext後面在調用的時候會分析的。
現在回到XMLScriptBuilder的parseScriptNode方法,方法在執行了parseDynamicTags方法後根據isDynamic屬性初始化了SqlSource,對應類結構圖如下圖:
我們主要關注DynamicSqlSource這個類,可以看到它就兩個屬性configuration、rootSqlNode,分別是全局配置和剛剛分析的MixedSqlNode,也可以從他的getBoundSql方法中看到後面對rootSqlNode的使用,這個留著後面分析。
總結
在configuration中有一個map屬性mappedStatements,他保存著MappedStatement對象,每個MappedStatement對象對應一個sql的所有信息,而MappedStatement也有一個屬性SqlSource,通過SqlSource能夠獲取到對應的sql,而sql的解析時依靠SqlNode。
今天的重點就在於生成SqlNode的過程,它是通過XMLScriptBuilder類是處理xml中crud節點生成。
這裡有意思的是設計時是XMLScriptBuilder自帶處理節點的方法parseDynamicTags生成需要的MixedSqlNode,而在parseDynamicTags方法內部可能會調用內部類WhereHandler、IfHandler的handleNode方法生成對應的SqlNode,而在這些handleNode方法中第一步就是調用parseDynamicTags去生成MixedSqlNode,根據MixedSqlNode生成對應的SqlNode。通過這種遞歸實現了節點多層嵌套的解析。
第二個有意思的點在於SqlNode的框架設計,MixedSqlNode存儲一個SqlNode的集合,而具體的比如IfSqlNode又可以持有SqlNode,通過這樣實現,每種SqlNode都可以擁有所有各種的SqlNode功能,但是他們有擁有自己獨立的特點。
正是XMLScriptBuilder和SqlNode這種靈活的設計才可以使xml標籤有非常強大的支持,同時解析的時候又不至於太複雜。
Java程式設計師日常學習筆記,如理解有誤歡迎各位交流討論!