1. 程式人生 > 其它 >mybatis 原始碼解析之如何實現 mapper 動態代理

mybatis 原始碼解析之如何實現 mapper 動態代理

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