重构Mybatis与Spring集成的SqlSessionFactoryBean(2)
三、代码重构
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重构成如下结构:

上面的数字即对应重构步骤中的操作,不包括最后一步的提取组件工厂。
重构Mybatis与Spring集成的SqlSessionFactoryBean(2)的更多相关文章
- 重构Mybatis与Spring集成的SqlSessionFactoryBean(1)
一般来说,修改框架的源代码是极其有风险的,除非万不得已,否则不要去修改.但是今天却小心翼翼的重构了Mybatis官方提供的与Spring集成的SqlSessionFactoryBean类,一来是抱着试 ...
- Mybatis与Spring集成时都做了什么?
Mybatis是java开发者非常熟悉的ORM框架,Spring集成Mybatis更是我们的日常开发姿势. 本篇主要讲Mybatis与Spring集成所做的事情,让读过本文的开发者对Mybatis和S ...
- mybatis与Spring集成(Aop整合PagerAspect插件)
目的: Mybatis与spring集成 Aop整合pagehelper插件 Mybatis与spring集成 导入pom依赖 <?xml version="1.0" enc ...
- Mybatis与Spring集成(易百教程)
整个Mybatis与Spring集成示例要完成的步骤如下: 1.示例功能描述 2.创建工程 3.数据库表结构及数据记录 4.实例对象 5.配置文件 6.测试执行,输出结果 1.示例功能描述 在本示例中 ...
- MyBatis与Spring集成
beans.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="htt ...
- 深入浅出mybatis之与spring集成
目录 写在前面 详细配置 1.dataSource(数据源) 2.sqlSessionFactory(Session工厂) 3.Mapper(映射器) 4.TransactionManager(事务管 ...
- MyBatis从入门到精通(第9章):Spring集成MyBatis(中)
MyBatis从入门到精通(第9章):Spring集成MyBatis(中) 框架(Framework)是整个或部分系统的可重用设计,表现为一组抽象构件及构件实例间交互的方法.应该将应用自身的设计和具体 ...
- 由“单独搭建Mybatis”到“Mybatis与Spring的整合/集成”
在J2EE领域,Hibernate与Mybatis是大家常用的持久层框架,它们各有特点,在持久层框架中处于领导地位. 本文主要介绍Mybatis(对于较小型的系统,特别是报表较多的系统,个人偏向Myb ...
- Java Persistence with MyBatis 3(中国版) 第五章 与Spring集成
MyBatis-Spring它是MyBatis子模块框.它用来提供流行的依赖注入框架Spring无缝集成. Spring框架是一个基于依赖注入(Dependency Injection)和面向切面编程 ...
随机推荐
- java中FileInputStream和FileOutputStream对图片操作的例子
package a.ab; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.F ...
- 使用Prism6 建立 Windows 10 通用程序.
使用Prism6 建立 Windows 10 通用程序. 目标: 使用prism6,建立Windows 通用程序项目. 1, 解决方案—添加新建项目—通用—空白应用—输入名称—确定—确定 2 ,引用上 ...
- mysql用户权限设置
1.创建新用户 通过root用户登录之后创建 >> grant all privileges on *.* to testuser@localhost identified by &quo ...
- day10---异步I/O,gevent协程
协程 协程,又称微线程,纤程.英文名Coroutine.一句话说明什么是线程:协程是一种用户态的轻量级线程. 协程拥有自己的寄存器上下文和栈.协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来 ...
- 查询指定网段可用IP脚本
1.脚本内容: #vi hhh 添加以下内容: #!/bin/bash read -p "Please keyin the network segment: (e.g. 10.88.1) & ...
- DLL和LIB的一些知识点(网上搜集)
关于lib和dll的区别如下:(1)lib是编译时用到的,dll是运行时用到的.如果要完成源代码的编译,只需要lib:如果要使动态链接的程序运行起来,只需要dll.(2)如果有dll文件,那么lib一 ...
- 重建Windows 8的图标缓存
Windows 8的图标缓存路径与Win7不同,重置方法如下: rem 关闭explorer.exe taskkill /f /im explorer.exe attrib -h -i %userpr ...
- 初学python里的yield send next
今天看书的时候突然看到这个想起来一直没有怎么使用过send和next试了一下 发现了一个诡异的问题 import math def get_primes(start): while 1 : if is ...
- 跨平台开源通讯组件elastic communication
elastic communication是基于c#开发支持.net和mono的通讯组件(简称EC),EC的主要目的简化mono和.net下的通讯开发难度,通过EC可以非常快速地开发基于mono和.n ...
- 在线教学、视频会议 Webus Fox(1)文本、语音、视频聊天及电子白板基本用法
Webus Fox是基于网页的在线教学.视频会议软件,不用安装,直接使用.它提供文本.语音.视频聊天,文件共享.电子白板等功能. 1. 登录 访问 http://flash.webus.cn/#,用自 ...