前言
新開一個坑,爲了學習一下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;
//模擬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());
}
}
}
映射器代理工廠實現:
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
源碼