1. 程式人生 > >Mybatis原始碼解讀-設計模式總結

Mybatis原始碼解讀-設計模式總結

雖然我們都知道有26個設計模式,但是大多停留在概念層面,真實開發中很少遇到,Mybatis原始碼中使用了大量的設計模式,閱讀原始碼並觀察設計模式在其中的應用,能夠更深入的理解設計模式。

Mybatis至少遇到了以下的設計模式的使用:

  1. Builder模式,例如SqlSessionFactoryBuilder、Environment;
  2. 工廠方法模式,例如SqlSessionFactory、TransactionFactoryTransactionFactoryLogFactoryObjectFactory、ReflectorFactory;
  3. 單例模式,例如ErrorContext和LogFactory;
  4. 代理模式,Mybatis實現的核心,比如MapperProxy、ConnectionLogger,用的jdk的動態代理;還有executor.loader包使用了cglib或者javassist達到延遲載入的效果;
  5. 組合模式,例如SqlNode和各個子類ChooseSqlNode等;
  6. 模板方法模式,例如BaseExecutor和SimpleExecutor,還有BaseTypeHandler和所有的子類例如IntegerTypeHandler;
  7. 介面卡模式,例如Log的Mybatis介面和它對jdbc、log4j等各種日誌框架的適配實現;
  8. 裝飾者模式,例如Cache包中的cache.decorators子包中等各個裝飾者的實現;
  9. 迭代器模式,例如迭代器模式PropertyTokenizer;

接下來挨個模式進行解讀,先介紹模式自身的知識,然後解讀在Mybatis中怎樣應用了該模式。

1、Builder模式

Builder模式的定義是“將一個複雜物件的構建與它的表示分離,使得同樣的構建過程可以建立不同的表示。”,它屬於建立類模式,一般來說,如果一個物件的構建比較複雜,超出了建構函式所能包含的範圍,就可以使用工廠模式和Builder模式,相對於工廠模式會產出一個完整的產品,Builder應用於更加複雜的物件的構建,甚至只會構建產品的一個部分。

建造模式:https://blog.csdn.net/qq_35807136/article/details/79086248

SqlSessionFactoryBuilder類根據不同的輸入引數來構建SqlSessionFactory這個工廠物件。


Environment通過靜態內部類的方式構建了Environment物件


2、工廠模式

在Mybatis中比如SqlSessionFactory使用的是工廠模式,該工廠沒有那麼複雜的邏輯,是一個簡單工廠模式。

簡單工廠模式(Simple Factory Pattern):又稱為靜態工廠方法(Static Factory Method)模式,它屬於類建立型模式。在簡單工廠模式中,可以根據引數的不同返回不同類的例項。簡單工廠模式專門定義一個類來負責建立其他類的例項,被建立的例項通常都具有共同的父類。

工廠模式:https://blog.csdn.net/qq_35807136/article/details/79908422

SqlSessionFactory根據不同得引數構建SqlSessionManager、和SqlSession兩個物件



TransactionFactory根據不同引數構建ManagedTransaction、和JdbcTransaction兩個物件



LogFactory得Log變數的的型別是Constructor<? extends Log>,也就是說該工廠生產的不只是一個產品,而是具有Log公共介面的一系列產品,比如Log4jImpl、Slf4jImpl等很多具體的Log。


3、單例模式

單例模式(Singleton Pattern):單例模式確保某一個類只有一個例項,而且自行例項化並向整個系統提供這個例項,這個類稱為單例類,它提供全域性訪問的方法。

單例模式的要點有三個:一是某個類只能有一個例項;二是它必須自行建立這個例項;三是它必須自行向整個系統提供這個例項。單例模式是一種物件建立型模式。單例模式又名單件模式或單態模式。

單利模式:https://blog.csdn.net/qq_35807136/article/details/79086248

ErrorContex通過餓漢式單利載入context


建構函式是private修飾,具有一個static的區域性instance變數和一個獲取instance變數的方法,在獲取例項的方法中,先判斷是否為空如果是的話就先建立,然後返回構造好的物件。

只是這裡有個有趣的地方是,LOCAL的靜態例項變數使用了ThreadLocal修飾,也就是說它屬於每個執行緒各自的資料,而在instance()方法中,先獲取本執行緒的該例項,如果沒有就建立該執行緒獨有的ErrorContext。

4、代理模式

代理模式可以認為是Mybatis的核心使用的模式,正是由於這個模式,我們只需要編寫Mapper.java介面,不需要實現,由Mybatis後臺幫我們完成具體SQL的執行。

代理模式(Proxy Pattern) :給某一個物件提供一個代 理,並由代理物件控制對原物件的引用。代理模式的英 文叫做Proxy或Surrogate,它是一種物件結構型模式。

代理模式:https://blog.csdn.net/qq_35807136/article/details/79912938

當我們使用Configuration的getMapper方法時,會呼叫mapperRegistry.getMapper方法,而該方法又會呼叫mapperProxyFactory.newInstance(sqlSession)來生成一個具體的代理:


在這裡,先通過T newInstance(SqlSession sqlSession)方法會得到一個MapperProxy物件,然後呼叫T newInstance(MapperProxy<T> mapperProxy)生成代理物件然後返回。

而檢視MapperProxy的程式碼,可以看到如下內容:

非常典型的,該MapperProxy類實現了InvocationHandler介面,並且實現了該介面的invoke方法。

通過這種方式,我們只需要編寫Mapper.java介面類,當真正執行一個Mapper介面的時候,就會轉發給MapperProxy.invoke方法,而該方法則會呼叫後續的sqlSession.cud>executor.execute>prepareStatement等一系列方法,完成SQL的執行和返回。

自定義得外掛也使用了代理模式


Plugin就是mybatis封裝得代理類

將外掛代理物件儲存到


配合責任鏈模式進行底層四大物件得實現;

5、組合模式

組合模式組合多個物件形成樹形結構以表示“整體-部分”的結構層次。

組合模式對單個物件(葉子物件)和組合物件(組合物件)具有一致性,它將物件組織到樹結構中,可以用來描述整體與部分的關係。同時它也模糊了簡單元素(葉子物件)和複雜元素(容器物件)的概念,使得客戶能夠像處理簡單元素一樣來處理複雜元素,從而使客戶程式能夠與複雜元素的內部結構解耦。

合成模式:https://blog.csdn.net/qq_35807136/article/details/79956906

Mybatis支援動態SQL的強大功能,比如下面的這個SQL


在這裡面使用到了trim、if等動態元素,可以根據條件來生成不同情況下的SQL;

在DynamicSqlSource.getBoundSql方法裡,呼叫了rootSqlNode.apply(context)方法,apply方法是所有的動態節點都實現的介面:


對於實現該SqlSource介面的所有節點,就是整個組合模式樹的各個節點:


組合模式的簡單之處在於,所有的子節點都是同一類節點,可以遞迴的向下執行,比如對於TextSqlNode,因為它是最底層的葉子節點,所以直接將對應的內容append到SQL語句中:


但是對於IfSqlNode,就需要先做判斷,如果判斷通過,仍然會呼叫子元素的SqlNode,即contents.apply方法,實現遞迴的解析。


6、模板方法模式

模板方法模式是所有模式中最為常見的幾個模式之一,是基於繼承的程式碼複用的基本技術。

模板方法模式需要開發抽象類和具體子類的設計師之間的協作。一個設計師負責給出一個演算法的輪廓和骨架,另一些設計師則負責給出這個演算法的各個邏輯步驟。代表這些具體邏輯步驟的方法稱做基本方法(primitive method);而將這些基本方法彙總起來的方法叫做模板方法(template method),這個設計模式的名字就是從此而來。

模板方法模式:https://blog.csdn.net/qq_35807136/article/details/79913211

在Mybatis中,sqlSession的SQL執行,都是委託給Executor實現的,Executor包含以下結構:


其中的BaseExecutor就採用了模板方法模式,它實現了大部分的SQL執行邏輯,然後把以下幾個方法交給子類定製化完成:

該模板方法類有幾個子類的具體實現,使用了不同的策略:

  • 簡單SimpleExecutor:每執行一次update或select,就開啟一個Statement物件,用完立刻關閉Statement物件。(可以是Statement或PrepareStatement物件)
  • 重用ReuseExecutor:執行update或select,以sql作為key查詢Statement物件,存在就使用,不存在就建立,用完後,不關閉Statement物件,而是放置於Map<String, Statement>內,供下一次使用。(可以是Statement或PrepareStatement物件)
  • 批量BatchExecutor:執行update(沒有select,JDBC批處理不支援select),將所有sql都新增到批處理中(addBatch()),等待統一執行(executeBatch()),它快取了多個Statement物件,每個Statement物件都是addBatch()完畢後,等待逐一執行executeBatch()批處理的;BatchExecutor相當於維護了多個桶,每個桶裡都裝了很多屬於自己的SQL,就像蘋果藍裡裝了很多蘋果,番茄藍裡裝了很多番茄,最後,再統一倒進倉庫。(可以是Statement或PrepareStatement物件)

比如在SimpleExecutor中這樣實現update方法:

7、介面卡模式

介面卡模式(Adapter Pattern) :將一個介面轉換成客戶希望的另一個介面,介面卡模式使介面不相容的那些類可以一起工作,其別名為包裝器(Wrapper)。介面卡模式既可以作為類結構型模式,也可以作為物件結構型模式。

介面卡模式:https://blog.csdn.net/qq_35807136/article/details/79913509

在Mybatsi的logging包中,有一個Log介面:

該介面定義了Mybatis直接使用的日誌方法,而Log介面具體由誰來實現呢?Mybatis提供了多種日誌框架的實現,這些實現都匹配這個Log介面所定義的介面方法,最終實現了所有外部日誌框架到Mybatis日誌包的適配:


比如對於Log4jImpl的實現來說,該實現持有了org.apache.log4j.Logger的例項,然後所有的日誌方法,均委託該例項來實現。


8、裝飾者模式

裝飾模式(Decorator Pattern) :動態地給一個物件增加一些額外的職責(Responsibility),就增加物件功能來說,裝飾模式比生成子類實現更為靈活。其別名也可以稱為包裝器(Wrapper),與介面卡模式的別名相同,但它們適用於不同的場合。根據翻譯的不同,裝飾模式也有人稱之為“油漆工模式”,它是一種物件結構型模式

裝飾者模式:https://blog.csdn.net/qq_35807136/article/details/79913844

在mybatis中,快取的功能由根介面Cache(org.apache.ibatis.cache.Cache)定義。整個體系採用裝飾器設計模式,資料儲存和快取的基本功能由PerpetualCache(org.apache.ibatis.cache.impl.PerpetualCache)永久快取實現,然後通過一系列的裝飾器來對PerpetualCache永久快取進行快取策略等方便的控制。

用於裝飾PerpetualCache的標準裝飾器共有8個(全部在org.apache.ibatis.cache.decorators包中):

  1. FifoCache:先進先出演算法,快取回收策略
  2. LoggingCache:輸出快取命中的日誌資訊
  3. LruCache:最近最少使用演算法,快取回收策略
  4. ScheduledCache:排程快取,負責定時清空快取
  5. SerializedCache:快取序列化和反序列化儲存
  6. SoftCache:基於軟引用實現的快取管理策略
  7. SynchronizedCache:同步的快取裝飾器,用於防止多執行緒併發訪問
  8. WeakCache:基於弱引用實現的快取管理策略

另外,還有一個特殊的裝飾器TransactionalCache:事務性的快取

正如大多數持久層框架一樣,mybatis快取同樣分為一級快取和二級快取

  • 一級快取,又叫本地快取,是PerpetualCache型別的永久快取,儲存在執行器中(BaseExecutor),而執行器又在SqlSession(DefaultSqlSession)中,所以一級快取的生命週期與SqlSession是相同的。
  • 二級快取,又叫自定義快取,實現了Cache介面的類都可以作為二級快取,所以可配置如encache等的第三方快取。二級快取以namespace名稱空間為其唯一標識,被儲存在Configuration核心配置物件中。

二級快取物件的預設型別為PerpetualCache,如果配置的快取是預設型別,則mybatis會根據配置自動追加一系列裝飾器。

Cache物件之間的引用順序為:

SynchronizedCache–>LoggingCache–>SerializedCache–>ScheduledCache–>LruCache–>PerpetualCache

9、迭代器模式

迭代器(Iterator)模式,又叫做遊標(Cursor)模式。GOF給出的定義為:提供一種方法訪問一個容器(container)物件中各個元素,而又不需暴露該物件的內部細節。

迭代器模式:https://blog.csdn.net/qq_35807136/article/details/79913844

比如Mybatis的PropertyTokenizer是property包中的重量級類,該類會被reflection包中其他的類頻繁的引用到。這個類實現了Iterator介面,在使用時經常被用到的是Iterator介面中的hasNext這個函式。


可以看到,這個類傳入一個字串到建構函式,然後提供了iterator方法對解析後的子串進行遍歷,是一個很常用的方法類。