在文章: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