Bootstrap

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

在实践中学习才是更古不变的道理,那些最无意义的如玩具般娇小的代码只是为了能够演示基本概念,所以才需要我们去学习开源框架,学习优秀的代码。这次我以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提供两种实现, 分别是和。

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

DataSourceFactory

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

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

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

负责创建

负责创建

如果需要产生新的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在解析配置文件时,会将"POOLED"字符串映射成工厂类,然后创建工厂实例,再通过这个工厂对象获取对象,最终将对象放入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工厂类时会解析配置文件。看一下具体代码片段(略去了很多无关代码,具体可以看):

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方法便是解析


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

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

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

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

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

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

稍后再写吧