重构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)和面向切面编程 ...
随机推荐
- loadrunner监控linux服务器
参考http://www.cnblogs.com/yangxia-test/archive/2012/11/27/2790771.html http://www.cnblogs.com/candle8 ...
- Parameter Passing / Request Parameters in JSF 2.0 (转)
This Blog is a compilation of various methods of passing Request Parameters in JSF (2.0 +) (1) f:vi ...
- berkeley db replica机制 - 消息处理
repmgr_method.c, __repmgr_start_int()repmgr_method.c, __repmgr_start_msg_threads()repmgr_msg.c, __re ...
- nginx实时记录请求状态信息( ngx_realtime_request_module)
cd /usr/local/src/ wget "http://nginx.org/download/nginx-1.4.2.tar.gz" tar -xzvf nginx.tar ...
- 原来在linux上切换jdk的版本是这么简单
上次在linux上切换jdk版本的时候,还配置了半天的环境变量,今天又查了一下,原来是这么的简单 1. 查看相应的jdk是否在 ubuntu的jdk菜单里,查看: (输全哦) update-alter ...
- webview使用总结及注意事项
1 网页 调用后台java代码 ,后台处理 一 网页上click事件 <a href="javascript:;" onclick="window.JsNative ...
- 图解,为多个oracle数据库下添加ArcSde实例
最开始肯定要先建一个oracle数据库,我假设名称为dbgis 1, 2, 3, 不重新指定就会出现这个错误,因为以前有sde.dbf文件了 4, 5, 6, 7, 8, 如果以前授权成功过就会出现这 ...
- Java中类继承、接口实现的一些细节(长期更新)
前言 在Java中,子类继承父类,类实现接口是属于常识性的内容了,作为一个Java程序员应该也比较熟悉.不过子类继承父类,类实现接口中还是有一些小细节值得注意一下,本文就从个人工作.学习中入手,总结一 ...
- 原创教程:《metasploit新手指南》介绍及下载
原创教程:<metasploit新手指南>介绍及下载 1.1 作者简介 这份教程并不是“玄魂工作室”原创,但是我还是要力推给大家.相比那些一连几年都在问“我怎么才能入门”的人而言,我们更欣 ...
- JavaScript使用DeviceOne开发实战(六)点墨真实案例
qq群里的yan用户开发的App,基本完工大家可以看看 安装二维码是 QQ群:365443130