從Spring及Mybatis框架源碼中學習設計模式(創建型)

設計模式是解決問題的方案,從大神的代碼中學習對設計模式的使用,可以有效提升個人編碼及設計代碼的能力。本系列博文用於總結閱讀過的框架源碼(Spring系列、Mybatis)及JDK源碼中 所使用過的設計模式,並結合個人工作經驗,重新理解設計模式。

本篇博文主要看一下創建型的幾個設計模式,即,單例模式、各種工廠模式 及 建造者模式。

單例模式

目標

確保某個類只有一個實例,並提供該實例的獲取方法。

實現方式

最簡單的就是 使用一個私有構造函數、一個私有靜態變量,以及一個公共靜態方法的方式來實現。懶漢式、餓漢式等簡單實現就不多BB咯,這裏強調一下雙檢鎖懶漢式實現的坑,以及枚舉方式的實現吧,最後再結合spring源碼 擴展一下單例bean的實現原理。

1. 雙檢鎖實現的坑

/**
* @author 雲之君
* 雙檢鎖 懶漢式,實現線程安全的單例
* 關鍵詞:JVM指令重排、volatile、反射攻擊
*/
public class Singleton3 { 
    /**
     * 對於我們初級開發來說,這個volatile在實際開發中可能見過,但很少會用到
     * 這裏加個volatile進行修飾,也是本單例模式的精髓所在。
     * 下面的 instance = new Singleton3(); 這行代碼在JVM中其實是分三步執行的:
     * 1、分配內存空間;
     * 2、初始化對象;
     * 3、將instance指向分配的內存地址。
     * 但JVM具有指令重排的特性,實際的執行順序可能會是1、3、2,導致多線程情況下出問題,
     * 使用volatile修飾instance變量 可以 避免上述的指令重排
     * tips:不太理解的是 第一個線程在執行第2步之前就已經釋放了鎖嗎?導致其它線程進入synchronized代碼塊
     *      執行 instance == null 的判斷?
     */
    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;
    }
}

2. 枚舉實現
其它的單例模式實現往往都會面臨序列化 和 反射攻擊的問題,比如上面的Singleton3如果實現了Serializable接口,那麼在每次序列化時都會創建一個新對象,若要保證單例,必須聲明所有字段都是transient的,並且提供一個readResolve()方法。反射攻擊可以通過setAccessible()方法將私有的構造方法公共化,進而實例化。若要防止這種攻擊,就需要在構造方法中添加 防止實例化第二個對象的代碼。

枚舉實現的單例在面對 複雜的序列化及反射攻擊時,依然能夠保持自己的單例狀態,所以被認爲是單例的最佳實踐。比如,mybatis在定義SQL命令類型時就使用到了枚舉。

package org.apache.ibatis.mapping;

/**
 * @author Clinton Begin
 */
public enum SqlCommandType {
  UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;
}

實際範例

1. java.lang.Runtime

/**
 * 每個Java應用程序都有一個單例的Runtime對象,通過getRuntime()方法獲得
 * @author  unascribed
 * @see     java.lang.Runtime#getRuntime()
 * @since   JDK1.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() {}
}

2. 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;
    }
}

Spring的單例bean是如何實現的?

Spring實現單例bean是使用map註冊表和synchronized同步機制實現的,通過分析spring的 AbstractBeanFactory 中的 doGetBean 方法和DefaultSingletonBeanRegistry的getSingleton()方法,可以理解其實現原理。

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);
        }
        
        ......
        
    }
}


/**
 * 默認的單例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!)");
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
                }
                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);
        }
    }
}

簡單工廠模式

個人理解

把同一系列類的實例化交由一個工廠類進行集中管控。與其說它是一種設計模式,倒不如把它看成一種編程習慣,因爲它不符合“開閉原則”,增加新的產品類需要修改工廠類的代碼。

簡單實現

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+個按鈕類進行了集中管控,不過其設計結構比上面這種要複雜的多。

工廠方法模式

個人理解

在頂級工廠(接口/抽象類)中定義 產品類的獲取方法,由具體的子工廠實例化對應的產品,一般是一個子工廠對應一個特定的產品,實現對產品的集中管控,並且符合“開閉原則”。

Mybatis中的範例

mybatis中數據源DataSource的獲取使用到了該設計模式。接口DataSourceFactory定義了獲取DataSource對象的方法,各實現類 完成了獲取對應類型的DataSource對象的實現。(mybatis的源碼都是縮進兩個空格,難道國外的編碼規範有獨門派系?)

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();
  }
}


public interface DataSource  extends CommonDataSource, Wrapper {

  Connection getConnection() throws SQLException;

  Connection getConnection(String username, String password)
    throws SQLException;
}

DataSource最主要的幾個實現類內容都比較多,代碼就不貼出來咯,感興趣的同學可以到我的源碼分析專題中看到詳細解析,地址:
https://github.com/doocs/source-code-hunter

tips:什麼時候該用簡單工廠模式?什麼時候該用工廠方法模式呢?
個人認爲,工廠方法模式符合“開閉原則”,增加新的產品類不用修改代碼,應當優先考慮使用這種模式。如果產品類結構簡單且數量龐大時,還是使用簡單工廠模式更容易維護些,如:上百個按鈕類。

抽象工廠模式

個人理解

設計結構上與“工廠方法”模式很像,最主要的區別是,工廠方法模式中 一個子工廠只對應一個具體的產品,而抽象工廠模式中,一個子工廠對應一組具有相關性的產品,即,存在多個獲取不同產品的方法。這種設計模式也很少見人用,倒是“工廠方法”模式見的最多。

簡單實現

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進行後續操作
        ...
    }
}

JDK中的範例

JDK的javax.xml.transform.TransformerFactory組件使用了類似“抽象工廠”模式的設計,抽象類TransformerFactory定義了兩個抽象方法newTransformer()和newTemplates()分別用於生成Transformer對象 和 Templates對象,其兩個子類進行了不同的實現,源碼如下(版本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);
    }
}

建造者模式

個人理解

該模式主要用於將複雜對象的構建過程分解成一個個簡單的步驟,或者分攤到多個類中進行構建,保證構建過程層次清晰,代碼不會過分臃腫,屏蔽掉了複雜對象內部的具體構建細節,其類圖結構如下所示。
在這裏插入圖片描述
該模式的主要角色如下:

  • 建造者接口(Builder):用於定義建造者構建產品對象的各種公共行爲,主要分爲 建造方法 和 獲取構建好的產品對象;
  • 具體建造者(ConcreteBuilder):實現上述接口方法;
  • 導演(Director):通過調用具體建造者創建需要的產品對象;
  • 產品(Product):被建造的複雜對象。

其中的導演角色不必瞭解產品類的內部細節,只提供需要的信息給建造者,由具體建造者處理這些信息(這個處理過程可能會比較複雜)並完成產品構造,使產品對象的上層代碼與產品對象的創建過程解耦。建造者模式將複雜產品的創建過程分散到不同的構造步驟中,這樣可以對產品創建過程實現更加精細的控制,也會使創建過程更加清晰。每個具體建造者都可以創建出完整的產品對象,而且具體建造者之間是相互獨立的, 因此係統就可以通過不同的具體建造者,得到不同的產品對象。當有新產品出現時,無須修改原有的代碼,只需要添加新的具體建造者即可完成擴展,這符合“開放一封閉” 原則。

典型的範例 StringBuilder和StringBuffer

相信在拼SQL語句時大家一定經常用到StringBuffer和StringBuilder這兩個類,它們就用到了建造者設計模式,源碼如下(版本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;
    
    /**
     * Creates an AbstractStringBuilder of the specified capacity.
     */
    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;
    }
}

Mybatis中的範例

MyBatis 的初始化過程使用了建造者模式,抽象類 BaseBuilder 扮演了“建造者接口”的角色,對一些公用方法進行了實現,並定義了公共屬性。XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder 等實現類扮演了“具體建造者”的角色,分別用於解析mybatis-config.xml配置文件、映射配置文件 以及 SQL節點。Configuration 和 SqlSessionFactoryBuilder 則分別扮演了“產品” 和 “導演”的角色。即,SqlSessionFactoryBuilder 使用了 BaseBuilder建造者組件 對複雜對象 Configuration 進行了構建。

BaseBuilder組件的設計與上面標準的建造者模式是有很大不同的,BaseBuilder的建造者模式主要是爲了將複雜對象Configuration的構建過程分解的層次更清晰,將整個構建過程分解到多個“具體構造者”類中,需要這些“具體構造者”共同配合才能完成Configuration的構造,單個“具體構造者”不具有單獨構造產品的能力,這與StringBuilder及StringBuffer是不同的。

個人理解的構建者模式 其核心就是用來構建複雜對象的,比如 mybatis 對 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();
  }
}


public class XMLConfigBuilder extends BaseBuilder {

  /** 標識是否已經解析過 mybatis-config.xml 配置文件 */
  private boolean parsed;
  /** 用於解析 mybatis-config.xml 配置文件 */
  private final XPathParser parser;
  /** 標識 <environment> 配置的名稱,默認讀取 <environment> 標籤的 default 屬性 */
  private String environment;
  /** 負責創建和緩存 Reflector 對象 */
  private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();

  public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    // 在 mybatis-config.xml 配置文件中查找<configuration>節點,並開始解析
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

  private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      // 解析<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);
      // read it after objectFactory and objectWrapperFactory issue #631
      // 解析<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);
    }
  }
}


public class XMLMapperBuilder extends BaseBuilder {

  private final XPathParser parser;
  private final MapperBuilderAssistant builderAssistant;
  private final Map<String, XNode> sqlFragments;
  private final String resource;

  public void parse() {
    // 判斷是否已經加載過該映射文件
    if (!configuration.isResourceLoaded(resource)) {
      // 處理<mapper>節點
      configurationElement(parser.evalNode("/mapper"));
      // 將 resource 添加到 Configuration.loadedResources 集合中保存,
      // 它是 HashSet<String> 類型的集合,其中記錄了已經加載過的映射文件
      configuration.addLoadedResource(resource);
      // 註冊 Mapper 接口
      bindMapperForNamespace();
    }
    // 處理 configurationElement() 方法中解析失敗的<resultMap>節點
    parsePendingResultMaps();
    // 處理 configurationElement() 方法中解析失敗的<cache-ref>節點
    parsePendingCacheRefs();
    // 處理 configurationElement() 方法中解析失敗的 SQL 語句節點
    parsePendingStatements();
  }

  private void configurationElement(XNode context) {
    try {
      // 獲取<mapper>節點的 namespace 屬性,若 namespace 屬性爲空,則拋出異常
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      // 設置 MapperBuilderAssistant 的 currentNamespace 字段,記錄當前命名空間
      builderAssistant.setCurrentNamespace(namespace);
      // 解析<cache-ref>節點
      cacheRefElement(context.evalNode("cache-ref"));
      // 解析<cache>節點
      cacheElement(context.evalNode("cache"));
      // 解析<parameterMap>節點,(該節點 已廢棄,不再推薦使用)
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      // 解析<resultMap>節點
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      // 解析<sql>節點
      sqlElement(context.evalNodes("/mapper/sql"));
      // 解析<select>、<insert>、<update>、<delete>等SQL節點
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }
}


public class XMLStatementBuilder extends BaseBuilder {

  private final MapperBuilderAssistant builderAssistant;
  private final XNode context;
  private final String requiredDatabaseId;

  public void parseStatementNode() {
    // 獲取 SQL 節點的 id 以及 databaseId 屬性,若其 databaseId屬性值與當前使用的數據庫不匹配,
    // 則不加載該 SQL 節點;若存在相同 id 且 databaseId 不爲空的 SQL 節點,則不再加載該 SQL 節點
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");

    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }

    // 根據 SQL 節點的名稱決定其 SqlCommandType
    String nodeName = context.getNode().getNodeName();
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // 在解析 SQL 語句之前,先處理其中的<include>節點
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);

    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);

    // 處理<selectKey>節點
    processSelectKeyNodes(id, parameterTypeClass, langDriver);

    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }

    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    String resultType = context.getStringAttribute("resultType");
    Class<?> resultTypeClass = resolveClass(resultType);
    String resultMap = context.getStringAttribute("resultMap");
    String resultSetType = context.getStringAttribute("resultSetType");
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    if (resultSetTypeEnum == null) {
      resultSetTypeEnum = configuration.getDefaultResultSetType();
    }
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    String resultSets = context.getStringAttribute("resultSets");

    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }
}


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);
  }
}

有被“讀過哪些知名的開源項目源碼?”這種問題所困擾過嗎?加入我們,一起通讀互聯網公司主流框架及中間件源碼,成爲強大的“源碼獵人”,目前開放的有 Spring 系列框架、Mybatis 框架、Netty 框架,及Redis中間件等,讓我們一起開拓新的領地,揭開這些源碼的神祕面紗。本項目主要用於記錄框架及中間件源碼的閱讀經驗、個人理解及解析,希望能夠使閱讀源碼變成一件更簡單有趣,且有價值的事情,抽空更新中…(如果本項目對您有幫助,請watch、star、fork 素質三連一波,鼓勵一下作者,謝謝)地址:
https://github.com/doocs/source-code-hunter

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章