Mybatis框架--動態代理源碼分析

Mybatis整體框架和運行原理圖

在這裏插入圖片描述

在這裏插入圖片描述

JDK動態代理:

代理是一種模式,提供了對目標對象的間接訪問方式,即通過代理訪問目標對象。如此便於在目標實現的基礎上增加額外的功能操作,前攔截,後攔截等,以滿足自身的業務需求,同時代理模式便於擴展目標對象功能的特點也爲多人所用。

JDK動態代理

定義接口

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;
    }
}

定義代理類,實現動態代理接口

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;
}
}

獲取代理類

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 方法。

Mybatis動態代理源碼分析:

從以上代碼可以看出,JDK 動態代理只能代理接口,而且還需要定義實現類,那 mybatis 是如何做到不需要實現類就輕鬆獲取到代理對象的呢?(Mybatis 之所以不用我們自己實現接口的實例,根本原因就在於它採用了反射機制以及動態代理來代理我們的接口。)

mybatis中的getMapper方法的實現方式理解爲動態代理方法的實現方式理解爲動態代理

mybatis中的getMapper的調用爲:sqlSession.getMapper(OrdersMapper1.class);

mapper是如何添加進去的呢?

DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(OrdersMapper1.class);// 添加Mapper接口
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);

等價於

String resource = "mybatis-config.xml"; // xml內容就不貼了
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

我們後去sqlSessionFactory採用以上方式
分析上面代碼生成sqlSessionFactory過程有這麼一行:
sqlSession類:

configuration.addMapper(OrdersMapper1.class);// 添加Mapper接口

addMapper 方法的代碼實現
Configuration類:

  public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
  }

mapper 實際上被添加到 mapperRegissry 中。繼續跟進代碼:

MapperRegistry類:

final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);


public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {// 只添加接口
      if (hasMapper(type)) {// 不允許重複添加
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<T>(type));// 注意這裏
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

所執行的configuration.addMapper(BlogMapper.class); 其實最終被放到了HashMap中,其名爲knownMappers ,knowMappers是MapperRegistry 類的一個私有屬性,它是一個HashMap 。其Key 爲當前Class對象,value 爲一個MapperProxyFactory 實例。

總結一下: 諸如OrdersMapper1之類的Mapper接口被添加到了MapperRegistry 中的一個HashMap中。並以 Mapper 接口的 Class 對象作爲 Key , 以一個攜帶Mapper接口作爲屬性的MapperProxyFactory 實例作爲value 。MapperProxyFacory從名字來看,好像是一個工廠,用來創建Mapper Proxy的工廠。

從源碼上我們可以看到getMapper方法會去調用Configuration類的getMapper方法。

DefaultSqlSession類:

 public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
  }

Configuration類裏面存放了所有關於XML文件的配置信息。從參數上我們可以看到他要我們傳入一個Class類型。這已經可以看到後面一定要用到反射機制和動態生成相應的類實例

Configuration類:

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

代碼所示,這裏的 getMapper 調用了 configuration.getMapper , 這一步操作其實最終是調用了MapperRegistry,而此前我們已經知道,MapperRegistry是存放了一個HashMap的

MapperRegistry類:

  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();


  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);
    }
  }

我們調用的session.getMapper(OrdersMapper1.class);最終會到達上面這個方法,這個方法根據OrdersMapper1的class對象,以它爲key在knowMappers 中找到了對應的value —— MapperProxyFactory(BlogMapper) 對象,然後調用這個對象的newInstance()方法。根據這個名字,我們就能猜到這個方法是創建了一個對象

public class MapperProxyFactory<T> { //映射器代理工廠

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();

  //刪除不必要代碼

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    //使用了JDK自帶的動態代理生成映射器代理類的對象 
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}

最終是通過Proxy.newProxyInstance產生了一個OrdersMapper1的代理對象。Mybatis 爲了完成 Mapper 接口的實現,運用了代理模式。具體是使用了JDK動態代理,這個Proxy.newProxyInstance方法生成代理類的三個要素是:
ClassLoader —— 指定當前接口的加載器即可
OrdersMapper1 —— 這當前被代理的接口是什麼
MapperProxy——代理類是什麼

代理模式中,代理類(MapperProxy)中才真正的完成了方法調用的邏輯。我們貼出MapperProxy的代碼

public class MapperProxy<T> implements InvocationHandler, Serializable {
//實現了InvocationHandler接口

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
    //代理以後,所有Mapper的方法調用時,都會調用這個invoke方法
      try {
        return method.invoke(this, args);// 注意1
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method); // 使用了緩存
    //執行CURD
    return mapperMethod.execute(sqlSession, args);// 注意2
  }

  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }

}

Blog blog =mapper.getOrdersById(3); 實際上最後是會調用這個MapperProxy的invoke方法。這段代碼中,if 語句先判斷,我們想要調用的方法是否來自Object類,這裏的意思就是,如果我們調用toString()方法,那麼是不需要做代理增強的,直接還調用原來的method.invoke()就行了。只有調用getOrdersById之類的方法的時候,才執行增強的調用——即mapperMethod.execute(sqlSession, args);
而mapperMethod.execute(sqlSession, args);這句最終就會執行增刪改查了

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        }
       // 刪除部分代碼      
      }
  // 刪除部分代碼
    return result;
  }

我們分析了 Mapper接口是如何註冊的,Mapper接口是如何產生動態代理對象的,Maper接口方法最終是如何執行的。總結起來主要就是這幾個點:

  1. Mapper 接口在初始SqlSessionFactory 註冊的。
  2. Mapper 接口註冊在了名爲 MapperRegistry 類的 HashMap中, key = Mapper class value = 創建當前Mapper的工廠。
  3. Mapper 註冊之後,可以從SqlSession中get
  4. SqlSession.getMapper 運用了 JDK動態代理,產生了目標Mapper接口的代理對象。
  5. 動態代理的 代理類是 MapperProxy ,這裏邊最終完成了增刪改查方法的調用。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章