重构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)和面向切面编程 ...
随机推荐
- Pointers and Dynamic Allocation of Memory
METHOD 1: Consider the case where we do not know the number of elements in each row at compile time, ...
- php数字补零的两种方法
在php中有两个函数——至少有两个是否有其他的我还不知道,能够实现数字补零,str_pad(),sprintf()详细如下 str_pad顾名思义这个函数是针对字符串来说的这个可以对指定的字符串填补任 ...
- Lua 栈的理解
提到C++与lua互调,不可不提栈. 栈是C++和Lua相互通讯的一个地方. 首先这个栈并不是传统意义上的栈(传统的栈需要放同一种数据类型,但在网上的某些资料说,每个栈元素是一个联合体). 栈从上向下 ...
- Mac OS X 安装Win7双系统
Mac10安装双系统 为了有一个纯净的开发环境,就在mac电脑中安装windows虚拟机.刚开始使用还很顺利,两个系统的交互很方便,mac用来下载.搜索和写笔记:windows纯开发.时间长了以后关机 ...
- velocity导出word报错解决
- mysql 备份软件 Xtrabackup 的 xtrabackup_binlog_pos_innodb和xtrabackup_binlog_info 文件区别
今天在操作 innobackupex 的时候,执行 change master to 的时候发现 xtrabackup_binlog_pos_innodb xtrabackup_binlog_i ...
- bootstrap-sidebar,后台边栏折叠功能
http://www.solutiisoft.com/bootstrap-sidebar/index.php
- 说说WeakReference弱引用
WeakReference弱引用概述 http://www.cnblogs.com/xrq730/p/4836700.html,关于Java的四种引用状态具体请参看此文 Java里一个对象obj被创建 ...
- 【C语言学习】《C Primer Plus》第11章 字符串和字符串函数
学习总结 1.字符串(character String)是以空字符串(\o)结尾的char数组. 2.gets()方法代表get String,它从系统的标准输入设备(通常是键盘)获取一个字符串,当字 ...
- CSS Font知识整理总结
1.什么是字体 字体是文字的外在形式,就是文字的风格,是文字的外衣.比如行书.楷书.草书,都是一种字体.同样一个字每个人写起来都会有差异,可以说每个人都有一套潜在的字体库.对于web页面来说,字体就是 ...