這只是個 interface 啊,它怎麼執行起來的?!
複習一下 interface
interface(介面),像是規格書、定義出該要有哪些 method;由 implement 它的 class(實作類別)去 override 這些 method、並把內容完成。
舉個例子,「List」是 interface、定義要有 add()
這個 method;而「ArrayList」和「LinkedList」implement 此 interface,分別用不同的實作方式實現了 add()
這個 method 所要有的功能。
interface 是不能用 new
產生出物件實例的,它只是規格書、不是藍圖;所以,當我們在 Spring Boot 的一個 Service class 裡注入了一個 Repository,它一定有一個實作的 class 本體……對吧?
範例
我有個 Entity class 名為 Game
,並其 primary key 的型別為 Integer
,因此我寫了一個 interface、繼承 Spring Data JPA 的 JpaRepository
:
1 | public interface GameRepo extends JpaRepository<Game, Integer> { |
只有寫這樣,也沒有加上 @Repository
annotation。
在 service class,用 constructor DI 注入了這個 interface 的 Bean:
1 | public class GameService { |
這樣就能直接使用了 O.O ——我沒有自己寫實作 class 哦?!
Spring 怎麼辦到的?——動態代理(Dynamic Proxy)
動態代理(Dynamic Proxy)是什麼?簡單來說,是在執行時動態地根據反射產生物件實例、而非根據寫好的 class 產生。
當 Spring Boot Application 啟動,會掃描所有繼承了 JpaRepository
的 interface、利用動態代理產生代理物件實例,並作為 Bean 給我們使用。
所以,我們自己沒有寫實作 class、是 Spring 幫我們做了個代理給我們用;並且,這個代理類別已經包含 @Repository
的功能,所以不用在我們自己寫的 repository interface 上面寫。
至於動態代理是怎麼實現的,還要先瞭解「反射」……就不在此細講了,因為我也還不是很懂。
怎麼知道它是代理?
對一個物件實例使用 getClass()
,可以得到一個 Class 物件,裡面有許多方法、可以取得關於這個 class 的相關資訊,舉幾個常用的:
getCanonicalName()
:完整的 class 名稱,包含完整 package 路徑getSimpleName()
:class 名稱,不包含 packagegetInterfaces()
:這個 classimplement
的所有 interface,回傳值是Class<?>[]
、可以再進一步取得這些 interface 的資訊getMethods()
:這個 class 裡所有的 method,回傳值是Method[]
、可以透過 Method 物件進一步取得 method 名稱、參數、回傳值等資訊。
所以,我對範例中注入進來的 gameRepo
物件實驗了一下:
1 | // ... |
回傳的結果是:
1 | { |
嗯,的確是個 proxy,implement 了我自己寫的 GameRepo
這個 interface、還有許多其他的…… interface 們。
要學的還很多呢 www
延伸閱讀
- How Spring instantiate JpaRepository interfaces. | by Lahiru Sampath | Medium
- [JAVA] 什麼是反射 ? JAVA運行與反射觀念 - KouWei.Lee - Medium
- 基礎訓練--(06)JAVA Reflection(映射)與命名規則-台灣的Web工程師|痞客邦
- 菜鳥工程師 肉豬: Java Reflection(反射)簡單範例
- 动态代理 - Java教程 - 廖雪峰的官方网站
- 動態代理 - HackMD
- 動態代理
參考資料
- ChatGPT
- 以上「延伸閱讀」資料