前言
新开一个坑,为了学习一下MyBatis
的源码,写代码是次要的,主要为了吸收一下其中的思想和手法。
目的
关联对象接口和映射类的问题,把 DAO
接口使用代理类,包装映射操作。
知识点
- 动态代理
- 简单工厂模式
InvocationHandler
接口的使用
实现
既然是简易的MyBatis
编写,那肯定得看下源码了;先来一波回忆,MyBatis
的使用:
忘记的朋友可以看下之前写的 MyBatis
手册: blog.csdn.net/weixin_4390...
www.cnblogs.com/xbhog/p/172...
ini
@Test
public void testMybatis(){
//加载核心配置文件
try {
//字符流加载配置
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
//创建sql连接工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
//创建session连接,设置true,默认提交事务
SqlSession sqlSession = sqlSessionFactory.openSession(true);
//反射获取类对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userAll = mapper.getUserAll();
System.out.println(userAll);
} catch (IOException e) {
e.printStackTrace();
}
}
代码中可以看到,接口和映射器有关系的地方应该是sqlSession.getMapper(UserMapper.class);
点进去。
先看映射器工厂类:MapperProxyFactory
这部分就是我们本次实现的地方。
typescript
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethodInvoker> getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
简易映射器类图:
MapperProxy
代理类来代理需要使用的接口,为了方便后续的维护扩展,在代理类上加一层代理工厂类MapperProxyFactory
使用代理类的好处是: 动态代理允许 MyBatis 在不修改接口实现的情况下,为接口方法提供自定义的行为。这意味着开发者只需要定义接口和 SQL 映射,而无需编写接口的实现类。这种设计促进了关注点分离,使得数据访问逻辑(SQL)与业务逻辑更加清晰,MapperProxy 能够在运行时将 SQL 语句与接口方法动态绑定,这样,MyBatis 可以根据接口方法的签名和注解或 XML 配置来执行相应的 SQL 操作。
使用简单工厂模式的好处: 实现代码复用和模块化,对代理逻辑和接口使用解耦,灵活性高,不改变公共接口等。
总之都是为了项目的高度灵活、扩展、复用等。
通过上述的分析,现在进行代码编写的流程比较明朗了。
代理类的实现:
typescript
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
//模拟SqlSession
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 {
//检查一个方法是否来自特定的类或者是一系列接口中的一个
//是Object自身的方法,就没必要代理,直接调用就行
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
//需要匹配的是类名+方法名
return "你的被代理了!" + sqlSession.get(mapperInterface.getName() + "." + method.getName());
}
}
}
映射器代理工厂实现:
arduino
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);
}
}
执行流程如下:
测试
ini
public void testApp() {
MapperProxyFactory<IUserDao> proxyFactory = new MapperProxyFactory<>(IUserDao.class);
Map<String,String> sqlSession = new HashMap<>();
sqlSession.put("com.xbhog.IUserDao.getUserName","模拟执行 Mapper.xml 中 SQL 语句的操作:查询用户姓名");
IUserDao userDao = proxyFactory.newInstance(sqlSession);
String userName = userDao.getUserName("100001");
System.out.println(userName);
}
总结
- 通过追溯
MyBatis
中的源码,明确本文的主要的内容 - 明确目标类的依赖关系
- 代码实现简易效果
- 明确执行流程
- 测试代码,符合预期结果
参考&学习
mp.weixin.qq.com/s/G3fZES2Fv...
AI
大模型辅助
MyBatis
源码