前言
新开一个坑,为了学习一下MyBatis
的源码,写代码是次要的,主要为了吸收一下其中的思想和手法。
目的
关联对象接口和映射类的问题,把 DAO
接口使用代理类,包装映射操作。
知识点
- 动态代理
- 简单工厂模式
InvocationHandler
接口的使用
实现
既然是简易的MyBatis
编写,那肯定得看下源码了;先来一波回忆,MyBatis
的使用:
忘记的朋友可以看下之前写的MyBatis
手册: https://blog.csdn.net/weixin_43908900/article/details/129780085
https://www.cnblogs.com/xbhog/p/17258782.html
@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
这部分就是我们本次实现的地方。
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 操作。
使用简单工厂模式的好处: 实现代码复用和模块化,对代理逻辑和接口使用解耦,灵活性高,不改变公共接口等。
总之都是为了项目的高度灵活、扩展、复用等。
通过上述的分析,现在进行代码编写的流程比较明朗了。
代理类的实现:
public class MapperProxy<T> implements InvocationHandler, Serializable {private static final long serialVersionUID = -6424540398559729838L;//模拟SqlSessionprivate Map<String, String> sqlSession;private final Class<T> mapperInterface;public MapperProxy(Map<String, String> sqlSession, Class<T> mapperInterface) {this.sqlSession = sqlSession;this.mapperInterface = mapperInterface;}@Overridepublic 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());}}}
映射器代理工厂实现:
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);}}
执行流程如下:
测试
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
中的源码,明确本文的主要的内容 - 明确目标类的依赖关系
- 代码实现简易效果
- 明确执行流程
- 测试代码,符合预期结果
参考&学习
https://mp.weixin.qq.com/s/G3fZES2FvNQK8JLnd9Hx9w
AI
大模型辅助
MyBatis
源码