工厂模式(二)MyBatis 中展示的简单的工厂模式

用户头像
LSJ
关注
发布于: 2020 年 05 月 26 日

在实践中学习才是更古不变的道理,那些最无意义的如玩具般娇小的代码只是为了能够演示基本概念,所以才需要我们去学习开源框架,学习优秀的代码。这次我以MyBatis框架中的DataSource模块作为例子来更深入的探讨工厂模式。



DataSource



DataSource是一个用于获取jdbc连接(Connection)的接口,众所周知,项目中有了Connection对象才能和数据库打交道,而Connection又是如此珍贵的一项资源,所以如何获取Connection成了至关重要的一个环节。每个框架都有自己的一套实现方式,为了让所有的已存在的或者即将产生的实现方式在项目中成为可能,DataSource接口便产生了



此接口定义了两个方法:



public interface DataSource extends CommonDataSource, Wrapper {
Connection getConnection() throws SQLException;
Connection getConnection(String username, String password)
throws SQLException;
}



这里的DataSource接口在工厂模式里扮演的角色就是Product接口,所有的具体产品类都实现这个接口。MyBatis提供两种实现, 分别是PooledDataSourceUnpooledDataSource



不同的实现类对于如何获取Connection对象,定义了不同的实现方式。比如每次调用UnpooledDataSource.getConnection()方法都是直接创建一个新的连接;而使用PooledDataSource则是从连接池中取出已经创建好的空闲的连接



DataSourceFactory



MyBatis同样提供了工厂接口:DataSourceFactory,针对于上面提到的两个DataSource实现类,MyBatis分别定义了两个工厂实现类UnpooledDataSourceFactoryPooledDataSourceFactory



不同的工厂实现类负责创建不同类型的DataSource。



public interface DataSourceFactory {
void setProperties(Properties props);
DataSource getDataSource();
}



UnpooledDataSourceFactory负责创建UnpooledDataSource



PooledDataSourceFactory负责创建PooledDataSource



如果需要产生新的DataSource实现,只需要添加对应的工厂实现类,新数据源就可以被MyBatis使用而不必修改已有的代码。符合开放-封闭原则。除此之外工厂方法会向调用者隐藏具体的产品类的实例化细节。

如下图所示,这就是DataSourceFactory工厂方法的典型应用 

从这个图片可以看到和我们上节讲的用法完全一致。



MyBatis源码还是比较轻量级,整体上看不是很难理解。本篇主要为了讲工厂模式,所以具体的内部实现细节不展开,感兴趣的读者可以自行查看源码。上面讲的就是工厂模式中最主要的组成部分——被分离出来的变化的部分。那么我们其实还需要看一下MyBatis在哪里使用了这个工厂类,如何使用?以及如何做到在不修改代码的情况下使用新的工厂对象和新的DataSource实现(透明的切换实现方式)



其实这也是大部分web框架实现的基础功能。暴露出扩展点供使用者自定义实现方式或者和第三方框架集成。



到这儿为止,工厂模式就可以结束了。不过此处仍可以做一下延伸。我们可以看看MyBatis框架是如何做的



Mybatis创建DataSourceFactory实例



在MyBatis中,,我们可以通过调用Mapper接口中的方法去执行与之关联的SQL语句。示例代码例如下:



try(final SqlSession sqlSession = MyBatisSqlSessionFactory.openSession()){
final StudentMapper studentMapper =sqlSession.getMapper(StudentMapper.class);
return studentMapper.findAllStudents();
}



其实在创建SqlSession对象时,MyBatis会根据mybatis-config.xml配置文件里的配置项去创建DataSourceFactory实例。具体要创建的是哪个工厂实例是通过配置文件里的参数去指定。比如下面的配置代码片段:



<environments default="development">
<environment id="development"> <transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
...
</dataSource>
</environment>
</environments>



MyBatis在解析配置文件时,会将"POOLED"字符串映射成PooledDataSourceFactory工厂类,然后创建工厂实例,再通过这个工厂对象获取PooledDataSource对象,最终将PooledDataSource对象放入MyBatis的Configuration对象中。



当第一次创建SqlSessionFactory对象时,就会解析mybatis-config.xml配置文件



Configuration 是 MyBatis 初始化过程的核心对象, MyBatis 中几乎全部的配置信息会保存到 Configuration 对象中。 Configuration 对象是在 MyBatis 初始化过程中创建且是全局唯一的, 也有人称它是一个 All-In-One 配置对象



下面看一下具体的代码片段:



/** 这是我们创建SqlSessionFactory和SqlSession的工具类 */
public class MyBatisSqlSessionFactory {
private static SqlSessionFactory sqlSessionFactory;
public static SqlSessionFactory getSqlSessionFactory() {
if (sqlSessionFactory == null) {
InputStream inputStream;
try {
inputStream = Resources.getResourceAsStream("mybatis/mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) { e.printStackTrace(); }
}
return sqlSessionFactory;
}
public static SqlSession openSession() {
return getSqlSessionFactory().openSession();
}
}



在上面说过:第一次创建SqlSessionFactory工厂类时会解析mybatis-config.xml配置文件。看一下具体代码片段(略去了很多无关代码,具体可以看XMLConfigBuilder.environmentsElement):



private void environmentsElement(XNode context) throws Exception {
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
}
}
}
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).getDeclaredConstructor().newInstance();
factory.setProperties(props);
return factory;
}



environmentsElement方法调用dataSourceElement方法拿到具体DataSourceFactory。dataSourceElement方法便是解析



<dataSource type="POOLED">



元素,然后获取到字符串"POOLED",根据这个值再通过TypeAliasRegistry获取关联的Class对象。



// MyBatis事先将这两物进行了绑定
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);



写到这里总体流程基本就走完了。



为了弄明白工厂模式,不得不展示一大堆代码。但是抛开这些代码,单纯对于工厂模式的核心概念来讲,本篇的内容相比于上一篇来讲并没有产生新事物。唯一不同的地方就在于如何在使用工厂对象。



在上篇文章的例子中,都是我们自己手动创建不同的工厂对象然后传递给某些方法,但是作为一个框架来说是不需要手动去做这些事情的。我们唯一要做的就是填写配置文件,然后系统在运行时,自动扫描解析配置文件,根据具体的值生成不同的Class对象,在通过Class创建实际的对象,其中"反射"在此体现出它无比强大的功能和威力。而且也正因为有了多态,才使这一切成为了可能。所以为了创建更加通用性的代码,我们需要面向接口编程或者面向父类编程



以上便是关于简单工厂模式的介绍。其实还有一种我认为更复杂又更高级一点的工厂模式:泛型工厂。



稍后再写吧



发布于: 2020 年 05 月 26 日 阅读数: 54
用户头像

LSJ

关注

微笑面对每一天 2018.11.11 加入

一个具有N年编程功力却早已拥有2N年工作经验的boy

评论

发布
暂无评论
工厂模式(二)MyBatis中展示的简单的工厂模式