Mybatis源碼解析,一步一步從淺入深(六):映射代理類的獲取

在文章:Mybatis源碼解析,一步一步從淺入深(二):按步驟解析源碼中咱們提到了兩個問題:html

  1,爲何在之前的代碼流程中歷來沒有addMapper,而這裏卻有getMapper?java

  2,UserDao明明是咱們定義的一個接口類,根本沒有定義實現類,那這個userMapper是什麼?是mybatis自動爲咱們生成的實現類嗎?sql

  爲了更好的解釋着兩個問題,咱們須要從新認識Configuration這個類。設計模式

  可是在這以前,你須要瞭解一個概念(設計模式):JAVA設計模式-動態代理(Proxy)示例及說明。不然你可能對接下來的流程一頭霧水。數組

一,再次認識Configuration緩存

public class Configuration {
  //映射註冊表
  protected MapperRegistry mapperRegistry = new MapperRegistry(this);

  // 獲取映射註冊表
  public MapperRegistry getMapperRegistry() {
    return mapperRegistry;
  }

  //添加到映射註冊表
  public void addMappers(String packageName, Class<?> superType) {
    mapperRegistry.addMappers(packageName, superType);
  }
  //添加到映射註冊表
  public void addMappers(String packageName) {
    mapperRegistry.addMappers(packageName);
  }
  //添加到映射註冊表
  public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
  }
  //從映射註冊表中獲取
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }
  //判斷映射註冊表中是否存在
  public boolean hasMapper(Class<?> type) {
    return mapperRegistry.hasMapper(type);
  }
}

  MapperRegistry源碼:mybatis

public class MapperRegistry {

  private Configuration config;
  //映射緩存 鍵:類對象,值:映射代理工廠
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

  public MapperRegistry(Configuration config) {
    this.config = config;
  }
  //從映射註冊表中獲取  
  @SuppressWarnings("unchecked")
  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);
    }
  }
  //判斷映射註冊表中是否存在  
  public <T> boolean hasMapper(Class<T> type) {
    return knownMappers.containsKey(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));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        // 處理接口類(例如UserDao)中的註解
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

  /**
   * @since 3.2.2
   */
  //獲取緩存中全部的Key,而且是不可修改的
  public Collection<Class<?>> getMappers() {
    return Collections.unmodifiableCollection(knownMappers.keySet());
  }

  /**
   * @since 3.2.2
   */
  //添加到映射註冊表
  public void addMappers(String packageName, Class<?> superType) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    for (Class<?> mapperClass : mapperSet) {
      addMapper(mapperClass);
    }
  }

  /**
   * @since 3.2.2
   */
  //添加到映射註冊表
  public void addMappers(String packageName) {
    addMappers(packageName, Object.class);
  }
}

  在方法getMappers中用到了Collections.unmodifiableCollection(knownMappers.keySet());,若是你不瞭解,能夠查閱:Collections.unmodifiableMap,Collections.unmodifiableList,Collections.unmodifiableSet做用及源碼解析app

  在瞭解了這兩個類以後,就來解決第一個問題:1,爲何在之前的代碼流程中歷來沒有addMapper,而這裏卻有getMapper?post

二,addMapper和getMapperui

  1,關於addMapper,在文章Mybatis源碼解析,一步一步從淺入深(五):mapper節點的解析中,不知道你們有沒有意識到,我少了一個部分沒有解讀:

  

  對了,就是第四部分的:綁定已經解析的命名空間

  代碼:bindMapperForNamespace();

  是的,addMapper就是在這個方法中用到的。可是前提是,你須要瞭解java的動態代理。來看看源碼:

private void bindMapperForNamespace() {
    //獲取當前命名空間(String:com.zcz.learnmybatis.dao.UserDao)
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
        // 使用類加載器加載,加載類,獲取類對象
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        //ignore, bound type is not required
      }
      if (boundType != null) {
        //判斷映射註冊表中是否存在
        if (!configuration.hasMapper(boundType)) {
          // Spring may not know the real resource name so we set a flag
          // to prevent loading again this resource from the mapper interface
          // look at MapperAnnotationBuilder#loadXmlResource
          // 添加到已經解析的緩存
          configuration.addLoadedResource("namespace:" + namespace);
          // 添加到映射這測表
          configuration.addMapper(boundType);
        }
      }
    }
  }

  看到了吧,就在最後一行代碼。可是這裏並非簡單的保存了一個類對象,而是在MapperRegistry中進行了進一步的處理:

 1 //添加到映射註冊表
 2   public <T> void addMapper(Class<T> type) {
 3     if (type.isInterface()) {
 4       if (hasMapper(type)) {
 5         throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
 6       }
 7       boolean loadCompleted = false;
 8       try {
 9      // 在這裏,保存的是new MapperProxyFactory實例對象。
10         knownMappers.put(type, new MapperProxyFactory<T>(type));
11         // It's important that the type is added before the parser is run
12         // otherwise the binding may automatically be attempted by the
13         // mapper parser. If the type is already known, it won't try.
14         // 處理接口類(例如UserDao)中的註解
15         MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
16         parser.parse();
17         loadCompleted = true;
18       } finally {
19         if (!loadCompleted) {
20           knownMappers.remove(type);
21         }
22       }
23     }
24   }

  在第10行,保存的是映射代理工廠(MapperProxyFactory)的實例對象。到這裏addMapper就解釋清楚了。接下來看看getMapper方法。

  2,getMapper

    調用的地方:在文章Mybatis源碼解析,一步一步從淺入深(二):按步驟解析源碼第三步中。

    代碼:configuration.<T>getMapper(type, this);

      type:是UserDao.class

      this:SqlSession的實例化對象

    從第一部分Configuration中能夠發現,Configuration又調用了MapperRegistry的getMapper方法

 1 //從映射註冊表中獲取  
 2   @SuppressWarnings("unchecked")
 3   public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
 4     final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
 5     if (mapperProxyFactory == null)
 6       throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
 7     try {
 8       return mapperProxyFactory.newInstance(sqlSession);
 9     } catch (Exception e) {
10       throw new BindingException("Error getting mapper instance. Cause: " + e, e);
11     }
12   }

  從代碼的第4行能夠清晰的看到,或者根據類對象從緩存Map中,獲取到了addMapper中保存的MapperProxyFactory對象實例。可是並無將這個對象實例直接返回,而是經過調用的MapperProxyFactory的newInstance方法返回的一個UserDao實現類。接下來咱們i就解釋一下第二個問題:2,UserDao明明是咱們定義的一個接口類,根本沒有定義實現類,那這個userMapper是什麼?是mybatis自動爲咱們生成的實現類嗎?

三,映射代理類的實現

  看過JAVA設計模式-動態代理(Proxy)示例及說明這篇文章的同窗應該知道這個問題的答案了,userMapper是一個代理類對象實例。是經過映射代理工廠(MapperProxyFactory)的方法newInstance方法獲取的。

  可是在這裏mybatis有一個很巧妙的構思,使得這個的動態代理的使用方法和文章JAVA設計模式-動態代理(Proxy)示例及說明中的使用方法有些許不一樣。

  不妨在你的腦海中回顧一下JAVA設計模式-動態代理(Proxy)示例及說明中實現動態代理的關鍵因素:

    1,一個接口

    2,實現了接口的類

    3,一個調用處理類(構造方法中要傳入2中的類的實例對象)

    4,調用Proxy.newProxyInstance方法獲取代理類實例化對象

  帶着這個印象,咱們來分析一下mybatis是怎麼實現動態代理的。既然userMapper是經過映射代理工廠(MapperProxyFactory)生產出來的,那麼咱們就看看它的源碼: 

 1 //映射代理工廠  
 2 public class MapperProxyFactory<T> {
 3   // 接口類對象(UserDao.class)    
 4   private final Class<T> mapperInterface;
 5   // 對象中的方法緩存
 6   private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
 7 
 8   //構造器
 9   public MapperProxyFactory(Class<T> mapperInterface) {
10     //爲接口類對象賦值
11     this.mapperInterface = mapperInterface;
12   }
13 
14   public Class<T> getMapperInterface() {
15     return mapperInterface;
16   }
17 
18   public Map<Method, MapperMethod> getMethodCache() {
19     return methodCache;
20   }
21   
22   // 實例化映射代理類
23   @SuppressWarnings("unchecked")
24   protected T newInstance(MapperProxy<T> mapperProxy) {
25     return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
26   }
27   
28   // 實例化映射代理類
29   public T newInstance(SqlSession sqlSession) {
30     final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
31     return newInstance(mapperProxy);
32   }
33 
34 }

  咱們調用的newInstance方法就是第29行的方法。而後這個方法又調用了24行的方法。咱們來看25行的代碼,是否是很熟悉?Proxy.newProxyInstance(類加載器,接口類對象數組,實現了InvocationHandler的對象實例 mapperProxy),到這裏映射代理類的實例化已經解釋清楚了,也就是解決了第二個問題,接下來咱們擴展一下:

  如今咱們尚未看到MapperProxy類的源碼,可是咱們能夠大膽的猜想,類MapperProxy必定是實現了InvocationHandler接口,而且也必定實現了Invoke方法:

 1 // 映射代理  
 2 public class MapperProxy<T> implements InvocationHandler, Serializable {
 3   private static final long serialVersionUID = -6424540398559729838L;
 4   private final SqlSession sqlSession;
 5   private final Class<T> mapperInterface;
 6   private final Map<Method, MapperMethod> methodCache;
 7   
 8   //構造方法
 9   public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
10     this.sqlSession = sqlSession;
11     this.mapperInterface = mapperInterface;
12     this.methodCache = methodCache;
13   }
14   //代理類調用的時候執行的方法
15   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
16     // 檢查Method方法所在的類是不是Object
17     if (Object.class.equals(method.getDeclaringClass())) {
18       try {
19         return method.invoke(this, args);
20       } catch (Throwable t) {
21         throw ExceptionUtil.unwrapThrowable(t);
22       }
23     }
24     // 應用緩存
25     final MapperMethod mapperMethod = cachedMapperMethod(method);
26     //執行查詢
27     return mapperMethod.execute(sqlSession, args);
28   }
29   
30   //應用緩存
31   private MapperMethod cachedMapperMethod(Method method) {
32     MapperMethod mapperMethod = methodCache.get(method);
33     if (mapperMethod == null) {
34       mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
35       methodCache.put(method, mapperMethod);
36     }
37     return mapperMethod;
38   }
39 }

  那麼,mybatis使用動態代理的方式跟文章JAVA設計模式-動態代理(Proxy)示例及說明使用動態代理的方式,有哪些不一樣呢?

  1,一個接口(UserDao)

  2,實現了接口的類(沒有)

  3,一個調用處理類(構造方法中要傳入2中的類的實例對象)(2中沒有,天然這裏也不會傳入對象)

  4,調用Proxy.newProxyInstance方法獲取代理類實例化對象(有的)

  

  若是你實實在在的明白了JAVA設計模式-動態代理(Proxy)示例及說明這篇文中所敘述的內容,相信這篇文章不難理解。

  好了,mybatis源碼解析到這裏,已經基本接近尾聲了,繼續探索吧:Mybatis源碼解析,一步一步從淺入深(二):按步驟解析源碼

 


原創不易,轉載請聲明出處:http://www.javashuo.com/article/p-kivujuri-de.html 

相關文章
相關標籤/搜索