您好,登錄后才能下訂單哦!
本篇文章為大家展示了怎么從 Spring及Mybatis框架源碼中學習設計模式,內容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。
設計模式是解決問題的方案,從大神的代碼中學習對設計模式的使用,可以有效提升個人編碼及設計代碼的能力。本系列博文用于總結閱讀過的框架源碼(Spring 系列、Mybatis)及 JDK 源碼中 所使用過的設計模式,并結合個人工作經驗,重新理解設計模式。
主要看一下創建型的幾個設計模式,即:單例模式、各種工廠模式及建造者模式。
確保某個類只有一個實例,并提供該實例的獲取方法。實際應用很多,不管是框架、JDK 還是實際的項目開發,但大都會使用“餓漢式”或“枚舉”來實現單例。“懶漢式”也有一些應用,但通過“雙檢鎖機制”來保證單例的實現很少見。
最簡單的就是 使用一個私有構造函數、一個私有靜態變量,以及一個公共靜態方法的方式來實現。懶漢式、餓漢式等簡單實現就不多 BB 咯,這里強調一下雙檢鎖懶漢式實現的坑,以及枚舉方式的實現吧,最后再結合 spring 源碼 擴展一下單例 bean 的實現原理。
1. 雙檢鎖實現的坑
/*** @author 云之君* 雙檢鎖 懶漢式,實現線程安全的單例* 關鍵詞:JVM指令重排、volatile、反射攻擊*/public class Singleton3 { /** * 這里加個volatile進行修飾,也是本單例模式的精髓所在。 * 下面的 instance = new Singleton3(); 這行代碼在JVM中其實是分三步執行的: * 1、分配內存空間; * 2、初始化對象; * 3、將instance指向分配的內存地址。 * 但JVM具有指令重排的特性,實際的執行順序可能會是1、3、2,導致多線程情況下出問題, * 使用volatile修飾instance變量 可以 避免上述的指令重排 * tips:不太理解的是 第一個線程在執行第2步之前就已經釋放了鎖嗎?導致其它線程進入synchronized代碼塊 * 執行 instance == null 的判斷? * 回答:第一個線程在執行第2步之前就已經釋放了鎖嗎?(沒有)。如果不使用volatile修飾instance變量,那么其他線程進來的時候,看到的instance就有可能不是null的,因為已經執行了第3步,那么此時這個線程(執行 return instance;)使用的instance是一個沒有初始化的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; }}
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 是使用 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 中數據源 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 最主要的幾個實現類內容都比較多,代碼就不貼出來咯,感興趣的同學可以到我的源碼分析專題中看到詳細解析。
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 的 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):被建造的復雜對象。
其中的導演角色不必了解產品類的內部細節,只提供需要的信息給建造者,由具體建造者處理這些信息(這個處理過程可能會比較復雜)并完成產品構造,使產品對象的上層代碼與產品對象的創建過程解耦。建造者模式將復雜產品的創建過程分散到不同的構造步驟中,這樣可以對產品創建過程實現更加精細的控制,也會使創建過程更加清晰。每個具體建造者都可以創建出完整的產品對象,而且具體建造者之間是相互獨立的, 因此系統就可以通過不同的具體建造者,得到不同的產品對象。當有新產品出現時,無須修改原有的代碼,只需要添加新的具體建造者即可完成擴展,這符合“開放一封閉” 原則。
相信在拼 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 的初始化過程使用了建造者模式,抽象類 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框架源碼中學習設計模式,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。