设计模式是解决问题的方案,从大神的代码中学习对设计模式的使用,可以有效提升个人编码及设计代码的能力。本系列博文用于总结阅读过的框架源码(Spring 系列、Mybatis)及 JDK 源码中 所使用过的设计模式,并结合个人工作经验,重新理解设计模式。

本篇博文主要看一下创建型的几个设计模式,即,单例模式、各种工厂模式 及 建造者模式。

单例模式

个人理解

确保某个类只有一个实例,并提供该实例的获取方法。实际应用很多,不管是框架、JDK 还是实际的项目开发,但大都会使用“饿汉式”或“枚举”来实现单例。“懒汉式”也有一些应用,但通过“双检锁机制”来保证单例的实现很少见。

实现方式

最简单的就是 使用一个私有构造函数、一个私有静态变量,以及一个公共静态方法的方式来实现。懒汉式、饿汉式等简单实现就不多 BB 咯,这里强调一下双检锁懒汉式实现的坑,以及枚举方式的实现吧,最后再结合 spring 源码 扩展一下单例 bean 的实现原理。

1. 双检锁实现的坑

  1. /**
  2. * @author 云之君
  3. * 双检锁 懒汉式,实现线程安全的单例
  4. * 关键词:JVM指令重排、volatile、反射攻击
  5. */
  6. public class Singleton3 {
  7. /**
  8. * 对于我们初级开发来说,这个volatile在实际开发中可能见过,但很少会用到
  9. * 这里加个volatile进行修饰,也是本单例模式的精髓所在。
  10. * 下面的 instance = new Singleton3(); 这行代码在JVM中其实是分三步执行的:
  11. * 1、分配内存空间;
  12. * 2、初始化对象;
  13. * 3、将instance指向分配的内存地址。
  14. * 但JVM具有指令重排的特性,实际的执行顺序可能会是1、3、2,导致多线程情况下出问题,
  15. * 使用volatile修饰instance变量 可以 避免上述的指令重排
  16. * tips:不太理解的是 第一个线程在执行第2步之前就已经释放了锁吗?导致其它线程进入synchronized代码块
  17. * 执行 instance == null 的判断?
  18. * 回答:第一个线程在执行第2步之前就已经释放了锁吗?(没有)。如果不使用volatile修饰instance变量,那么其他线程进来的时候,看到的instance就有可能不是null的,因为已经执行了第3步,那么此时这个线程(执行 return instance;)使用的instance是一个没有初始化的instance,就会有问题。
  19. */
  20. private volatile static Singleton3 instance;
  21. private Singleton3(){
  22. }
  23. public static Singleton3 getInstance(){
  24. if(instance == null){
  25. synchronized(Singleton3.class){
  26. if(instance == null){
  27. instance = new Singleton3();
  28. }
  29. }
  30. }
  31. return instance;
  32. }
  33. }

2. 枚举实现
其它的单例模式实现往往都会面临序列化 和 反射攻击的问题,比如上面的 Singleton3 如果实现了 Serializable 接口,那么在每次序列化时都会创建一个新对象,若要保证单例,必须声明所有字段都是 transient 的,并且提供一个 readResolve()方法。反射攻击可以通过 setAccessible()方法将私有的构造方法公共化,进而实例化。若要防止这种攻击,就需要在构造方法中添加 防止实例化第二个对象的代码。

枚举实现的单例在面对 复杂的序列化及反射攻击时,依然能够保持自己的单例状态,所以被认为是单例的最佳实践。比如,mybatis 在定义 SQL 命令类型时就使用到了枚举。

  1. package org.apache.ibatis.mapping;
  2. /**
  3. * @author Clinton Begin
  4. */
  5. public enum SqlCommandType {
  6. UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;
  7. }

JDK 中的范例

1. java.lang.Runtime

  1. /**
  2. * 每个Java应用程序都有一个单例的Runtime对象,通过getRuntime()方法获得
  3. * @author unascribed
  4. * @see java.lang.Runtime#getRuntime()
  5. * @since JDK1.0
  6. */
  7. public class Runtime {
  8. /** 很明显,这里用的是饿汉式 实现单例 */
  9. private static Runtime currentRuntime = new Runtime();
  10. public static Runtime getRuntime() {
  11. return currentRuntime;
  12. }
  13. /** Don't let anyone else instantiate this class */
  14. private Runtime() {}
  15. }

2. java.awt.Desktop

  1. public class Desktop {
  2. /**
  3. * Suppresses default constructor for noninstantiability.
  4. */
  5. private Desktop() {
  6. peer = Toolkit.getDefaultToolkit().createDesktopPeer(this);
  7. }
  8. /**
  9. * 由于对象较大,这里使用了懒汉式延迟加载,方式比较简单,直接把锁加在方法上。
  10. * 使用双检锁方式实现的单例 还没怎么碰到过,有经验的小伙伴 欢迎留言补充
  11. */
  12. public static synchronized Desktop getDesktop(){
  13. if (GraphicsEnvironment.isHeadless()) throw new HeadlessException();
  14. if (!Desktop.isDesktopSupported()) {
  15. throw new UnsupportedOperationException("Desktop API is not " +
  16. "supported on the current platform");
  17. }
  18. sun.awt.AppContext context = sun.awt.AppContext.getAppContext();
  19. Desktop desktop = (Desktop)context.get(Desktop.class);
  20. if (desktop == null) {
  21. desktop = new Desktop();
  22. context.put(Desktop.class, desktop);
  23. }
  24. return desktop;
  25. }
  26. }

Spring 的单例 bean 是如何实现的?

Spring 实现单例 bean 是使用 map 注册表和 synchronized 同步机制实现的,通过分析 spring 的 AbstractBeanFactory 中的 doGetBean 方法和 DefaultSingletonBeanRegistry 的 getSingleton()方法,可以理解其实现原理。

  1. public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
  2. ......
  3. /**
  4. * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  5. * 真正实现向IOC容器获取Bean的功能,也是触发依赖注入(DI)功能的地方
  6. * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  7. */
  8. @SuppressWarnings("unchecked")
  9. protected <T> T doGetBean(final String name, final Class<T> requiredType, final Object[] args,
  10. boolean typeCheckOnly) throws BeansException {
  11. ......
  12. //创建单例模式bean的实例对象
  13. if (mbd.isSingleton()) {
  14. //这里使用了一个匿名内部类,创建Bean实例对象,并且注册给所依赖的对象
  15. sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
  16. public Object getObject() throws BeansException {
  17. try {
  18. /**
  19. * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  20. * 创建一个指定的Bean实例对象,如果有父级继承,则合并子类和父类的定义
  21. * 走子类中的实现
  22. * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  23. */
  24. return createBean(beanName, mbd, args);
  25. }
  26. catch (BeansException ex) {
  27. destroySingleton(beanName);
  28. throw ex;
  29. }
  30. }
  31. });
  32. //获取给定Bean的实例对象
  33. bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
  34. }
  35. ......
  36. }
  37. }
  38. /**
  39. * 默认的单例bean注册器
  40. */
  41. public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
  42. /** 单例的bean实例的缓存 */
  43. private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);
  44. /**
  45. * 返回给定beanName的 已经注册的 单例bean,如果没有注册,则注册并返回
  46. */
  47. public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
  48. Assert.notNull(beanName, "'beanName' must not be null");
  49. // 加锁,保证单例bean在多线程环境下不会创建多个
  50. synchronized (this.singletonObjects) {
  51. // 先从缓存中取,有就直接返回,没有就创建、注册到singletonObjects、返回
  52. Object singletonObject = this.singletonObjects.get(beanName);
  53. if (singletonObject == null) {
  54. if (this.singletonsCurrentlyInDestruction) {
  55. throw new BeanCreationNotAllowedException(beanName,
  56. "Singleton bean creation not allowed while the singletons of this factory are in destruction " +
  57. "(Do not request a bean from a BeanFactory in a destroy method implementation!)");
  58. }
  59. if (logger.isDebugEnabled()) {
  60. logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
  61. }
  62. beforeSingletonCreation(beanName);
  63. boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
  64. if (recordSuppressedExceptions) {
  65. this.suppressedExceptions = new LinkedHashSet<Exception>();
  66. }
  67. try {
  68. singletonObject = singletonFactory.getObject();
  69. }
  70. catch (BeanCreationException ex) {
  71. if (recordSuppressedExceptions) {
  72. for (Exception suppressedException : this.suppressedExceptions) {
  73. ex.addRelatedCause(suppressedException);
  74. }
  75. }
  76. throw ex;
  77. }
  78. finally {
  79. if (recordSuppressedExceptions) {
  80. this.suppressedExceptions = null;
  81. }
  82. afterSingletonCreation(beanName);
  83. }
  84. // 注册到单例bean的缓存
  85. addSingleton(beanName, singletonObject);
  86. }
  87. return (singletonObject != NULL_OBJECT ? singletonObject : null);
  88. }
  89. }
  90. }

简单工厂模式

个人理解

把同一系列类的实例化交由一个工厂类进行集中管控。与其说它是一种设计模式,倒不如把它看成一种编程习惯,因为它不符合“开闭原则”,增加新的产品类需要修改工厂类的代码。

简单实现

  1. public interface Hero {
  2. void speak();
  3. }
  4. public class DaJi implements Hero {
  5. @Override
  6. public void speak() {
  7. System.out.println("妲己,陪你玩 ~");
  8. }
  9. }
  10. public class LiBai implements Hero{
  11. @Override
  12. public void speak() {
  13. System.out.println("今朝有酒 今朝醉 ~");
  14. }
  15. }
  16. /** 对各种英雄进行集中管理 */
  17. public class HeroFactory {
  18. public static Hero getShibing(String name){
  19. if("LiBai".equals(name))
  20. return new LiBai();
  21. else if("DaJi".equals(name))
  22. return new DaJi();
  23. else
  24. return null;
  25. }
  26. }

这种设计方式只在我们产品的“FBM 资金管理”模块有看到过,其中对 100+个按钮类进行了集中管控,不过其设计结构比上面这种要复杂的多。

工厂方法模式

个人理解

在顶级工厂(接口/抽象类)中定义 产品类的获取方法,由具体的子工厂实例化对应的产品,一般是一个子工厂对应一个特定的产品,实现对产品的集中管控,并且符合“开闭原则”。

Mybatis 中的范例

mybatis 中数据源 DataSource 的获取使用到了该设计模式。接口 DataSourceFactory 定义了获取 DataSource 对象的方法,各实现类 完成了获取对应类型的 DataSource 对象的实现。(mybatis 的源码都是缩进两个空格,难道国外的编码规范有独门派系?)

  1. public interface DataSourceFactory {
  2. // 设置DataSource的属性,一般紧跟在DataSource初始化之后
  3. void setProperties(Properties props);
  4. // 获取DataSource对象
  5. DataSource getDataSource();
  6. }
  7. public class JndiDataSourceFactory implements DataSourceFactory {
  8. private DataSource dataSource;
  9. @Override
  10. public DataSource getDataSource() {
  11. return dataSource;
  12. }
  13. @Override
  14. public void setProperties(Properties properties) {
  15. try {
  16. InitialContext initCtx;
  17. Properties env = getEnvProperties(properties);
  18. if (env == null) {
  19. initCtx = new InitialContext();
  20. } else {
  21. initCtx = new InitialContext(env);
  22. }
  23. if (properties.containsKey(INITIAL_CONTEXT)
  24. && properties.containsKey(DATA_SOURCE)) {
  25. Context ctx = (Context) initCtx.lookup(properties.getProperty(INITIAL_CONTEXT));
  26. dataSource = (DataSource) ctx.lookup(properties.getProperty(DATA_SOURCE));
  27. } else if (properties.containsKey(DATA_SOURCE)) {
  28. dataSource = (DataSource) initCtx.lookup(properties.getProperty(DATA_SOURCE));
  29. }
  30. } catch (NamingException e) {
  31. throw new DataSourceException("There was an error configuring JndiDataSourceTransactionPool. Cause: " + e, e);
  32. }
  33. }
  34. }
  35. public class UnpooledDataSourceFactory implements DataSourceFactory {
  36. protected DataSource dataSource;
  37. // 在实例化该工厂时,就完成了DataSource的实例化
  38. public UnpooledDataSourceFactory() {
  39. this.dataSource = new UnpooledDataSource();
  40. }
  41. @Override
  42. public DataSource getDataSource() {
  43. return dataSource;
  44. }
  45. }
  46. public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
  47. // 与UnpooledDataSourceFactory的不同之处是,其初始化的DataSource为PooledDataSource
  48. public PooledDataSourceFactory() {
  49. this.dataSource = new PooledDataSource();
  50. }
  51. }
  52. public interface DataSource extends CommonDataSource, Wrapper {
  53. Connection getConnection() throws SQLException;
  54. Connection getConnection(String username, String password)
  55. throws SQLException;
  56. }

DataSource 最主要的几个实现类内容都比较多,代码就不贴出来咯,感兴趣的同学可以到我的源码分析专题中看到详细解析。

tips:什么时候该用简单工厂模式?什么时候该用工厂方法模式呢?
个人认为,工厂方法模式符合“开闭原则”,增加新的产品类不用修改代码,应当优先考虑使用这种模式。如果产品类结构简单且数量庞大时,还是使用简单工厂模式更容易维护些,如:上百个按钮类。

抽象工厂模式

个人理解

设计结构上与“工厂方法”模式很像,最主要的区别是,工厂方法模式中 一个子工厂只对应一个具体的产品,而抽象工厂模式中,一个子工厂对应一组具有相关性的产品,即,存在多个获取不同产品的方法。这种设计模式也很少见人用,倒是“工厂方法”模式见的最多。

简单实现

  1. public abstract class AbstractFactory {
  2. abstract protected AbstractProductA createProductA();
  3. abstract protected AbstractProductB createProductB();
  4. }
  5. public class ConcreteFactory1 extends AbstractFactory {
  6. @Override
  7. protected AbstractProductA createProductA() {
  8. return new ProductA1();
  9. }
  10. @Override
  11. protected AbstractProductB createProductB() {
  12. return new ProductB1();
  13. }
  14. }
  15. public class ConcreteFactory2 extends AbstractFactory {
  16. @Override
  17. protected AbstractProductA createProductA() {
  18. return new ProductA2();
  19. }
  20. @Override
  21. protected AbstractProductB createProductB() {
  22. return new ProductB2();
  23. }
  24. }
  25. public class Client {
  26. public static void main(String[] args) {
  27. AbstractFactory factory = new ConcreteFactory1();
  28. AbstractProductA productA = factory.createProductA();
  29. AbstractProductB productB = factory.createProductB();
  30. ...
  31. // 结合使用productA和productB进行后续操作
  32. ...
  33. }
  34. }

JDK 中的范例

JDK 的 javax.xml.transform.TransformerFactory 组件使用了类似“抽象工厂”模式的设计,抽象类 TransformerFactory 定义了两个抽象方法 newTransformer()和 newTemplates()分别用于生成 Transformer 对象 和 Templates 对象,其两个子类进行了不同的实现,源码如下(版本 1.8)。

  1. public abstract class TransformerFactory {
  2. public abstract Transformer newTransformer(Source source)
  3. throws TransformerConfigurationException;
  4. public abstract Templates newTemplates(Source source)
  5. throws TransformerConfigurationException;
  6. }
  7. /**
  8. * SAXTransformerFactory 继承了 TransformerFactory
  9. */
  10. public class TransformerFactoryImpl
  11. extends SAXTransformerFactory implements SourceLoader, ErrorListener {
  12. @Override
  13. public Transformer newTransformer(Source source) throws TransformerConfigurationException {
  14. final Templates templates = newTemplates(source);
  15. final Transformer transformer = templates.newTransformer();
  16. if (_uriResolver != null) {
  17. transformer.setURIResolver(_uriResolver);
  18. }
  19. return(transformer);
  20. }
  21. @Override
  22. public Templates newTemplates(Source source) throws TransformerConfigurationException {
  23. ......
  24. return new TemplatesImpl(bytecodes, transletName,
  25. xsltc.getOutputProperties(), _indentNumber, this);
  26. }
  27. }
  28. public class SmartTransformerFactoryImpl extends SAXTransformerFactory {
  29. public Transformer newTransformer(Source source) throws TransformerConfigurationException {
  30. if (_xalanFactory == null) {
  31. createXalanTransformerFactory();
  32. }
  33. if (_errorlistener != null) {
  34. _xalanFactory.setErrorListener(_errorlistener);
  35. }
  36. if (_uriresolver != null) {
  37. _xalanFactory.setURIResolver(_uriresolver);
  38. }
  39. _currFactory = _xalanFactory;
  40. return _currFactory.newTransformer(source);
  41. }
  42. public Templates newTemplates(Source source) throws TransformerConfigurationException {
  43. if (_xsltcFactory == null) {
  44. createXSLTCTransformerFactory();
  45. }
  46. if (_errorlistener != null) {
  47. _xsltcFactory.setErrorListener(_errorlistener);
  48. }
  49. if (_uriresolver != null) {
  50. _xsltcFactory.setURIResolver(_uriresolver);
  51. }
  52. _currFactory = _xsltcFactory;
  53. return _currFactory.newTemplates(source);
  54. }
  55. }

建造者模式

个人理解

该模式主要用于将复杂对象的构建过程分解成一个个简单的步骤,或者分摊到多个类中进行构建,保证构建过程层次清晰,代码不会过分臃肿,屏蔽掉了复杂对象内部的具体构建细节,其类图结构如下所示。

avatar

该模式的主要角色如下:

  • 建造者接口(Builder):用于定义建造者构建产品对象的各种公共行为,主要分为 建造方法 和 获取构建好的产品对象;
  • 具体建造者(ConcreteBuilder):实现上述接口方法;
  • 导演(Director):通过调用具体建造者创建需要的产品对象;
  • 产品(Product):被建造的复杂对象。

其中的导演角色不必了解产品类的内部细节,只提供需要的信息给建造者,由具体建造者处理这些信息(这个处理过程可能会比较复杂)并完成产品构造,使产品对象的上层代码与产品对象的创建过程解耦。建造者模式将复杂产品的创建过程分散到不同的构造步骤中,这样可以对产品创建过程实现更加精细的控制,也会使创建过程更加清晰。每个具体建造者都可以创建出完整的产品对象,而且具体建造者之间是相互独立的, 因此系统就可以通过不同的具体建造者,得到不同的产品对象。当有新产品出现时,无须修改原有的代码,只需要添加新的具体建造者即可完成扩展,这符合“开放一封闭” 原则。

典型的范例 StringBuilder 和 StringBuffer

相信在拼 SQL 语句时大家一定经常用到 StringBuffer 和 StringBuilder 这两个类,它们就用到了建造者设计模式,源码如下(版本 1.8):

  1. abstract class AbstractStringBuilder implements Appendable, CharSequence {
  2. /**
  3. * The value is used for character storage.
  4. */
  5. char[] value;
  6. /**
  7. * The count is the number of characters used.
  8. */
  9. int count;
  10. /**
  11. * Creates an AbstractStringBuilder of the specified capacity.
  12. */
  13. AbstractStringBuilder(int capacity) {
  14. value = new char[capacity];
  15. }
  16. public AbstractStringBuilder append(String str) {
  17. if (str == null)
  18. return appendNull();
  19. int len = str.length();
  20. ensureCapacityInternal(count + len);
  21. // 这里完成了对复杂String的构造,将str拼接到当前对象后面
  22. str.getChars(0, len, value, count);
  23. count += len;
  24. return this;
  25. }
  26. }
  27. /**
  28. * @since JDK 1.5
  29. */
  30. public final class StringBuilder extends AbstractStringBuilder
  31. implements java.io.Serializable, CharSequence {
  32. public StringBuilder() {
  33. super(16);
  34. }
  35. @Override
  36. public StringBuilder append(String str) {
  37. super.append(str);
  38. return this;
  39. }
  40. @Override
  41. public String toString() {
  42. // Create a copy, don't share the array
  43. return new String(value, 0, count);
  44. }
  45. }
  46. /**
  47. * @since JDK 1.0
  48. */
  49. public final class StringBuffer extends AbstractStringBuilder
  50. implements java.io.Serializable, CharSequence {
  51. /**
  52. * toString返回的最后一个值的缓存。在修改StringBuffer时清除。
  53. */
  54. private transient char[] toStringCache;
  55. public StringBuffer() {
  56. super(16);
  57. }
  58. /**
  59. * 与StringBuilder建造者最大的不同就是,增加了线程安全机制
  60. */
  61. @Override
  62. public synchronized StringBuffer append(String str) {
  63. toStringCache = null;
  64. super.append(str);
  65. return this;
  66. }
  67. }

Mybatis 中的范例

MyBatis 的初始化过程使用了建造者模式,抽象类 BaseBuilder 扮演了“建造者接口”的角色,对一些公用方法进行了实现,并定义了公共属性。XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder 等实现类扮演了“具体建造者”的角色,分别用于解析 mybatis-config.xml 配置文件、映射配置文件 以及 SQL 节点。Configuration 和 SqlSessionFactoryBuilder 则分别扮演了“产品” 和 “导演”的角色。即,SqlSessionFactoryBuilder 使用了 BaseBuilder 建造者组件 对复杂对象 Configuration 进行了构建。

BaseBuilder 组件的设计与上面标准的建造者模式是有很大不同的,BaseBuilder 的建造者模式主要是为了将复杂对象 Configuration 的构建过程分解的层次更清晰,将整个构建过程分解到多个“具体构造者”类中,需要这些“具体构造者”共同配合才能完成 Configuration 的构造,单个“具体构造者”不具有单独构造产品的能力,这与 StringBuilder 及 StringBuffer 是不同的。

个人理解的构建者模式 其核心就是用来构建复杂对象的,比如 mybatis 对 Configuration 对象的构建。当然,我们也可以把 对这个对象的构建过程 写在一个类中,来满足我们的需求,但这样做的话,这个类就会变得及其臃肿,难以维护。所以把整个构建过程合理地拆分到多个类中,分别构建,整个代码就显得非常规整,且思路清晰,而且 建造者模式符合 开闭原则。其源码实现如下。

  1. public abstract class BaseBuilder {
  2. /**
  3. * Configuration 是 MyBatis 初始化过程的核心对象并且全局唯一,
  4. * MyBatis 中几乎全部的配置信息会保存到Configuration 对象中。
  5. * 也有人称它是一个“All-In-One”配置对象
  6. */
  7. protected final Configuration configuration;
  8. /**
  9. * 在 mybatis-config.xml 配置文件中可以使用<typeAliases>标签定义别名,
  10. * 这些定义的别名都会记录在该 TypeAliasRegistry 对象中
  11. */
  12. protected final TypeAliasRegistry typeAliasRegistry;
  13. /**
  14. * 在 mybatis-config.xml 配置文件中可以使用<typeHandlers>标签添加自定义
  15. * TypeHandler,完成指定数据库类型与 Java 类型的转换,这些 TypeHandler
  16. * 都会记录在 TypeHandlerRegistry 中
  17. */
  18. protected final TypeHandlerRegistry typeHandlerRegistry;
  19. /**
  20. * BaseBuilder 中记录的 TypeAliasRegistry 对象和 TypeHandlerRegistry 对象,
  21. * 其实是全局唯一的,它们都是在 Configuration 对象初始化时创建的
  22. */
  23. public BaseBuilder(Configuration configuration) {
  24. this.configuration = configuration;
  25. this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
  26. this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
  27. }
  28. }
  29. public class XMLConfigBuilder extends BaseBuilder {
  30. /** 标识是否已经解析过 mybatis-config.xml 配置文件 */
  31. private boolean parsed;
  32. /** 用于解析 mybatis-config.xml 配置文件 */
  33. private final XPathParser parser;
  34. /** 标识 <environment> 配置的名称,默认读取 <environment> 标签的 default 属性 */
  35. private String environment;
  36. /** 负责创建和缓存 Reflector 对象 */
  37. private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
  38. public Configuration parse() {
  39. if (parsed) {
  40. throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  41. }
  42. parsed = true;
  43. // 在 mybatis-config.xml 配置文件中查找<configuration>节点,并开始解析
  44. parseConfiguration(parser.evalNode("/configuration"));
  45. return configuration;
  46. }
  47. private void parseConfiguration(XNode root) {
  48. try {
  49. //issue #117 read properties first
  50. // 解析<properties>节点
  51. propertiesElement(root.evalNode("properties"));
  52. // 解析<settings>节点
  53. Properties settings = settingsAsProperties(root.evalNode("settings"));
  54. loadCustomVfs(settings);
  55. loadCustomLogImpl(settings);
  56. // 解析<typeAliases>节点
  57. typeAliasesElement(root.evalNode("typeAliases"));
  58. // 解析<plugins>节点
  59. pluginElement(root.evalNode("plugins"));
  60. // 解析<objectFactory>节点
  61. objectFactoryElement(root.evalNode("objectFactory"));
  62. // 解析<objectWrapperFactory>节点
  63. objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
  64. // 解析<reflectorFactory>节点
  65. reflectorFactoryElement(root.evalNode("reflectorFactory"));
  66. settingsElement(settings);
  67. // read it after objectFactory and objectWrapperFactory issue #631
  68. // 解析<environments>节点
  69. environmentsElement(root.evalNode("environments"));
  70. // 解析<databaseIdProvider>节点
  71. databaseIdProviderElement(root.evalNode("databaseIdProvider"));
  72. // 解析<typeHandlers>节点
  73. typeHandlerElement(root.evalNode("typeHandlers"));
  74. // 解析<mappers>节点
  75. mapperElement(root.evalNode("mappers"));
  76. } catch (Exception e) {
  77. throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  78. }
  79. }
  80. }
  81. public class XMLMapperBuilder extends BaseBuilder {
  82. private final XPathParser parser;
  83. private final MapperBuilderAssistant builderAssistant;
  84. private final Map<String, XNode> sqlFragments;
  85. private final String resource;
  86. public void parse() {
  87. // 判断是否已经加载过该映射文件
  88. if (!configuration.isResourceLoaded(resource)) {
  89. // 处理<mapper>节点
  90. configurationElement(parser.evalNode("/mapper"));
  91. // 将 resource 添加到 Configuration.loadedResources 集合中保存,
  92. // 它是 HashSet<String> 类型的集合,其中记录了已经加载过的映射文件
  93. configuration.addLoadedResource(resource);
  94. // 注册 Mapper 接口
  95. bindMapperForNamespace();
  96. }
  97. // 处理 configurationElement() 方法中解析失败的<resultMap>节点
  98. parsePendingResultMaps();
  99. // 处理 configurationElement() 方法中解析失败的<cache-ref>节点
  100. parsePendingCacheRefs();
  101. // 处理 configurationElement() 方法中解析失败的 SQL 语句节点
  102. parsePendingStatements();
  103. }
  104. private void configurationElement(XNode context) {
  105. try {
  106. // 获取<mapper>节点的 namespace 属性,若 namespace 属性为空,则抛出异常
  107. String namespace = context.getStringAttribute("namespace");
  108. if (namespace == null || namespace.equals("")) {
  109. throw new BuilderException("Mapper's namespace cannot be empty");
  110. }
  111. // 设置 MapperBuilderAssistant 的 currentNamespace 字段,记录当前命名空间
  112. builderAssistant.setCurrentNamespace(namespace);
  113. // 解析<cache-ref>节点
  114. cacheRefElement(context.evalNode("cache-ref"));
  115. // 解析<cache>节点
  116. cacheElement(context.evalNode("cache"));
  117. // 解析<parameterMap>节点,(该节点 已废弃,不再推荐使用)
  118. parameterMapElement(context.evalNodes("/mapper/parameterMap"));
  119. // 解析<resultMap>节点
  120. resultMapElements(context.evalNodes("/mapper/resultMap"));
  121. // 解析<sql>节点
  122. sqlElement(context.evalNodes("/mapper/sql"));
  123. // 解析<select>、<insert>、<update>、<delete>等SQL节点
  124. buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  125. } catch (Exception e) {
  126. throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
  127. }
  128. }
  129. }
  130. public class XMLStatementBuilder extends BaseBuilder {
  131. private final MapperBuilderAssistant builderAssistant;
  132. private final XNode context;
  133. private final String requiredDatabaseId;
  134. public void parseStatementNode() {
  135. // 获取 SQL 节点的 id 以及 databaseId 属性,若其 databaseId属性值与当前使用的数据库不匹配,
  136. // 则不加载该 SQL 节点;若存在相同 id 且 databaseId 不为空的 SQL 节点,则不再加载该 SQL 节点
  137. String id = context.getStringAttribute("id");
  138. String databaseId = context.getStringAttribute("databaseId");
  139. if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
  140. return;
  141. }
  142. // 根据 SQL 节点的名称决定其 SqlCommandType
  143. String nodeName = context.getNode().getNodeName();
  144. SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
  145. boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
  146. boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
  147. boolean useCache = context.getBooleanAttribute("useCache", isSelect);
  148. boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
  149. // 在解析 SQL 语句之前,先处理其中的<include>节点
  150. XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
  151. includeParser.applyIncludes(context.getNode());
  152. String parameterType = context.getStringAttribute("parameterType");
  153. Class<?> parameterTypeClass = resolveClass(parameterType);
  154. String lang = context.getStringAttribute("lang");
  155. LanguageDriver langDriver = getLanguageDriver(lang);
  156. // 处理<selectKey>节点
  157. processSelectKeyNodes(id, parameterTypeClass, langDriver);
  158. // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
  159. KeyGenerator keyGenerator;
  160. String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
  161. keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
  162. if (configuration.hasKeyGenerator(keyStatementId)) {
  163. keyGenerator = configuration.getKeyGenerator(keyStatementId);
  164. } else {
  165. keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
  166. configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
  167. ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
  168. }
  169. SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
  170. StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
  171. Integer fetchSize = context.getIntAttribute("fetchSize");
  172. Integer timeout = context.getIntAttribute("timeout");
  173. String parameterMap = context.getStringAttribute("parameterMap");
  174. String resultType = context.getStringAttribute("resultType");
  175. Class<?> resultTypeClass = resolveClass(resultType);
  176. String resultMap = context.getStringAttribute("resultMap");
  177. String resultSetType = context.getStringAttribute("resultSetType");
  178. ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
  179. if (resultSetTypeEnum == null) {
  180. resultSetTypeEnum = configuration.getDefaultResultSetType();
  181. }
  182. String keyProperty = context.getStringAttribute("keyProperty");
  183. String keyColumn = context.getStringAttribute("keyColumn");
  184. String resultSets = context.getStringAttribute("resultSets");
  185. builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
  186. fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
  187. resultSetTypeEnum, flushCache, useCache, resultOrdered,
  188. keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  189. }
  190. }
  191. public class SqlSessionFactoryBuilder {
  192. public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
  193. try {
  194. // 读取配置文件
  195. XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
  196. // 解析配置文件得到 Configuration 对象,然后用其创建 DefaultSqlSessionFactory 对象
  197. return build(parser.parse());
  198. } catch (Exception e) {
  199. throw ExceptionFactory.wrapException("Error building SqlSession.", e);
  200. } finally {
  201. ErrorContext.instance().reset();
  202. try {
  203. inputStream.close();
  204. } catch (IOException e) {
  205. // Intentionally ignore. Prefer previous error.
  206. }
  207. }
  208. }
  209. public SqlSessionFactory build(Configuration config) {
  210. return new DefaultSqlSessionFactory(config);
  211. }
  212. }