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

202 阅读5分钟

在实践中学习才是更古不变的道理,那些最无意义的如玩具般娇小的代码只是为了能够演示基本概念,所以才需要我们去学习开源框架,学习优秀的代码。这次我以 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 创建实际的对象,其中"反射"在此体现出它无比强大的功能和威力。而且也正因为有了多态,才使这一切成为了可能。所以为了创建更加通用性的代码,我们需要面向接口编程或者面向父类编程

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

稍后再写吧