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

2021-01-07 田維常

字數:2434,閱讀耗時:3分40秒。

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

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

看源碼有什麼用?

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

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

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

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

這裡有三個問題:

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

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

這裡也沒做什麼,繼續調用MapperRegistry中的getMapper:

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

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

單例模式methodCache(註冊式單例模式)。工廠模式getMapper()。繼續看MapperProxyFactory中的newInstance方法。

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

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

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

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

進入MapperProxy類中:

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

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

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類型的觸發管理類。這裡我們就拿到代理類了,後面我們就可以使用這個代理對象進行方法調用。

問題涉及到的設計模式:

代理模式。工廠模式。單例模式。整個流程圖:

冰凍三尺,非一日之寒表面意義是冰凍了三尺,並不是一天的寒冷所能達到的效果。學習亦如此,你每一天的一點點努力,都是為你以後的成功做鋪墊。

相關焦點

  • 面試官經常喜歡問的Mybatis經典面試題,值得好好收藏哦!
    今天給大家分享一些面試官喜歡提問的Mybatis面試題,好了,廢話不多說,直接上乾貨吧!一、請說說在Mybatis 中#和$有什麼區別?#相當於對數據 加上 雙引號,$相當於直接顯示數據1.對結果集解析麻煩,sql 變化導致解析代碼變化,且解析前需要遍歷,如果能將資料庫記錄封裝成 pojo 對象解析比較方便。解決:Mybatis 自動將 sql 執行結果映射至 java 對象。三、請你說一下使用 MyBatis 的 mapper 接口調用時有哪些要求?
  • 面試官寫了個雙冒號::問我這是什麼語法?Java中有這玩意?
    三:Optional 可選值  一:簡潔  方法引用分為三種,方法引用通過一對雙冒號:: 來表示,方法引用是一種函數式接口的另一種書寫方式    靜態方法引用,通過類名::靜態方法名, 如 Integer::parseInt    實例方法引用,通過實例對象::實例方法
  • 五大框架之MyBatis面試經常被問到的問題
    前面講了springmvc和hibernate的一些原理和使用,有些朋友私信我問能不能講一下面試中經常遇到的問題,那麼今天就不講mybatis在項目中怎麼使用的,著重談一下我經常遇到的一些問題吧,無論是筆試題還是面試官問到的一些問題,能想起來的都列舉了下來。
  • (四)Mybatis從入門到入土——別名、配置文件以及引入mapper
    方式1:使用mapper resouce屬性註冊mapper xml文件    目前我們所涉及到的各種例子都是採用的這種方式,使用下面的方法進行引入:1<mappers>2    <mapper resource="Mapper xml的路徑(相對於classes的路徑)"/>3</mappers>
  • 通過Java經典面試題之MyBatis與Hibernate的區別深入了解MyBatis
    4.接口與mapper的namespace命名空間一致。下面我們創建接口的實現類 創建了嗎?沒創建啊,看圖中的mapper是通過SqlSession.getMapper();創建的。那麼該實現類擁有哪些方法?它除了繼承了Root類Object的方法,實現的只有我們剛剛才接口編寫的方法。
  • 問:JDK 動態代理調用接口方法時是怎麼做到先調用 invoke 方法的?
    這一篇我們繼續解答關於 JDK 動態代理的另一個深度問題:JDK 動態代理調用自己接口方法時是怎麼做到先調用 invoke 方法的?>[] { Foo.class }, handler);//調用接口方法f.add();你有沒有想過,通過上面代碼,明明最後我調用的是接口 Foo 的 add 方法f.add();,為什麼就自動觸發了 InvocationHandler 接口的invoke方法呢?
  • Mybatis中mapper相關註解解析類詳解
    接著上一篇通過掃描接口添加mapper的方法會創建MapperAnnotationBuilder並執行parse方法,具體源碼如下圖:MapperAnnotationBuilder關鍵屬性說明:statementAnnotationTypes:靜態屬性,存有各種sql對於在mybatis
  • 面試官寫了個雙冒號:問我這是什麼語法?Java中有這玩意?
    一:簡潔方法引用分為三種,方法引用通過一對雙冒號:: 來表示,方法引用是一種函數式接口的另一種書寫方式靜態方法引用,通過類名::靜態方法名, 如 Integer::parseInt通過賦值給Function,說明方法引用也是一種函數式接口的書寫方式,Lambda表達式也是一種函數式接口,Lambda表達式一般用於自己提供方法體,而方法引用一般直接引用現成的方法。
  • C++中是如何調用C接口的?
    前言如何在C++代碼中調用寫好的C接口?你可能會奇怪,C++不是兼容C嗎?直接調用不就可以了?這裡我們先按下不表,先看看C++如何調用C代碼接口。C++如何調用C接口為什麼會有這樣的情況呢?想像一下,有些接口是用C實現的,並提供了庫,那麼C++中該如何使用呢?我們先不做任何區別對待,看看普通情況下會發生什麼意想不到的事情。
  • copy_{to, from}_user()的思考
    畢竟它是kernel space和user space溝通的橋梁。所有的數據交互都應該使用類似這種接口。所以,我們沒有理由不知道接口的作用。但是,我也曾經有過以下疑問。為什麼需要copy_{to,from}_user(),它究竟在背後為我們做了什麼?copy_{to,from}_user()和memcpy()的區別是什麼,直接使用memcpy()可以嗎?
  • copy_{to,from}_user()的思考
    畢竟它是kernel space和user space溝通的橋梁。所有的數據交互都應該使用類似這種接口。所以,我們沒有理由不知道接口的作用。但是,我也曾經有過以下疑問。為什麼需要copy_{to,from}_user(),它究竟在背後為我們做了什麼?copy_{to,from}_user()和memcpy()的區別是什麼,直接使用memcpy()可以嗎?
  • TypeScript真香系列——接口篇
    接口帶來了什麼好處好處One —— 過去我們寫 JavaScriptJavaScript 中定義一個函數,用來獲取一個用戶的姓名和年齡的字符串:const getUserInfo = function(user) { return name: ${user.name}, age: ${user.age}}函數調用:
  • 面試中經常問到的動態代理到底是什麼
    上面的面試場景是不是特別熟悉, 不瞞大家說,我就是其中的一員。String result = userService.createUser(username);        long costTime = System.currentTimeMillis() - startTime;        System.out.println("基於接口實現的靜態代理, 創建用戶耗時
  • Mybatis中mapper的xml解析詳解
    基礎介紹回顧下之前是在分析configuration的初始化過程,已經進行到了最後一步mapperElement(root.evalNode("mappers")),這個方法裡有兩種解析mapper的方法,一種是解析類,一種是解析xml文件,上一篇文章在講解析類中的註解,今天說到的就是解析xml