一、回顧 Mapper 代理寫法
思考?個問題,通常的Mapper接口我們都沒有實現(xiàn)的方法卻可以使用蔼水,是為什么呢?
答案很簡單:動態(tài)代理
開始之前介紹?下 MyBatis 初始化時對接口的處理:MapperRegistry 是 Configuration 中的?個屬性录肯,它內部維護?個 HashMap 用于存放 mapper 接口的工廠類趴腋,每個接口對應?個工廠類
/**
* mapper代理方式
*/
@Test
public void test2() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = factory.openSession();
// 使用JDK動態(tài)代理對mapper接口產生代理對象
IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);
//代理對象調用接口中的任意方法,執(zhí)行的都是動態(tài)代理中的invoke方法
List<User> all = mapper.findAll();
for (User user : all) {
System.out.println(user);
}
}
mappers 標簽中可以配置接口的包路徑嘁信,或者某個具體的接口類
<!--引入映射配置文件-->
<mappers>
<package name="com.wujun.mapper"/>
</mappers>
二于样、初始化 Mapper
- 之前在說 Mybatis源碼剖析 -- 初始化過程(傳統(tǒng)方式)的時候,提到過
XMLConfigBuilder.parseConfiguration(XNode root)
這個方法里面會對xml進行解析潘靖,那么 mappers 的解析也在里面// 解析 <mappers /> 標簽 mapperElement(root.evalNode("mappers"));
private void mapperElement(XNode parent) throws Exception { if (parent != null) { // 遍歷子節(jié)點 for (XNode child : parent.getChildren()) { // 如果是 package 標簽穿剖,則掃描該包 if ("package".equals(child.getName())) { // 獲取 <package> 節(jié)點中的 name 屬性 String mapperPackage = child.getStringAttribute("name"); // 從指定包中查找 mapper 接口,并根據 mapper 接口解析映射配置 configuration.addMappers(mapperPackage); // 如果是 mapper 標簽卦溢, } else { // 獲得 resource糊余、url、class 屬性 String resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); // resource 不為空单寂,且其他兩者為空贬芥,則從指定路徑中加載配置 if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); // 獲得 resource 的 InputStream 對象 InputStream inputStream = Resources.getResourceAsStream(resource); // 創(chuàng)建 XMLMapperBuilder 對象 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); // 執(zhí)行解析 mapperParser.parse(); // url 不為空,且其他兩者為空宣决,則通過 url 加載配置 } else if (resource == null && url != null && mapperClass == null) { ErrorContext.instance().resource(url); // 獲得 url 的 InputStream 對象 InputStream inputStream = Resources.getUrlAsStream(url); // 創(chuàng)建 XMLMapperBuilder 對象 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); // 執(zhí)行解析 mapperParser.parse(); // mapperClass 不為空蘸劈,且其他兩者為空,則通過 mapperClass 解析映射配置 } else if (resource == null && url == null && mapperClass != null) { // 獲得 Mapper 接口 Class<?> mapperInterface = Resources.classForName(mapperClass); // 添加到 configuration 中 configuration.addMapper(mapperInterface); // 以上條件不滿足尊沸,則拋出異常 } else { throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } } } } }
- 威沫,調用
configuration.addMapper(mapperPackage)
方法將包名或者接口傳入 mapper 注冊表贤惯,存入一個名叫 knownMappers 的 HashMap 中,key就是接口的類型棒掠,value就是針對這個接口所生成的代理對象/** * Mapper 注冊表 * * @author Clinton Begin * @author Eduardo Macarron * @author Lasse Voss */ public class MapperRegistry { /** * MyBatis Configuration 對象 */ private final Configuration config; /** * MapperProxyFactory 的映射 * * KEY:Mapper 接口 */ //這個類中維護一個HashMap存放MapperProxyFactory private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
- 接著看
getMapper()
的源碼public <T> T getMapper(Class<T> type, SqlSession sqlSession) { // 獲得 MapperProxyFactory 對象 final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); // 不存在孵构,則拋出 BindingException 異常 if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } /// 通過動態(tài)代理工廠生成實例。 try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } }
public T newInstance(SqlSession sqlSession) { // 創(chuàng)建了JDK動態(tài)代理的invocationHandler接口的實現(xiàn)類mapperProxy final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache); // 調用了重載方法 return newInstance(mapperProxy); }
protected T newInstance(MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy); }
-
invoke()
方法源碼烟很,其實本質上還是調用了 sqlSession 里面的增刪改查方法public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { // 如果是 Object 定義的方法颈墅,直接調用 if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } // 獲得 MapperMethod 對象 final MapperMethod mapperMethod = cachedMapperMethod(method); // 重點在這:MapperMethod最終調用了執(zhí)行的方法 return mapperMethod.execute(sqlSession, args); }
public Object execute(SqlSession sqlSession, Object[] args) { Object result; //判斷mapper中的方法類型,最終調用的還是SqlSession中的方法 switch (command.getType()) { case INSERT: { // 轉換參數 Object param = method.convertArgsToSqlCommandParam(args); // 執(zhí)行 INSERT 操作 // 轉換 rowCount result = rowCountResult(sqlSession.insert(command.getName(), param)); break; } case UPDATE: { // 轉換參數 Object param = method.convertArgsToSqlCommandParam(args); // 轉換 rowCount result = rowCountResult(sqlSession.update(command.getName(), param)); break; } case DELETE: { // 轉換參數 Object param = method.convertArgsToSqlCommandParam(args); // 轉換 rowCount result = rowCountResult(sqlSession.delete(command.getName(), param)); break; } case SELECT: // 無返回雾袱,并且有 ResultHandler 方法參數恤筛,則將查詢的結果,提交給 ResultHandler 進行處理 if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; // 執(zhí)行查詢谜酒,返回列表 } else if (method.returnsMany()) { result = executeForMany(sqlSession, args); // 執(zhí)行查詢叹俏,返回 Map } else if (method.returnsMap()) { result = executeForMap(sqlSession, args); // 執(zhí)行查詢,返回 Cursor } else if (method.returnsCursor()) { result = executeForCursor(sqlSession, args); // 執(zhí)行查詢僻族,返回單個對象 } else { // 轉換參數 Object param = method.convertArgsToSqlCommandParam(args); // 查詢單條 result = sqlSession.selectOne(command.getName(), param); if (method.returnsOptional() && (result == null || !method.getReturnType().equals(result.getClass()))) { result = Optional.ofNullable(result); } } break; case FLUSH: result = sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + command.getName()); } // 返回結果為 null ,并且返回類型為基本類型屡谐,則拋出 BindingException 異常 if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) { throw new BindingException("Mapper method '" + command.getName() + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ")."); } // 返回結果 return result; }