mybatis 原始碼解析之如何實現 mapper 動態代理
阿新 • • 發佈:2022-03-04
mybatis 底層是基於 JDK 動態代理來實現 mapper 動態代理的,所以我們先來看看 JDK 動態代理。
1、回顧JDK 動態代理
1.1、定義介面 & 實現類
public interface Subject { int add(int x, int y); } public class RealSubject implements Subject { @Override public int add(int x, int y) { System.out.println("計算兩個數的和,結果為:" + (x + y));return x + y; } }
1.2、定義代理類,實現動態代理介面
public class MyProxy implements InvocationHandler{ private Object targer; public MyProxy(Object targer) { this.targer = targer; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("before method do something"); Object result = method.invoke(targer, args); System.out.println("after method do something"); return result; } }
1.3、獲取代理類
public class Client { public static void main(String[] args) { Subject target = new RealSubject(); MyProxy h= new MyProxy(target); Subject proxy = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(), new Class[]{Subject.class}, h); int result = proxy.add(1,2); } }
簡單三步即可獲取到代理物件,呼叫代理物件的任何方法都會執行 invoke 方法。
從以上程式碼可以看出,JDK 動態代理只能代理介面,而且還需要定義實現類,那 mybatis 是如何做到不需要實現類就輕鬆獲取到代理物件的呢。
2、mybatis 動態代理
一般我們會這麼獲取代理類。
1、DefaultSqlSession.java public <T> T getMapper(Class<T> type) { return configuration.<T>getMapper(type, this); } 2、Configuration.java public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); } 3、MapperRegistry.java public <T> T getMapper(Class<T> type, SqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } } 4、MapperProxyFactory.java public T newInstance(SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } protected T newInstance(MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); }
一路追蹤程式碼下來發現是先獲取MapperProxyFactory
,然後通過引數構建出代理物件即可。
重點就是獲取 MapperProxyFactory 類,從程式碼看是從 knownMappers 中獲取的,來看看這個 knownMappers 到底是個啥。
在MapperRegistry類中是這樣定義的
Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>()原來是一個 Class 為 key,MapperProxyFactory 為 value 的 map 物件。可見是針對不同的 mapper 型別會有不一樣的 MapperProxyFactory。那是在什麼時候放進去該 map 的呢?檢視原始碼得知在 MapperRegistry 類中 54 行有這麼一行程式碼。
knownMappers.put(type, new MapperProxyFactory<T>(type));
而該方法源頭是在 XMLConfigBuilder.mapperElement(XNode)
開始一步步被呼叫的,而 XMLConfigBuilder.mapperElement(XNode)
是在讀取配置檔案時[XMLConfigBuilder 118行]被呼叫的,也就是說載入配置檔案的時候就把 key - value 注入到 knownMappers 中去了,以便後續呼叫。
至此,mapper 介面是如何被 mybatis 代理的一目瞭然。
同時可以看出獲取到的代理物件就是 MapperProxy 類,該類實現 InvocationHandler 介面和 invoke 方法。獲取到了 MapperProxy 物件之後,在呼叫該代理物件的介面方法時,只要在這個物件的 invoke 方法裡執行相應的 SQL 語句並將結果集返回不就達到我們的目的了嗎,因此也就不需要介面實現類了。
摘自:https://www.jianshu.com/p/93e18dcc7c10