创建简单的映射器代理工厂

一、前言

着急和快,是最大的障碍!

慢下来,慢下来,只有慢下来,你才能看到更全的信息,才能学到更扎实的技术。而那些满足你快的短篇内容虽然有时候更抓眼球,但也容易把人在技术学习上带偏,总想着越快越好。

二、设计

在设计一个ORM框架的过程中,首先要考虑怎么把用户定义的数据库操作接口,xml配置的sql语句、数据库三者联系起来。

所以可以使用代理,封装一个复杂的流程为接口对象的实现类。

  • 首先提供一个映射器的代理实现类 MapperProxy,通过代理类包装对数据库的操作,目前我们本章节会先提供一个简单的包装,模拟对数据库的调用。
  • 之后对 MapperProxy 代理类,提供工厂实例化操作 MapperProxyFactory#newInstance,为每个 IDAO 接口生成代理类。这块其实用到的就是一个简单工厂模式

三、实现

映射器代理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* 映射器代理类
*
* @author:zzc
* @date: 2022/4/10
*/
public class MapperProxy<T> implements InvocationHandler, Serializable {


private static final long serialVersionUID = 3754159323506184630L;

private Map<String, String> sqlSession;
private final Class<T> mapperInterface;

public MapperProxy(Map<String, String> sqlSession, Class<T> mapperInterface) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
}

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
return "你的被代理了!" + sqlSession.get(mapperInterface.getName() + "." + method.getName());
}
}
}
  • 通过实现 InvocationHandler#invoke 代理类接口,封装操作逻辑的方式,对外接口提供数据库操作对象。
  • 目前我们这里只是简单的封装了一个 sqlSession 的 Map 对象,可以想象成所有的数据库语句操作,都是通过接口名称+方法名称作为key,操作作为逻辑的方式进行使用的。那么在反射调用中则获取对应的操作直接执行并返回结果即可。
  • 另外这里要注意如果是 Object 提供的 toString、hashCode 等方法是不需要代理执行的,所以添加 Object.class.equals(method.getDeclaringClass()) 判断。

代理类工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 代理类工厂
*
* @author:zzc
* @date: 2022/4/10
*/
public class MapperProxyFactory<T> {

private final Class<T> mapperInterface;

public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}

public T newInstance(Map<String, String> sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface);
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
}

}
  • 工厂操作相当于把代理的创建给封装起来了,如果不做这层封装,那么每一个创建代理类的操作,都需要自己使用 Proxy.newProxyInstance 进行处理,那么这样的操作方式就显得比较麻烦了。

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* @author:zzc
* @date: 2022/4/10
*/
public class ApiTest {

private Logger logger = LoggerFactory.getLogger(ApiTest.class);

@Test
public void test_MapperProxyFactory() {
MapperProxyFactory<IUserDao> factory = new MapperProxyFactory<>(IUserDao.class);

Map<String, String> sqlSession = new HashMap<>();
sqlSession.put("com.zzc.hankz.test.dao.IUserDao.queryUserName", "模拟执行 Mapper.xml 中 SQL 语句的操作:查询用户姓名");
sqlSession.put("com.zzc.hankz.test.dao.IUserDao.queryUserAge", "模拟执行 Mapper.xml 中 SQL 语句的操作:查询用户年龄");
IUserDao userDao = factory.newInstance(sqlSession);

userDao.toString();

String res = userDao.queryUserName("10001");
logger.info("测试结果:{}", res);
}
}
1
2
3
4
5
6
7
8
9
10
/**
* @author:zzc
* @date: 2022/4/10
*/
public interface IUserDao {

String queryUserName(String uid);

Integer queryUserAge(String uid);
}
1
16:11:43.067 [main] INFO com.zzc.hankz.ApiTest - 测试结果:你的被代理了!模拟执行 Mapper.xml 中 SQL 语句的操作:查询用户姓名
  • 在单测中创建 MapperProxyFactory 工厂,并手动给 sqlSession Map 赋值,这里的赋值相当于模拟数据库中的操作。
  • 接下来再把赋值信息传递给代理对象实例化操作,这样就可以在我们调用具体的 DAO 方法时从 sqlSession 中取值了。

总结

  • 对 Mybatis 框架中的数据库 DAO 操作接口和映射器通过代理类的方式进行链接,这一步也是 ORM 框架里非常核心的部分。
  • 在框架实现方面引入简单工厂模式包装代理类,屏蔽创建细节