三、代碼重構
1、先使用Eclipse把buildSqlSessionFactory()方法中眾多的if換成小函數
protected SqlSessionFactory buildSqlSessionFactory() throws IOException { Configuration configuration; XMLConfigBuilder xmlConfigBuilder = null; if (this.configuration != null) { configuration = this.configuration; if (configuration.getVariables() == null) { configuration.setVariables(this.configurationProperties); } else if (this.configurationProperties != null) { configuration.getVariables().putAll(this.configurationProperties); } } else if (this.configLocation != null) { xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties); configuration = xmlConfigBuilder.getConfiguration(); } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug( "Property `configuration` or 'configLocation' not specified, using default MyBatis Configuration"); } configuration = new Configuration(); configuration.setVariables(this.configurationProperties); } doResolveObjectFactory(configuration); doResolveObjectWrapperFactory(configuration); doResolveVfs(configuration); doResolveTypeAliasesPackage(configuration); doResolveTypeAliases(configuration); doResolvePlugins(configuration); doResolveTypeHandlersPackage(configuration); doResolveTypeHandlers(configuration); doResolveDatabaseIdProvider(configuration); doResolveCache(configuration); doParseConfig(xmlConfigBuilder); doResolveTransactionFactory(); doResolveEnvironment(configuration); doParseSqlMapper(configuration); return this.sqlSessionFactoryBuilder.build(configuration); }
說明一下:
- 這里的重構全部使用Eclipse完成,操作步驟是選定需要重構的代碼,右鍵選擇Refactor—>Extract Method,然后輸入新的方法名,點擊OK完成
- 新方法名規則:全部使用do開頭,表示實際做某件事,對於解析XML的,使用doParse(如doParseConfig、doParseSqlMapper),其它的則使用doResolve為前綴
- 新方法一開始全部為private,但是為了后續擴展性,可以根據需要修改為protected
- 第一段的if語句,由於有兩個變量需要返回,直接使用Eclipse重構不成功,先保持不變
看其中一個重構提取的方法:
protected void doParseSqlMapper(Configuration configuration) throws NestedIOException { if (!isEmpty(this.mapperLocations)) { for (Resource mapperLocation : this.mapperLocations) { if (mapperLocation == null) { continue; } try { XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments()); xmlMapperBuilder.parse(); } catch (Exception e) { throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e); } finally { ErrorContext.instance().reset(); } if (LOGGER.isDebugEnabled()) { LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'"); } } } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found"); } } }
這里還可以再次實施重構:
protected void doParseSqlMapper(Configuration configuration) throws NestedIOException { if (!isEmpty(this.mapperLocations)) { for (Resource mapperLocation : this.mapperLocations) { if (mapperLocation == null) { continue; } doParserSqlMapperResource(configuration, mapperLocation); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'"); } } } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found"); } } } protected void doParserSqlMapperResource(Configuration configuration, Resource mapperLocation) throws NestedIOException { try { XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments()); xmlMapperBuilder.parse(); } catch (Exception e) { throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e); } finally { ErrorContext.instance().reset(); } }
2、對於第一段的if語句,添加一個內部類,用來包裝兩個變量,然后再重構,相關代碼如下:
private class ConfigurationWrapper{//定義一個內部類,包裝兩個變量 Configuration configuration; XMLConfigBuilder xmlConfigBuilder; } protected SqlSessionFactory buildSqlSessionFactory() throws IOException { ConfigurationWrapper wrapper = doGetConfigurationWrapper();//將獲取變量的代碼重構為單獨一個方法 Configuration configuration = wrapper.configuration;//從內部包裝對象中獲取需要的變量 XMLConfigBuilder xmlConfigBuilder = wrapper.xmlConfigBuilder; doResolveObjectFactory(configuration); doResolveObjectWrapperFactory(configuration); doResolveVfs(configuration); doResolveTypeAliasesPackage(configuration); doResolveTypeAliases(configuration); doResolvePlugins(configuration); doResolveTypeHandlersPackage(configuration); doResolveTypeHandlers(configuration); doResolveDatabaseIdProvider(configuration); doResolveCache(configuration); doParseConfig(xmlConfigBuilder); doResolveTransactionFactory(); doResolveEnvironment(configuration); doParseSqlMapper(configuration); return this.sqlSessionFactoryBuilder.build(configuration); } protected ConfigurationWrapper doGetConfigurationWrapper() throws IOException { ConfigurationWrapper wrapper = new ConfigurationWrapper(); Configuration configuration; XMLConfigBuilder xmlConfigBuilder = null; if (this.configuration != null) { configuration = this.configuration; if (configuration.getVariables() == null) { configuration.setVariables(this.configurationProperties); } else if (this.configurationProperties != null) { configuration.getVariables().putAll(this.configurationProperties); } } else if (this.configLocation != null) { xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties); configuration = xmlConfigBuilder.getConfiguration(); } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug( "Property `configuration` or 'configLocation' not specified, using default MyBatis Configuration"); } configuration = new Configuration(); configuration.setVariables(this.configurationProperties); } wrapper.configuration = configuration; wrapper.xmlConfigBuilder = xmlConfigBuilder; return wrapper; }
這里的新方法由於需要返回值,將其命名為doGetConfigurationWrapper。
3、再預留一些方法,給子類覆蓋留下空間
protected SqlSessionFactory buildSqlSessionFactory() throws IOException { ConfigurationWrapper wrapper = doGetConfigurationWrapper();//將獲取變量的代碼重構為單獨一個方法 Configuration configuration = wrapper.configuration;//從內部包裝對象中獲取需要的變量 XMLConfigBuilder xmlConfigBuilder = wrapper.xmlConfigBuilder; onBeforeConfigurationPropertiesSet(configuration);//設置Configuration對象屬性之前 doResolveObjectFactory(configuration); doResolveObjectWrapperFactory(configuration); doResolveVfs(configuration); doResolveTypeAliasesPackage(configuration); doResolveTypeAliases(configuration); doResolvePlugins(configuration); doResolveTypeHandlersPackage(configuration); doResolveTypeHandlers(configuration); doResolveDatabaseIdProvider(configuration); doResolveCache(configuration); onBeforeParseConfig(configuration);//解析mybatis-config.xml配置之前 doParseConfig(xmlConfigBuilder); doResolveTransactionFactory(); doResolveEnvironment(configuration); onBeforeParseSqlMapper(configuration);//解析sql-mapper.xml腳本配置之前 doParseSqlMapper(configuration); doCustomConfiguration(configuration);//其它個性化配置 return this.sqlSessionFactoryBuilder.build(configuration); } protected void doCustomConfiguration(Configuration configuration){//其它個性化配置,用於子類覆蓋 }
這里設置多少樁是就見仁見智了,一般來說,在每個相對獨立的任務前后添加一些事件即可。
另外,也可以將這些樁方法提取為一個接口,然后在Spring中注入這個接口的一個或多個實現類,相關代碼如下:
public interface ISqlSessionFactoryDecorate{ void onBeforeConfigurationPropertiesSet(Configuration configuration); void onBeforeParseConfig(Configuration configuration); void onBeforeParseSqlMapper(Configuration configuration); void doCustomConfiguration(Configuration configuration); } private Set<ISqlSessionFactoryDecorate> decorates; public Set<ISqlSessionFactoryDecorate> getDecorates() { return decorates; } public void setDecorates(Set<ISqlSessionFactoryDecorate> decorates) { this.decorates = decorates; } protected SqlSessionFactory buildSqlSessionFactory() throws IOException { ConfigurationWrapper wrapper = doGetConfigurationWrapper();//將獲取變量的代碼重構為單獨一個方法 Configuration configuration = wrapper.configuration;//從內部包裝對象中獲取需要的變量 XMLConfigBuilder xmlConfigBuilder = wrapper.xmlConfigBuilder; Set<ISqlSessionFactoryDecorate> decorates = getDecorates(); boolean hasDecorates = null != decorates && !decorates.isEmpty(); if(hasDecorates){ for(ISqlSessionFactoryDecorate decorate : decorates){ decorate.onBeforeConfigurationPropertiesSet(configuration);//設置Configuration對象屬性之前 } } doResolveObjectFactory(configuration); doResolveObjectWrapperFactory(configuration); doResolveVfs(configuration); doResolveTypeAliasesPackage(configuration); doResolveTypeAliases(configuration); doResolvePlugins(configuration); doResolveTypeHandlersPackage(configuration); doResolveTypeHandlers(configuration); doResolveDatabaseIdProvider(configuration); doResolveCache(configuration); if(hasDecorates){ for(ISqlSessionFactoryDecorate decorate : decorates){ decorate.onBeforeParseConfig(configuration);//解析mybatis-config.xml配置之前 } } doParseConfig(xmlConfigBuilder); doResolveTransactionFactory(); doResolveEnvironment(configuration); if(hasDecorates){ for(ISqlSessionFactoryDecorate decorate : decorates){ decorate.onBeforeParseSqlMapper(configuration);//解析sql-mapper.xml腳本配置之前 } } doParseSqlMapper(configuration); if(hasDecorates){ for(ISqlSessionFactoryDecorate decorate : decorates){ decorate.doCustomConfiguration(configuration);//其它個性化配置 } } return this.sqlSessionFactoryBuilder.build(configuration); }
這兩種方式各有優缺點,使用子類的方式需要繼承,但是可以訪問父類中的一些屬性和方法,而使用接口的方式,代碼相對獨立,邏輯比較清晰,可以實現多個不同邏輯的擴展,但是不能直接訪問原有類中的屬性,具體使用哪種方式,需要視情況而定。
4、添加一些獲取方法
剛剛說使用子類的方式有一個優勢就是可以直接訪問父類中的屬性和方法,但這只限於是protected和public級別的。我們看看SqlSessionFactoryBean這個類的OutLine:

可以看到,除了databaseIdProvider、vfs、cache這幾個屬性有get方法之外,其它的屬性都是private並且沒有提供get方法的。Mybatis為什么只給這三個屬性提供get方法?當然可以解釋為外界只需要訪問這三個屬性,然而在我看來,真正的原因其實是mybatis編碼的隨意性,起碼到目前為止,我根本不需要訪問這三個屬性,而configuration、dataSource、transactionFactory、objectFactory等屬性卻是需要訪問的,如果不做任何變更,那就只能通過反射的方式獲取了,但是這里我們的目的就是重構,那不妨添加這幾個屬性的get方法。
5、添加組件工廠
再看源碼,可以看到有很多地方直接使用new創建對象,把這些對象的創建提取出來,添加新的接口ISqlSessionComponentFactory,並編寫默認實現類:
(1)接口
public interface ISqlSessionComponentFactory { public SqlSessionFactoryBuilder newSqlSessionFactoryBuilder(); public XMLConfigBuilder newXMLConfigBuilder(InputStream inputStream, String environment, Properties props); public XMLMapperBuilder newXMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments); public Configuration newConfiguration(); public Environment newEnvironment(String id, TransactionFactory transactionFactory, DataSource dataSource); public TransactionFactory newTransactionFactory(); }
(2)默認實現
public class DefaultSqlSessionComponentFactory implements ISqlSessionComponentFactory{ @Override public SqlSessionFactoryBuilder newSqlSessionFactoryBuilder() { return new SqlSessionFactoryBuilder(); } @Override public XMLConfigBuilder newXMLConfigBuilder(InputStream inputStream, String environment, Properties props) { return new XMLConfigBuilder(inputStream, environment, props); } @Override public XMLMapperBuilder newXMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) { return new XMLMapperBuilder(inputStream, configuration, resource, sqlFragments); } @Override public Configuration newConfiguration() { return new Configuration(); } @Override public Environment newEnvironment(String id, TransactionFactory transactionFactory, DataSource dataSource) { return new Environment(id, transactionFactory, dataSource); } @Override public TransactionFactory newTransactionFactory() { return new SpringManagedTransactionFactory(); } }
(3)工廠支持類/靜態幫助類
組件工廠可能會用於多個地方,因此可以添加一個Support類,可注入工廠實現類,然后其它應用繼承這個Support類;也可以添加一個靜態幫助類:
public class SqlSessionComponetFactorys { private static ISqlSessionComponentFactory factory = new DefaultSqlSessionComponentFactory(); public static ISqlSessionComponentFactory getFactory() { return factory; } // 這里沒有設置為static方法,主要是便於在Spring配置文件中注入新的工廠接口實現類 public void setFactory(ISqlSessionComponentFactory factory) { if(null != factory){ SqlSessionComponetFactorys.factory = factory; } } public static SqlSessionFactoryBuilder newSqlSessionFactoryBuilder() { return factory.newSqlSessionFactoryBuilder(); } // 省略其它的方法 }
(4)應用,替換原來的new Xxx(),修改為SqlSessionComponetFactorys.newXxx()。
這里需要注意一點,看下面的情形:
private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
這種直接在類定義時就賦值的組件(需要初始化的類屬性),因為SqlSessionComponetFactorys中的工廠接口可能在Spring啟動時修改,因此不能簡單的替換,而應采用延遲創建的方式,比如修改成如下形式:
private SqlSessionFactoryBuilder sqlSessionFactoryBuilder; @Override public void afterPropertiesSet() throws Exception { notNull(dataSource, "Property 'dataSource' is required"); if(null == this.sqlSessionFactoryBuilder){ this.sqlSessionFactoryBuilder = SqlSessionComponetFactorys.newSqlSessionFactoryBuilder(); } //notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required"); state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null), "Property 'configuration' and 'configLocation' can not specified with together"); this.sqlSessionFactory = buildSqlSessionFactory(); }
最終,SqlSessionFactoryBean重構成如下結構:

上面的數字即對應重構步驟中的操作,不包括最后一步的提取組件工廠。
