
從 Spring 與 Mybatis 源碼學習創建型設計模式單例、工廠與建造者的工業級實踐【免費下載鏈接】source-code-hunter 從源碼層面剖析挖掘互聯網行業主流技術的底層實現原理為廣大開發者 “提升技術深度” 提供便利。目前開放 Spring 全家桶Mybatis、Netty、Dubbo 框架及 Redis、Tomcat 中間件等項目地址: https://gitcode.com/GitHub_Trending/so/source-code-hunter本篇技術指南以 docs/LearningExperience/DesignPattern/從Spring及Mybatis框架源碼中學習設計模式(創建型).md.md) 為主體脈絡系統梳理創建型設計模式單例模式、簡單工廠模式、工廠方法模式、抽象工廠模式、建造者模式的概念、經典實現與踩坑點并結合當前倉庫對 Spring 與 Mybatis 源碼的分析深入到DefaultSingletonBeanRegistry單例注冊表、MybatisDataSourceFactory數據源工廠族、BaseBuilder建造者體系等真實實現中幫助你既看懂模式是什么也看透大神的代碼怎么用。適用場景希望在 Spring 全家桶、Mybatis、JDK 源碼中尋找設計模式最佳實踐并將之遷移到個人項目的開發者。讀完本篇你將能手寫線程安全且抗反射/序列化攻擊的單例理解 Spring 單例 bean 的三級緩存與雙重加鎖機制分清三種工廠模式各自的使用時機理解 Mybatis 初始化中BaseBuilder建造者體系的協作方式。一、單例模式確保全局只有一個實例1.1 個人理解單例模式的核心訴求是確保某個類只有一個實例并提供該實例的獲取方法。無論是框架、JDK 還是實際項目開發單例的應用都非常普遍。從實踐經驗看絕大多數場景使用餓漢式或枚舉來實現單例即可懶漢式也有應用但通過雙檢鎖機制Double-Checked LockingDCL實現的線程安全單例在真實代碼中反而很少見因為它的正確性高度依賴對 JVM 內存模型的深入理解。1.2 雙檢鎖懶漢式的坑volatile 與指令重排最簡單的實現方式是一個私有構造函數 一個私有靜態變量 一個公共靜態方法。懶漢式、餓漢式的簡單寫法這里不再贅述重點強調雙檢鎖懶漢式實現的坑/** * 雙檢鎖懶漢式實現線程安全的單例 * 關鍵詞JVM指令重排、volatile、反射攻擊 */ public class Singleton3 { /** * instance new Singleton3(); 在JVM中實際分三步執行 * 1、分配內存空間 * 2、初始化對象 * 3、將instance指向分配的內存地址。 * 但JVM具有指令重排的特性實際的執行順序可能會是1、3、2導致多線程情況下出問題。 * 使用volatile修飾instance變量可以避免上述的指令重排 */ private volatile static Singleton3 instance; private Singleton3() { } public static Singleton3 getInstance() { if (instance null) { synchronized (Singleton3.class) { if (instance null) { instance new Singleton3(); } } } return instance; } }這里有兩個容易困惑的點務必理解清楚外層判空的意義絕大多數線程進來時instance已非 null直接返回避免每次都進入synchronized代碼塊競爭鎖這是雙檢性能上的核心價值為什么必須加 volatile第一個線程執行new Singleton3()時如果 JVM 把指向內存地址第 3 步重排到了初始化對象第 2 步之前那么其他線程看到instance ! null后直接return instance拿到的就是一個尚未初始化完成的對象。volatile通過內存屏障禁止這種重排保證初始化完成先行發生于引用發布。1.3 為什么枚舉是單例的最佳實踐上述Singleton3如果實現了Serializable接口每次序列化都會創建一個新對象要保證單例必須把所有字段聲明為transient并額外提供readResolve()方法。反射攻擊則可以通過setAccessible(true)將私有構造方法公共化進而繞過檢查實例化出第二個對象除非在構造方法里手工編寫禁止實例化第二個對象的防御代碼。枚舉實現的單例在面對復雜的序列化及反射攻擊時依然能夠保持自己的單例狀態因此被認為是單例的最佳實踐。JDK 對枚舉的序列化有專門機制Enum實現了Serializable反序列化時通過valueOf按名稱返回已有實例且反射 API 明確禁止通過Constructor#newInstance創建枚舉實例。Mybatis 在定義 SQL 命令類型時就用到了枚舉見org.apache.ibatis.mapping.SqlCommandTypepackage org.apache.ibatis.mapping; /** * author Clinton Begin */ public enum SqlCommandType { UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH; }這個枚舉在 Mybatis 初始化解析 SQL 節點時被直接使用XMLStatementBuilder.parseStatementNode()中通過SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH))根據 SQL 節點的名稱確定其命令類型進而決定flushCache、useCache等默認行為詳細解析見 docs/Mybatis/核心處理層/1、MyBatis初始化.md。1.4 JDK 中的范例Runtime 與 Desktop餓漢式范例 —— java.lang.RuntimeJDK 1.0 起public class Runtime { /** 很明顯這里用的是餓漢式實現單例 */ private static Runtime currentRuntime new Runtime(); public static Runtime getRuntime() { return currentRuntime; } /** Dont let anyone else instantiate this class */ private Runtime() {} }每個 Java 應用程序都有一個單例的Runtime對象通過getRuntime()獲得。類加載階段即完成初始化天然線程安全代價是無論是否使用都會創建實例。懶漢式范例 —— java.awt.Desktoppublic class Desktop { /** * Suppresses default constructor for noninstantiability. */ private Desktop() { peer Toolkit.getDefaultToolkit().createDesktopPeer(this); } /** * 由于對象較大這里使用了懶漢式延遲加載 * 方式比較簡單直接把鎖加在方法上。 */ public static synchronized Desktop getDesktop() { if (GraphicsEnvironment.isHeadless()) throw new HeadlessException(); if (!Desktop.isDesktopSupported()) { throw new UnsupportedOperationException(Desktop API is not supported on the current platform); } sun.awt.AppContext context sun.awt.AppContext.getAppContext(); Desktop desktop (Desktop)context.get(Desktop.class); if (desktop null) { desktop new Desktop(); context.put(Desktop.class, desktop); } return desktop; } }注意這里synchronized直接加在方法上粗粒度加鎖對象存于AppContext這個線程局部上下文中。可見 JDK 官方也未在單例上使用雙檢鎖。1.5 Spring 的單例 bean 是如何實現的源碼級剖析Spring 實現單例 bean不是用我們上面手寫的雙檢鎖而是ConcurrentHashMap 注冊表 synchronized 同步機制的組合。核心是AbstractBeanFactory.doGetBean()與DefaultSingletonBeanRegistry.getSingleton()兩者在本倉庫均有專門解析見 docs/Spring/clazz/Spring-DefaultSingletonBeanRegistry.md 與 docs/Spring/IoC/4、依賴注入(DI).md.md)。第一步doGetBean() 中判斷是否為單例并用 ObjectFactory 匿名內部類延遲創建public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory { /** * 真正實現向IoC容器獲取Bean的功能也是觸發依賴注入(DI)功能的地方 */ SuppressWarnings(unchecked) protected T T doGetBean(final String name, final ClassT requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException { ...... //創建單例模式bean的實例對象 if (mbd.isSingleton()) { //這里使用了一個匿名內部類創建Bean實例對象并且注冊給所依賴的對象 sharedInstance getSingleton(beanName, new ObjectFactoryObject() { public Object getObject() throws BeansException { try { /** * 創建一個指定的Bean實例對象如果有父級繼承則合并子類和父類的定義 * 走子類中的實現 */ return createBean(beanName, mbd, args); } catch (BeansException ex) { destroySingleton(beanName); throw ex; } } }); //獲取給定Bean的實例對象 bean getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } } }第二步getSingleton() 中先查緩存未命中則創建并注冊/** * 默認的單例bean注冊器 */ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry { /** 單例的bean實例的緩存 */ private final MapString, Object singletonObjects new ConcurrentHashMapString, Object(64); /** * 返回給定beanName的已經注冊的單例bean如果沒有注冊則注冊并返回 */ public Object getSingleton(String beanName, ObjectFactory? singletonFactory) { Assert.notNull(beanName, beanName must not be null); // 加鎖保證單例bean在多線程環境下不會創建多個 synchronized (this.singletonObjects) { // 先從緩存中取有就直接返回沒有就創建、注冊到singletonObjects、返回 Object singletonObject this.singletonObjects.get(beanName); if (singletonObject null) { if (this.singletonsCurrentlyInDestruction) { throw new BeanCreationNotAllowedException(beanName, Singleton bean creation not allowed while the singletons of this factory are in destruction (Do not request a bean from a BeanFactory in a destroy method implementation!)); } beforeSingletonCreation(beanName); boolean recordSuppressedExceptions (this.suppressedExceptions null); if (recordSuppressedExceptions) { this.suppressedExceptions new LinkedHashSetException(); } try { singletonObject singletonFactory.getObject(); } catch (BeanCreationException ex) { if (recordSuppressedExceptions) { for (Exception suppressedException : this.suppressedExceptions) { ex.addRelatedCause(suppressedException); } } throw ex; } finally { if (recordSuppressedExceptions) { this.suppressedExceptions null; } afterSingletonCreation(beanName); } // 注冊到單例bean的緩存 addSingleton(beanName, singletonObject); } return (singletonObject ! NULL_OBJECT ? singletonObject : null); } } }與手寫單例的對照關系一目了然手寫單例要素Spring 的對應實現私有靜態變量instancesingletonObjects注冊表ConcurrentHashMapsynchronized代碼塊synchronized (this.singletonObjects)同步塊雙重判空get()判空 創建后addSingleton()注冊對象的創建邏輯外部傳入的ObjectFactory.getObject()進階Spring 的三級緩存與循環依賴。上述getSingleton(beanName, singletonFactory)是創建型入口而讀取側還維護了三層緩存一級singletonObjects完整 bean、二級earlySingletonObjects半成品僅實例化未初始化、三級singletonFactoriesObjectFactorylambda 表達式。當一個 bean 實例化完畢但尚未初始化時Spring 會提前把() - getEarlyBeanReference(beanName, mbd, bean)放入三級緩存依賴它的另一個 bean 在屬性填充時populateBean→applyPropertyValues→resolveReference→getBean從三級緩存取出這個半成品從而解決循環依賴。詳細調用鏈見 docs/Spring/IoC/循環依賴.md。二、簡單工廠模式集中管控同一系列類的實例化2.1 個人理解簡單工廠模式把同一系列類的實例化交由一個工廠類集中管控。與其說它是一種設計模式倒不如把它看成一種編程習慣——因為它不符合開閉原則增加新的產品類需要修改工廠類的代碼。2.2 簡單實現public interface Hero { void speak(); } public class DaJi implements Hero { Override public void speak() { System.out.println(妲己陪你玩 ~); } } public class LiBai implements Hero { Override public void speak() { System.out.println(今朝有酒 今朝醉 ~); } } /** 對各種英雄進行集中管理 */ public class HeroFactory { public static Hero getShibing(String name) { if (LiBai.equals(name)) return new LiBai(); else if (DaJi.equals(name)) return new DaJi(); else return null; } }這種設計方式只在FBM 資金管理模塊中看到過對 100 個按鈕類進行了集中管控但其設計結構比上面這種要復雜得多例如引入注冊表/配置驅動來緩解每次加按鈕都要改工廠的問題。2.3 使用時機工廠方法模式符合開閉原則增加新的產品類不用修改既有代碼應當優先考慮如果產品類結構簡單且數量龐大如上百個按鈕類簡單工廠反而更容易維護——把所有if/else集中在一個類里比分散到上百個工廠類里更直觀。三、工廠方法模式一個子工廠對應一個產品3.1 個人理解在頂級工廠接口/抽象類中定義產品類的獲取方法由具體的子工廠實例化對應的產品。一般一個子工廠對應一個特定的產品實現對產品的集中管控并且符合開閉原則——新增產品只需新增一個工廠子類不改動既有代碼。3.2 Mybatis 中的范例DataSourceFactory 家族Mybatis 中數據源DataSource的獲取就使用了該設計模式。接口DataSourceFactory定義了獲取DataSource對象的方法各實現類完成獲取對應類型DataSource對象的實現。本倉庫對這塊的完整源碼解析見 docs/Mybatis/基礎支持層/2、DataSource及Transaction模塊.md 與 docs/Mybatis/核心處理層/Mybatis-DataSource.md。public interface DataSourceFactory { // 設置DataSource的屬性一般緊跟在DataSource初始化之后 void setProperties(Properties props); // 獲取DataSource對象 DataSource getDataSource(); } public class JndiDataSourceFactory implements DataSourceFactory { private DataSource dataSource; Override public DataSource getDataSource() { return dataSource; } Override public void setProperties(Properties properties) { try { InitialContext initCtx; Properties env getEnvProperties(properties); if (env null) { initCtx new InitialContext(); } else { initCtx new InitialContext(env); } if (properties.containsKey(INITIAL_CONTEXT) properties.containsKey(DATA_SOURCE)) { Context ctx (Context) initCtx.lookup(properties.getProperty(INITIAL_CONTEXT)); dataSource (DataSource) ctx.lookup(properties.getProperty(DATA_SOURCE)); } else if (properties.containsKey(DATA_SOURCE)) { dataSource (DataSource) initCtx.lookup(properties.getProperty(DATA_SOURCE)); } } catch (NamingException e) { throw new DataSourceException(There was an error configuring JndiDataSourceTransactionPool. Cause: e, e); } } } public class UnpooledDataSourceFactory implements DataSourceFactory { protected DataSource dataSource; // 在實例化該工廠時就完成了DataSource的實例化 public UnpooledDataSourceFactory() { this.dataSource new UnpooledDataSource(); } Override public DataSource getDataSource() { return dataSource; } } public class PooledDataSourceFactory extends UnpooledDataSourceFactory { // 與UnpooledDataSourceFactory的不同之處是其初始化的DataSource為PooledDataSource public PooledDataSourceFactory() { this.dataSource new PooledDataSource(); } }三個具體工廠各司其職工廠類產品對象適用場景JndiDataSourceFactory從 JNDI 上下文查找的DataSource應用服務器托管的連接池如 Tomcat 數據源UnpooledDataSourceFactoryUnpooledDataSource每次getConnection()直接新建連接無池化PooledDataSourceFactoryPooledDataSource內部持有UnpooledDataSource自帶簡易連接池實現連接復用工廠方法模式在 Mybatis 中的真實調用鏈可從 docs/Mybatis/核心處理層/1、MyBatis初始化.md 的environmentsElement()看到解析environments節點時若未指定 environment 則取environments default...屬性dataSourceElement()讀取dataSource typePOOLED的type屬性通過TypeAliasRegistry注冊的別名反射創建工廠typeAliasRegistry.registerAlias(POOLED, PooledDataSourceFactory.class)、registerAlias(UNPOOLED, UnpooledDataSourceFactory.class)、registerAlias(JNDI, JndiDataSourceFactory.class)調用factory.setProperties(props)注入property配置再factory.getDataSource()得到DataSource最終封裝進Environment.Builder注入到全局唯一的Configuration對象。UnpooledDataSourceFactory.setProperties()還有一個實現細節值得注意它以driver.為前綴識別驅動級參數其余參數通過MetaObject反射判斷DataSource是否有對應 setter 再注入遇到未知屬性會拋出DataSourceException(Unknown DataSource property: ...)——這種屬性前綴分類 反射校驗的寫法在解析配置類工廠時非常實用。什么時候該用簡單工廠、什么時候該用工廠方法工廠方法符合開閉原則新增產品類不用修改代碼應優先考慮若產品類結構簡單且數量龐大簡單工廠更容易維護。四、抽象工廠模式一個子工廠對應一組相關產品4.1 個人理解設計結構上與工廠方法模式很像最主要的區別是工廠方法模式一個子工廠只對應一個具體的產品抽象工廠模式一個子工廠對應一組具有相關性的產品即存在多個獲取不同產品的方法。這種設計模式在實踐中最少見反倒是工廠方法模式見的最多。4.2 簡單實現public abstract class AbstractFactory { abstract protected AbstractProductA createProductA(); abstract protected AbstractProductB createProductB(); } public class ConcreteFactory1 extends AbstractFactory { Override protected AbstractProductA createProductA() { return new ProductA1(); } Override protected AbstractProductB createProductB() { return new ProductB1(); } } public class ConcreteFactory2 extends AbstractFactory { Override protected AbstractProductA createProductA() { return new ProductA2(); } Override protected AbstractProductB createProductB() { return new ProductB2(); } } public class Client { public static void main(String[] args) { AbstractFactory factory new ConcreteFactory1(); AbstractProductA productA factory.createProductA(); AbstractProductB productB factory.createProductB(); // 結合使用productA和productB進行后續操作保證產品族的一致性 } }4.3 JDK 中的范例javax.xml.transform.TransformerFactoryJDK 的javax.xml.transform.TransformerFactory組件使用了類似抽象工廠模式的設計抽象類TransformerFactory定義了兩個抽象方法newTransformer()和newTemplates()分別用于生成Transformer對象和Templates對象其子類進行了不同的實現以下為 JDK 1.8 源碼public abstract class TransformerFactory { public abstract Transformer newTransformer(Source source) throws TransformerConfigurationException; public abstract Templates newTemplates(Source source) throws TransformerConfigurationException; } /** * SAXTransformerFactory 繼承了 TransformerFactory */ public class TransformerFactoryImpl extends SAXTransformerFactory implements SourceLoader, ErrorListener { Override public Transformer newTransformer(Source source) throws TransformerConfigurationException { final Templates templates newTemplates(source); final Transformer transformer templates.newTransformer(); if (_uriResolver ! null) { transformer.setURIResolver(_uriResolver); } return (transformer); } Override public Templates newTemplates(Source source) throws TransformerConfigurationException { ...... return new TemplatesImpl(bytecodes, transletName, xsltc.getOutputProperties(), _indentNumber, this); } } public class SmartTransformerFactoryImpl extends SAXTransformerFactory { public Transformer newTransformer(Source source) throws TransformerConfigurationException { if (_xalanFactory null) { createXalanTransformerFactory(); } if (_errorlistener ! null) { _xalanFactory.setErrorListener(_errorlistener); } if (_uriresolver ! null) { _xalanFactory.setURIResolver(_uriresolver); } _currFactory _xalanFactory; return _currFactory.newTransformer(source); } public Templates newTemplates(Source source) throws TransformerConfigurationException { if (_xsltcFactory null) { createXSLTCTransformerFactory(); } if (_errorlistener ! null) { _xsltcFactory.setErrorListener(_errorlistener); } if (_uriresolver ! null) { _xsltcFactory.setURIResolver(_uriresolver); } _currFactory _xsltcFactory; return _currFactory.newTemplates(source); } }兩個子工廠分別產出XSLTC 編譯器實現與Xalan 實現兩組相關產品Transformer 與 Templates客戶端面向抽象工廠編程無需關心具體實現族。五、建造者模式把復雜對象的構建拆分成清晰步驟5.1 個人理解與類圖該模式主要用于將復雜對象的構建過程分解成一個一個簡單的步驟或分攤到多個類中構建保證構建過程層次清晰、代碼不過分臃腫屏蔽掉復雜對象內部的具體構建細節其類圖結構如下該模式的主要角色建造者接口Builder定義建造者構建產品對象的各種公共行為主要分為建造方法與獲取構建好的產品對象具體建造者ConcreteBuilder實現上述接口方法導演Director通過調用具體建造者創建需要的產品對象產品Product被建造的復雜對象。導演角色不必了解產品類的內部細節只提供需要的信息給建造者由具體建造者處理這些信息處理過程可能比較復雜并完成產品構造使產品對象的上層代碼與產品對象的創建過程解耦。建造者模式將復雜產品的創建過程分散到不同構造步驟中既實現了對創建過程的精細控制也使過程更清晰。每個具體建造者都能創建出完整的產品對象且具體建造者之間相互獨立因此系統可以通過不同的具體建造者得到不同的產品對象當有新產品出現時無需修改原有代碼只需添加新的具體建造者即可完成擴展符合開放-封閉原則。5.2 典型范例StringBuilder 與 StringBuffer拼 SQL 語句時常用的StringBuffer和StringBuilder就使用了建造者設計模式JDK 1.8 源碼abstract class AbstractStringBuilder implements Appendable, CharSequence { /** The value is used for character storage. */ char[] value; /** The count is the number of characters used. */ int count; AbstractStringBuilder(int capacity) { value new char[capacity]; } public AbstractStringBuilder append(String str) { if (str null) return appendNull(); int len str.length(); ensureCapacityInternal(count len); // 這里完成了對復雜String的構造將str拼接到當前對象后面 str.getChars(0, len, value, count); count len; return this; } } /** * since JDK 1.5 */ public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, CharSequence { public StringBuilder() { super(16); } Override public StringBuilder append(String str) { super.append(str); return this; } Override public String toString() { // Create a copy, dont share the array return new String(value, 0, count); } } /** * since JDK 1.0 */ public final class StringBuffer extends AbstractStringBuilder implements java.io.Serializable, CharSequence { /** toString返回的最后一個值的緩存。在修改StringBuffer時清除。 */ private transient char[] toStringCache; public StringBuffer() { super(16); } /** * 與StringBuilder建造者最大的不同就是增加了線程安全機制 */ Override public synchronized StringBuffer append(String str) { toStringCache null; super.append(str); return this; } }在建造者模式的角色劃分中Appendable接口append方法扮演建造者接口AbstractStringBuilder與StringBuilder/StringBuffer扮演具體建造者默認容量 16超出自動擴容最終產品是被構造出來的String。StringBuilder與StringBuffer的核心差異僅在于append等方法是否加synchronized——即線程安全由建造者自身保證這正是建造者模式通過不同具體建造者得到不同產品行為的體現。5.3 Mybatis 中的范例BaseBuilder 建造者體系Mybatis 的初始化過程使用了建造者模式詳見 docs/Mybatis/核心處理層/1、MyBatis初始化.md角色劃分如下建造者模式角色Mybatis 中的類建造者接口Builder抽象類BaseBuilder實現公用方法、定義公共屬性具體建造者ConcreteBuilderXMLConfigBuilder解析 mybatis-config.xml、XMLMapperBuilder解析映射配置文件、XMLStatementBuilder解析 SQL 節點產品Product全局唯一、All-In-One 的Configuration對象導演DirectorSqlSessionFactoryBuilder即SqlSessionFactoryBuilder使用BaseBuilder建造者組件對復雜對象Configuration進行了構建。public abstract class BaseBuilder { /** * Configuration 是 MyBatis 初始化過程的核心對象并且全局唯一 * MyBatis 中幾乎全部的配置信息會保存到 Configuration 對象中。 * 也有人稱它是一個All-In-One配置對象 */ protected final Configuration configuration; /** * 在 mybatis-config.xml 配置文件中可以使用typeAliases標簽定義別名 * 這些定義的別名都會記錄在該 TypeAliasRegistry 對象中 */ protected final TypeAliasRegistry typeAliasRegistry; /** * 在 mybatis-config.xml 配置文件中可以使用typeHandlers標簽添加自定義 * TypeHandler完成指定數據庫類型與 Java 類型的轉換這些 TypeHandler * 都會記錄在 TypeHandlerRegistry 中 */ protected final TypeHandlerRegistry typeHandlerRegistry; /** * BaseBuilder 中記錄的 TypeAliasRegistry 對象和 TypeHandlerRegistry 對象 * 其實是全局唯一的它們都是在 Configuration 對象初始化時創建的 */ public BaseBuilder(Configuration configuration) { this.configuration configuration; this.typeAliasRegistry this.configuration.getTypeAliasRegistry(); this.typeHandlerRegistry this.configuration.getTypeHandlerRegistry(); } }導演角色SqlSessionFactoryBuilder的編排邏輯public class SqlSessionFactoryBuilder { public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { // 讀取配置文件 XMLConfigBuilder parser new XMLConfigBuilder(inputStream, environment, properties); // 解析配置文件得到 Configuration 對象然后用其創建 DefaultSqlSessionFactory 對象 return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException(Error building SqlSession., e); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } } public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); } }具體建造者XMLConfigBuilder把 mybatis-config.xml 中每個節點封裝成獨立解析方法parseConfiguration()依次調用private void parseConfiguration(XNode root) { try { // 解析properties節點 propertiesElement(root.evalNode(properties)); // 解析settings節點 Properties settings settingsAsProperties(root.evalNode(settings)); loadCustomVfs(settings); loadCustomLogImpl(settings); // 解析typeAliases節點 typeAliasesElement(root.evalNode(typeAliases)); // 解析plugins節點 pluginElement(root.evalNode(plugins)); // 解析objectFactory節點 objectFactoryElement(root.evalNode(objectFactory)); // 解析objectWrapperFactory節點 objectWrapperFactoryElement(root.evalNode(objectWrapperFactory)); // 解析reflectorFactory節點 reflectorFactoryElement(root.evalNode(reflectorFactory)); settingsElement(settings); // 解析environments節點 environmentsElement(root.evalNode(environments)); // 解析databaseIdProvider節點 databaseIdProviderElement(root.evalNode(databaseIdProvider)); // 解析typeHandlers節點 typeHandlerElement(root.evalNode(typeHandlers)); // 解析mappers節點 mapperElement(root.evalNode(mappers)); } catch (Exception e) { throw new BuilderException(Error parsing SQL Mapper Configuration. Cause: e, e); } }具體建造者XMLMapperBuilder負責解析映射配置文件parse()中先通過configuration.isResourceLoaded(resource)判重再調用configurationElement()依次解析cache-ref、cache、resultMap、sql、select|insert|update|delete等節點最后通過bindMapperForNamespace()將命名空間與 Mapper 接口綁定注冊進MapperRegistry解析過程中出現的前向引用如尚未解析的resultMap會記錄到Configuration的 incomplete 集合由parsePendingResultMaps()等補償機制兜底重試。具體建造者XMLStatementBuilder負責把單個 SQL 節點解析成MappedStatement先校驗id與databaseId是否匹配當前數據庫用SqlCommandType.valueOf(...)確定命令類型處理include、selectKey再通過LanguageDriver.createSqlSource()創建SqlSource最終調用builderAssistant.addMappedStatement(...)注冊到Configuration。BaseBuilder 體系與標準建造者模式的關鍵差異BaseBuilder的建造者模式主要是為了將復雜對象Configuration的構建過程拆解得更清晰把整個構建過程分解到多個具體建造者類中需要這些具體建造者共同配合才能完成Configuration的構造單個具體建造者不具有單獨構造產品的能力——這與StringBuilder/StringBuffer單個建造者即可完成產品構造不同。這種多建造者協作的變體正是應對配置文件種類多、節點多、依賴關系復雜這類真實場景的工程化答案如果把這些解析邏輯全部塞進一個類該類會極其臃腫、難以維護拆分到多個類中代碼規整、思路清晰且符合開閉原則。六、創建型模式選擇指南把五種創建型模式放在一起對照實戰選型會更加清晰模式核心思想是否滿足開閉原則典型應用單例模式全局唯一實例—java.lang.Runtime、Spring 單例 bean、MybatisConfiguration簡單工廠一個工廠集中創建同類產品否新增產品需改工廠FBM 資金管理模塊 100 按鈕類工廠方法一個子工廠對應一個產品是MybatisDataSourceFactory家族抽象工廠一個子工廠對應一組相關產品是JDKTransformerFactory建造者拆分復雜對象的構建步驟是StringBuilder/StringBuffer、MybatisBaseBuilder初始化體系實踐要點回顧單例優先用枚舉或餓漢式必須懶加載時用雙檢鎖并務必加 volatile防止指令重排發布未初始化對象Spring 的單例不是一個類怎么保證唯一而是一個注冊表 一把鎖的容器級單例管理配合三級緩存解決循環依賴工廠選擇上產品多且簡單 → 簡單工廠集中管理產品可擴展 → 工廠方法產品存在族關系 → 抽象工廠遇到構造參數多、構建流程復雜的對象如 Mybatis 的Configuration用建造者模式把構建過程拆分到多個協作類中保持代碼層次清晰。延伸閱讀當前倉庫單例的容器級實現docs/Spring/clazz/Spring-DefaultSingletonBeanRegistry.md、docs/Spring/IoC/循環依賴.md工廠方法在 Mybatis 數據源中的應用docs/Mybatis/基礎支持層/2、DataSource及Transaction模塊.md、docs/Mybatis/核心處理層/Mybatis-DataSource.md建造者模式在 Mybatis 初始化中的應用docs/Mybatis/核心處理層/1、MyBatis初始化.md本系列其余篇目結構型設計模式.md)、行為型設計模式.md)、從框架源碼中學習設計模式的感悟【免費下載鏈接】source-code-hunter 從源碼層面剖析挖掘互聯網行業主流技術的底層實現原理為廣大開發者 “提升技術深度” 提供便利。目前開放 Spring 全家桶Mybatis、Netty、Dubbo 框架及 Redis、Tomcat 中間件等項目地址: https://gitcode.com/GitHub_Trending/so/source-code-hunter創作聲明:本文部分內容由AI輔助生成(AIGC),僅供參考