从 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 Locking,DCL)"实现的线程安全单例在真实代码中反而很少见,因为它的正确性高度依赖对 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.SqlCommandType:
package 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.Runtime(JDK 1.0 起):
public class Runtime { /** 很明显,这里用的是饿汉式实现单例 */ private static Runtime currentRuntime = new Runtime(); public static Runtime getRuntime() { return currentRuntime; } /** Don't let anyone else instantiate this class */ private Runtime() {} }每个 Java 应用程序都有一个单例的Runtime对象,通过getRuntime()获得。类加载阶段即完成初始化,天然线程安全,代价是无论是否使用都会创建实例。
懒汉式范例 —— java.awt.Desktop:
public 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 Class<T> requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException { ...... //创建单例模式bean的实例对象 if (mbd.isSingleton()) { //这里使用了一个匿名内部类,创建Bean实例对象,并且注册给所依赖的对象 sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() { 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 Map<String, Object> singletonObjects = new ConcurrentHashMap<String, 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 LinkedHashSet<Exception>(); } 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 的对应实现 |
|---|---|
私有静态变量instance | singletonObjects注册表(ConcurrentHashMap) |
synchronized代码块 | synchronized (this.singletonObjects)同步块 |
| 双重判空 | get()判空 + 创建后addSingleton()注册 |
| 对象的创建逻辑 | 外部传入的ObjectFactory.getObject() |
进阶:Spring 的三级缓存与循环依赖。上述getSingleton(beanName, singletonFactory)是"创建型"入口,而读取侧还维护了三层缓存:一级singletonObjects(完整 bean)、二级earlySingletonObjects(半成品,仅实例化未初始化)、三级singletonFactories(ObjectFactorylambda 表达式)。当一个 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 数据源) |
UnpooledDataSourceFactory | UnpooledDataSource | 每次getConnection()直接新建连接,无池化 |
PooledDataSourceFactory | PooledDataSource(内部持有UnpooledDataSource) | 自带简易连接池,实现连接复用 |
工厂方法模式在 Mybatis 中的真实调用链(可从 docs/Mybatis/核心处理层/1、MyBatis初始化.md 的environmentsElement()看到):
- 解析
<environments>节点时,若未指定 environment 则取<environments default="...">属性; dataSourceElement()读取<dataSource type="POOLED">的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.TransformerFactory
JDK 的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, don't 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(实现公用方法、定义公共属性) |
| 具体建造者(ConcreteBuilder) | XMLConfigBuilder(解析 mybatis-config.xml)、XMLMapperBuilder(解析映射配置文件)、XMLStatementBuilder(解析 SQL 节点) |
| 产品(Product) | 全局唯一、All-In-One 的Configuration对象 |
| 导演(Director) | SqlSessionFactoryBuilder |
即: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),仅供参考