工厂模式(Factory Method Pattern)是一种创建型设计模式
,它定义了一个用于创建对象的接口
,但允许子类决定实例化哪一个类。工厂模式让类的实例化推迟到子类中进行。
工厂方法模式通常包含以下几个角色:
// 抽象产品
interface Product {
void use();
}
// 具体产品A
class ConcreteProductA implements Product {
@Override
public void use() {
System.out.println("Using ConcreteProductA");
}
}
// 具体产品B
class ConcreteProductB implements Product {
@Override
public void use() {
System.out.println("Using ConcreteProductB");
}
}
// 抽象工厂
abstract class Creator {
abstract Product factoryMethod();
}
// 具体工厂A
class ConcreteCreatorA extends Creator {
@Override
Product factoryMethod() {
return new ConcreteProductA();
}
}
// 具体工厂B
class ConcreteCreatorB extends Creator {
@Override
Product factoryMethod() {
return new ConcreteProductB();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Creator creatorA = new ConcreteCreatorA();
Product productA = creatorA.factoryMethod();
productA.use();
Creator creatorB = new ConcreteCreatorB();
Product productB = creatorB.factoryMethod();
productB.use();
}
}
工厂方法模式是实现对象创建的一种非常灵活的方式,它提供了一种封装机制,使得代码能够独立于具体类的创建。这种模式在实际开发中非常常见,特别是在需要根据不同情况创建不同实例时。
为了方便用户对接框架,我们要实现扫描指定包路径下的数据库操作DAO接口和Mapper映射器的关联,并且提供一个工厂,用来对外统一提供SqlSession的数据库交互服务.
为了用户方便对接框架, 我们想把整个工程包下关于数据库操作的 DAO 接口与 Mapper 映射器关联起来,就需要包装一个可以扫描包路径的完成映射的注册器类。
完善上一章节简化的 SqlSession,由 SqlSession 定义数据库处理接口和获取 Mapper 对象的操作,并把它交给映射器代理类进行使用。
有了 SqlSession 以后,你可以把它理解成一种功能服务,还需要这个功能服务提供一个工厂,来对外统一提供这类服务。比如我们在 Mybatis 中非常常见的操作,开启一个 SqlSession。
整体设计如图 :
MapperRegistry
,自动扫描包下接口并把每个接口类映射的代理类全部存入映射器代理的 HashMap 缓存中。ClassScanner.scanPackage
扫描包路径,调用 addMapper
方法,给接口类创建 MapperProxyFactory
映射器代理类,并写入到 knownMappers 的 HashMap 缓存中。public class MapperRegistry {
/**
* 将已注册的映射器代理加入到 HashMap.
*/
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
/**
* 获取映射器代理.
*/
public <T> T getMapper(Class<T> type, SqlSession sqlSession){
MapperProxyFactory<?> mapperProxyFactory = knownMappers.get(type);
if (mapperProxyFactory == null){
throw new RuntimeException("Type " + type + " is not known to the MapperRegistry.");
}
try{
return (T) mapperProxyFactory.newInstance(sqlSession);
}catch (Exception e){
throw new RuntimeException("Error getting mapper instance. Cause: " + e, e);
}
}
/**
* 扫描指定路径,注册映射器代理.
*/
public <T> void addMapper(String packageName){
Set<Class<?>> mapperSet = ClassScanner.scanPackage(packageName);
for(Class<?> mapperClass : mapperSet){
addMapper(mapperClass);
}
}
/**
* 注册映射器代理.
*/
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new RuntimeException("Type " + type + " is already known to the MapperRegistry.");
}
knownMappers.put(type, new MapperProxyFactory<>(type));
}
}
/**
* 判断是否已经注册.
* @param type
* @return
* @param
*/
public <T> boolean hasMapper(Class<T> type){
return knownMappers.containsKey(type);
}
}
SqlSession
public interface SqlSession {
/**
* 根据给定的执行SQL获取一条记录的封装对象.
* @param statement
* @return
* @param
*/
<T> T selectOne(String statement);
/**
* 根据给定的执行SQL获取一条记录的封装对象,带参数.
* @param statement
* @param parameter
* @return
* @param
*/
<T> T selectOne(String statement, Object parameter);
/**
* 获取映射器.
* 这里使用了泛型,使得类型安全.
* @param type
* @param
* @return
*/
<T> T getMapper(Class<T> type);
}
DefaultSqlSession
SqlSessionFactory
public interface SqlSessionFactory {
/**
* 打开一个会话.
*/
SqlSession openSession();
}
DefaultSqlSessionFactory
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final MapperRegistry mapperRegistry;
public DefaultSqlSessionFactory(MapperRegistry mapperRegistry) {
this.mapperRegistry = mapperRegistry;
}
@Override
public SqlSession openSession() {
return new DefaultSqlSession(mapperRegistry);
}
}
定义一个数据库接口 IUserDao
IUserDao
public interface IUserDao {
String queryUserName(String uid);
Integer queryUserAge(String uid);
}
public class ApiTest {
private Logger logger = LoggerFactory.getLogger(ApiTest.class);
// 测试mapperProxyFactory
@Test
public void testMapperProxyFactory(){
// 1.注册Mapper
MapperRegistry registry = new MapperRegistry();
registry.addMapper("cn.suwg.mybatis.test.dao");
// 2.从SqlSessionFactory获取Session
DefaultSqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(registry);
SqlSession sqlSession = sqlSessionFactory.openSession();
// 3.获取映射器对象
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
// 4.测试验证
String result = userDao.queryUserName("10001");
logger.info("测试结果:{}, 年龄:{}",result, 28);
}
}
从输出的结果来看,我们实现的Mybatis手写 ORM 框架中,已经完成了代理类的注册和使用过程。
参考书籍:《手写Mybatis渐进式源码实践》
书籍源代码:https://github.com/fuzhengwei/book-small-mybatis