成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

美團面試:為什么就能直接調用userMapper接口的方法?

開發 前端
老規矩,先上案例代碼,這樣大家可以更加熟悉是如何使用的,看過Mybatis系列的小伙伴,對這段代碼差不多都可以背下來了。

[[361093]]

老規矩,先上案例代碼,這樣大家可以更加熟悉是如何使用的,看過Mybatis系列的小伙伴,對這段代碼差不多都可以背下來了。

哈哈~,有點夸張嗎?不夸張的,就這行代碼。

  1. public class MybatisApplication { 
  2.         public static final String URL = "jdbc:mysql://localhost:3306/mblog"
  3.         public static final String USER = "root"
  4.         public static final String PASSWORD = "123456"
  5.      
  6.         public static void main(String[] args) { 
  7.             String resource = "mybatis-config.xml"
  8.             InputStream inputStream = null
  9.             SqlSession sqlSession = null
  10.             try { 
  11.                 inputStream = Resources.getResourceAsStream(resource); 
  12.                 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 
  13.                 sqlSession = sqlSessionFactory.openSession(); 
  14.                 //今天主要這行代碼 
  15.                 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); 
  16.                 System.out.println(userMapper.selectById(1)); 
  17.      
  18.             } catch (Exception e) { 
  19.                 e.printStackTrace(); 
  20.             } finally { 
  21.                 try { 
  22.                     inputStream.close(); 
  23.                 } catch (IOException e) { 
  24.                     e.printStackTrace(); 
  25.                 } 
  26.                 sqlSession.close(); 
  27.             } 
  28.         } 

看源碼有什么用?

通過源碼的學習,我們可以收獲Mybatis的核心思想和框架設計,另外還可以收獲設計模式的應用。

前兩篇文章我們已經Mybatis配置文件解析到獲取SqlSession,下面我們來分析從SqlSession到userMapper:

  1. UserMapper userMapper = sqlSession.getMapper(UserMapper.class); 

前面那篇文章已經知道了這里的sqlSession使用的是默認實現類DefaultSqlSession。所以我們直接進入DefaultSqlSession的getMapper方法。

  1. //DefaultSqlSession中   
  2. private final Configuration configuration; 
  3. //type=UserMapper.class 
  4. @Override 
  5. public <T> T getMapper(Class<T> type) { 
  6.   return configuration.getMapper(type, this); 

這里有三個問題:

問題1:getMapper返回的是個什么對象?

上面可以看出,getMapper方法調用的是Configuration中的getMapper方法。然后我們進入Configuration中

  1. //Configuration中   
  2. protected final MapperRegistry mapperRegistry = new MapperRegistry(this); 
  3. ////type=UserMapper.class 
  4. public <T> T getMapper(Class<T> type, SqlSession sqlSession) { 
  5.     return mapperRegistry.getMapper(type, sqlSession); 

這里也沒做什么,繼續調用MapperRegistry中的getMapper:

  1. //MapperRegistry中 
  2. public class MapperRegistry { 
  3.   //主要是存放配置信息 
  4.   private final Configuration config; 
  5.   //MapperProxyFactory 的映射 
  6.   private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>(); 
  7.  
  8.   //獲得 Mapper Proxy 對象 
  9.   //type=UserMapper.class,session為當前會話 
  10.   public <T> T getMapper(Class<T> type, SqlSession sqlSession) { 
  11.     //這里是get,那就有add或者put 
  12.     final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); 
  13.     if (mapperProxyFactory == null) { 
  14.       throw new BindingException("Type " + type + " is not known to the MapperRegistry."); 
  15.     } 
  16.    try { 
  17.       //創建實例 
  18.       return mapperProxyFactory.newInstance(sqlSession); 
  19.     } catch (Exception e) { 
  20.       throw new BindingException("Error getting mapper instance. Cause: " + e, e); 
  21.     } 
  22.   } 
  23.    
  24.   //解析配置文件的時候就會調用這個方法, 
  25.   //type=UserMapper.class 
  26.   public <T> void addMapper(Class<T> type) { 
  27.     // 判斷 type 必須是接口,也就是說 Mapper 接口。 
  28.     if (type.isInterface()) { 
  29.         //已經添加過,則拋出 BindingException 異常 
  30.         if (hasMapper(type)) { 
  31.             throw new BindingException("Type " + type + " is already known to the MapperRegistry."); 
  32.         } 
  33.         boolean loadCompleted = false
  34.         try { 
  35.             //添加到 knownMappers 中 
  36.             knownMappers.put(type, new MapperProxyFactory<>(type)); 
  37.             //創建 MapperAnnotationBuilder 對象,解析 Mapper 的注解配置 
  38.             MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); 
  39.             parser.parse(); 
  40.             //標記加載完成 
  41.             loadCompleted = true
  42.         } finally { 
  43.             //若加載未完成,從 knownMappers 中移除 
  44.             if (!loadCompleted) { 
  45.                 knownMappers.remove(type); 
  46.             } 
  47.         } 
  48.     } 

MapperProxyFactory對象里保存了mapper接口的class對象,就是一個普通的類,沒有什么邏輯。

在MapperProxyFactory類中使用了兩種設計模式:

  1. 單例模式methodCache(注冊式單例模式)。
  2. 工廠模式getMapper()。

繼續看MapperProxyFactory中的newInstance方法。

  1. public class MapperProxyFactory<T> { 
  2.       private final Class<T> mapperInterface; 
  3.       private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>(); 
  4.      
  5.       public MapperProxyFactory(Class<T> mapperInterface) { 
  6.         this.mapperInterface = mapperInterface; 
  7.       } 
  8.      public T newInstance(SqlSession sqlSession) { 
  9.       //創建MapperProxy對象 
  10.       final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache); 
  11.       return newInstance(mapperProxy); 
  12.     } 
  13.     //最終以JDK動態代理創建對象并返回 
  14.      protected T newInstance(MapperProxy<T> mapperProxy) { 
  15.         return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); 
  16.     } 
  17.     } 

從代碼中可以看出,依然是穩穩的基于 JDK Proxy 實現的,而 InvocationHandler 參數是 MapperProxy 對象。

  1. //UserMapper 的類加載器 
  2. //接口是UserMapper 
  3. //h是mapperProxy對象 
  4. public static Object newProxyInstance(ClassLoader loader, 
  5.                                           Class<?>[] interfaces, 
  6.                                        InvocationHandler h){ 

問題2:為什么就可以調用他的方法?

上面調用newInstance方法時候創建了MapperProxy對象,并且是當做newProxyInstance的第三個參數,所以MapperProxy類肯定實現了InvocationHandler。

進入MapperProxy類中:

  1. //果然實現了InvocationHandler接口 
  2.   public class MapperProxy<T> implements InvocationHandler, Serializable { 
  3.    
  4.     private static final long serialVersionUID = -6424540398559729838L; 
  5.     private final SqlSession sqlSession; 
  6.     private final Class<T> mapperInterface; 
  7.     private final Map<Method, MapperMethod> methodCache; 
  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.     //調用userMapper.selectById()實質上是調用這個invoke方法 
  15.     @Override 
  16.     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
  17.       try { 
  18.         //如果是Object的方法toString()、hashCode()等方法   
  19.         if (Object.class.equals(method.getDeclaringClass())) { 
  20.           return method.invoke(this, args); 
  21.         } else if (method.isDefault()) { 
  22.           //JDK8以后的接口默認實現方法   
  23.           return invokeDefaultMethod(proxy, method, args); 
  24.         } 
  25.       } catch (Throwable t) { 
  26.         throw ExceptionUtil.unwrapThrowable(t); 
  27.       } 
  28.       //創建MapperMethod對象 
  29.       final MapperMethod mapperMethod = cachedMapperMethod(method); 
  30.       //下一篇再聊 
  31.       return mapperMethod.execute(sqlSession, args); 
  32.     } 
  33.   } 

也就是說,getMapper方法返回的是一個JDK動態代理對象(類型是$Proxy+數字)。這個代理對象會繼承Proxy類,實現被代理的接口UserMpper,里面持有了一個MapperProxy類型的觸發管理類。

當我們調用UserMpper的方法時候,實質上調用的是MapperProxy的invoke方法。

  1. userMapper=$Proxy6@2355。 

為什么要在MapperRegistry中保存一個工廠類?

原來他是用來創建并返回代理類的。這里是代理模式的一個非常經典的應用。

MapperProxy如何實現對接口的代理?

JDK動態代理

我們知道,JDK動態代理有三個核心角色:

  • 被代理類(即就是實現類)
  • 接口
  • 實現了InvocationHanndler的觸發管理類,用來生成代理對象。

被代理類必須實現接口,因為要通過接口獲取方法,而且代理類也要實現這個接口。

而Mybatis中并沒有Mapper接口的實現類,怎么被代理呢?它忽略了實現類,直接對Mapper接口進行代理。

MyBatis動態代理:

在Mybatis中,JDK動態代理為什么不需要實現類呢?

這里我們的目的其實就是根據一個可以執行的方法,直接找到Mapper.xml中statement ID ,方便調用。

最后返回的userMapper就是MapperProxyFactory的創建的代理對象,然后這個對象中包含了MapperProxy對象,

問題3:到底是怎么根據Mapper.java找到Mapper.xml的?

最后我們調用userMapper.selectUserById(),本質上調用的是MapperProxy的invoke()方法。

請看下面這張圖:

如果根據(接口+方法名找到Statement ID ),這個邏輯在InvocationHandler子類(MapperProxy類)中就可以完成了,其實也就沒有必要在用實現類了。

總結

本文中主要是講getMapper方法,該方法實質上是獲取一個JDK動態代理對象(類型是Proxy+數字),這個代理類會繼承MapperProxy類,實現被代理的接口UserMapper,并且里面持有一個MapperProxy類型的觸發管理類。這里我們就拿到代理類了,后面我們就可以使用這個代理對象進行方法調用。

問題涉及到的設計模式:

  1. 代理模式。
  2. 工廠模式。
  3. 單例模式。

整個流程圖:

本文轉載自微信公眾號「Java后端技術全棧」,可以通過以下二維碼關注。轉載本文請聯系Java后端技術全棧公眾號。

 

責任編輯:武曉燕 來源: Java后端技術全棧
相關推薦

2018-04-23 09:50:54

2020-03-23 12:58:34

美團公有云互聯網

2023-03-28 21:33:53

面試隔離MVCC

2024-06-07 08:10:14

Netty操作系統零拷貝

2025-06-05 03:10:00

mmapmalloc共享內存

2024-05-16 17:58:30

線程任務線程通訊線程池

2023-05-22 08:17:04

2023-12-20 07:36:58

GoLinux語言

2013-08-20 13:11:58

技術美團

2022-02-15 07:03:04

start 源碼run線程

2023-05-09 10:05:24

HashMapNull

2022-06-15 09:02:32

JVM線程openJDK

2023-11-30 08:16:19

SpringjarTomcat

2022-09-05 15:36:47

線程方法Java

2022-03-03 16:45:02

美團述職反饋

2023-09-29 11:50:10

接口編程代碼

2017-06-01 10:52:35

互聯網

2021-08-29 18:36:17

MySQL技術面試題

2016-11-27 20:43:26

云計算迭代

2023-01-31 08:44:50

SQL語句查詢
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 91婷婷韩国欧美一区二区 | 欧美亚洲另类在线 | 999久久久久久久久6666 | 日韩a| 国产精品1 | 精品99爱视频在线观看 | 夜久久| 中文字幕一区二区不卡 | 国产一区二区在线播放 | 在线免费观看成人 | 五月婷婷 六月丁香 | 国产一级久久久久 | 精品自拍视频在线观看 | 91欧美精品成人综合在线观看 | 国产精品久久久久久久久久 | 日韩免费视频 | 久操伊人| 久久69精品久久久久久国产越南 | 日韩成人在线免费视频 | 国产精品久久一区二区三区 | 在线观看av网站永久 | 免费国产一区二区 | 成人免费视频观看视频 | 成人在线播放网站 | 国产免费色 | 青青久草 | 欧美在线观看一区 | 日韩一区二区三区四区五区六区 | 久久久久久99| 欧美日本久久 | 一区二区三区视频在线 | 中文字幕在线免费观看 | 久久精品国产免费 | 黄色在线免费观看 | 国产综合欧美 | 三极网站 | www.色综合| 中文字幕一区二区三区四区 | 欧美综合一区 | 国产精品久久久久一区二区三区 | 精品久久久久一区二区国产 |