Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

反射與類別載入器

275 views

Published on

取得.class檔案資訊
動態生成物件與操作方法
認識模組與反射的權限設定
瞭解JDK9類別載入器階層
使用ClassLoader實例

Published in: Technology
  • Be the first to comment

  • Be the first to like this

反射與類別載入器

  1. 1. 1
  2. 2. 反射與類別載入器 學習目標 • 取得.class檔案資訊 • 動態生成物件與操作方法 • 認識模組與反射的權限設定 • 瞭解JDK9類別載入器階層 • 使用ClassLoader實例 2
  3. 3. 運用反射 • JDK9支援模組化 • 開發者在採取模組設計時,如何在不破壞模 組封裝下又能運用反射機制的彈性,就成了 認識反射時必須知道的一大課題 3
  4. 4. Class與.class檔案 • Java真正需要某個類別時才會載入對應 的.class檔案 • java.lang.Class的實例代表Java應用程 式運行時載入的.class檔案 • Class類別沒有公開(public)建構式, 實例是由JVM自動產生 4
  5. 5. Class與.class檔案 • 可以透過Object的getClass()方法,或 者是透過.class常量(Class literal)取得每個 物件對應的Class物件 • 如果是基本型態,也可以使用對應的包裹類 別加上.TYPE取得Class物件 – 如Integer.TYPE可取得代表int的Class物件 – 如果要取得代表Integer.class檔案的Class,則必 須使用Integer.class 5
  6. 6. Class與.class檔案 • 可以操作Class物件的公開方法取得類別基 本資訊 6
  7. 7. Class與.class檔案 • 載入.class檔案的時機 – 使用指定類別生成物件時 – 使用 Class.forName() – 使用java.lang.ClassLoader實例的 loadClass() • 使用類別宣告參考名稱並不會載入.class檔案 7
  8. 8. Class與.class檔案 8
  9. 9. Class與.class檔案 • 編譯時期若使用到相關類別,編譯器會檢查 對應的.class檔案中記載之資訊,以確定是否 可完成編譯 • 執行時期使用某類別時,會先檢查是否有對 應的 Class物件,如果沒有,會載入對應 的.class檔案並生成對應的Class實例 9
  10. 10. Class與.class檔案 • 預設JVM只會用一個Class實例來代表一 個.class檔案(確切說法是,經由同一類別載 入器載入的.class檔案,只會有一個對應的 Class實例) • 每個類別的實例都會知道自己由哪個Class實 例生成。預設使用getClass()或.class取得 的Class實例會是同一個物件 10
  11. 11. 使用Class.forName() • 可以使用Class.forName()方法實現動態 載入類別,可用字串指定類別名稱來獲得類 別相關資訊 11
  12. 12. 使用Class.forName() • Class.forName()另一版本可以讓指定類 別名稱、載入類別時是否執行靜態區塊與類 別載入器: • 如果使用第一個版本的Class.forName() 方法,等同於: 12
  13. 13. 使用Class.forName() 13
  14. 14. 從Class獲得資訊 • 取得Class物件後,就可以取得與.class檔案 中記載的的資訊,像是套件、建構式、方法 成員、資料成員等訊息 – java.lang.Package – java.lang.reflect.Constructor – java.lang.reflect.Method – java.lang.reflect.Field – … 14
  15. 15. 15
  16. 16. 16
  17. 17. 17
  18. 18. 18
  19. 19. 從Class建立物件 • 取得Class物件之後 • 呼叫Class的getConstructor()或 getDeclaredConstructor()方法 • 取得代表建構式的Constructor物件,利 用其newInstance()方法建立類別實例 19
  20. 20. 從Class建立物件 • 你想採用影片程式庫來播放動畫,然而負責 實作影片程式庫的部門遲遲還沒動工,怎麼 辦呢? 20
  21. 21. 從Class建立物件 • 可以在啟動程式時,透過系統屬性 cc.openhome.PlayerImpl指定 21
  22. 22. 從Class建立物件 • 執行MediaPlayer若指定了- Dcc.openhome.PlayerImpl=cc.openhome.Conso lePlayer 22
  23. 23. 從Class建立物件 • 若類別定義有多個建構式,也可以指定使用 哪個建構式生成物件 • 假設因為某個原因,必須動態載入 java.util.List實作類別 23
  24. 24. 從Class建立物件 • 陣列的Class實例是由JVM生成,你並不知 道陣列的建構式為何 • 若要動態生成陣列,必須使用 java.lang.reflect.Array的 newInstance()方法 24
  25. 25. 從Class建立物件 • 可以使用Array.set()方法指定索引設值, 或是使用Array.get()方法指定索引取值 • 比較偷懶的方式,直接當作Object[](或 已知的陣列型態)使用 25
  26. 26. 從Class建立物件 • 為何要使用Array.newInstance()建立陣 列實例? • 回顧一下9.1.7中實作過的ArrayList,如果 現在為其設計一個toArray()方法: 26
  27. 27. 從Class建立物件 • 現在有個使用者這麼使用ArrayList,會拋 出java.lang.ClassCastException, 告訴你不可以將Object[]當作String[] 來使用 27
  28. 28. 從Class建立物件 • 可以如下解決: 28
  29. 29. 操作物件方法與成員 • java.lang.reflect.Method實例是方法 的代表物件,可以使用invoke()方法來動 態呼叫指定的方法 29
  30. 30. 操作物件方法與成員 • 底下會設計一個BeanUtil類別,可以指定 Map物件與類別名稱呼叫getBean()方法, 這個方法會抽取Map內容並封裝為指定類別 的實例 – 例如Map中收集了學生資料,則以下傳回的就是 Student實例 30
  31. 31. 31
  32. 32. 操作物件方法與成員 • 想呼叫受保護的(protected)或私有 (private)方法 32
  33. 33. 操作物件方法與成員 • 可以使用反射機制存取類別資料成員(Field) 33
  34. 34. 操作物件方法與成員 • setAccessible(true)時,如果模組權限 設定上不允許,會拋出 java.lang.reflect.InaccessibleOb jectException • 就目前來說,上面的程式片段中 setAccessible(true)可以用於同一模組 的類別,然而不能用於java.base中的類別 • 因為java.base模組並沒有開放權限 34
  35. 35. 動態代理 • 需要在執行某些方法時進行日誌記錄,你可 能會如下撰寫: 35
  36. 36. 靜態代理 • 在靜態代理實現中,代理物件與被代理物件 必須實現同一介面 36
  37. 37. 靜態代理 • 代理物件同樣也要實現Hello介面 37
  38. 38. 靜態代理 • 可以如下使用代理物件: 38
  39. 39. 動態代理 • 使用動態代理機制,可使用一個處理者 (Handler)代理多個介面的實作物件 39
  40. 40. 40
  41. 41. 動態代理 • 使用LoggingHandler的bind()方法來綁 定被代理物件 41
  42. 42. 當反射遇上模組 • 對於JDK8以及先前版本的JDK,就算開發者 不願意,只要程式庫的使用者運用反射,就 可以訪問類別定義的成員 • 這包括了公開、受保護與私有成員,事實上 ,在Java的生態圈中 • 有不少程式庫或框架,都依賴在這種深層反 射(Deep reflection)機制上運作 42
  43. 43. 當反射遇上模組 • JDK9的模組是為了強封裝,若反射機制依舊 可以這般自由,那麼模組就失去了強封裝的 意義 • 然而,也必須考慮現存程式庫或框架依賴於 深層反射的現況,否則這些程式庫或框架將 不會考慮遷移至JDK9 43
  44. 44. 當反射遇上模組 • 模組的設計者可以決定是否允許反射 • 範例檔labsCH17ReflectModule 44
  45. 45. 45
  46. 46. 模組圖 • 一個模組在模組路徑之中,只是表示JVM可 以找到模組描述檔(module-info.class) • 不代表模組圖(Module graph)中有這個模 組,模組圖中不存在的模組,自然就找不到 模組中的類別 46
  47. 47. • 要將模組加入模組圖的方式之一,是在執行 java指令啟動JVM時,使用--add- modules引數加入其他模組作為根模組 • 如果執行java指令指定了--add-modules cc.openhome.reflect 47
  48. 48. • 另一個將指定模組加入模組圖方式是,在模 組描述檔中加入requires設定 • 現有模組會依賴在requires的模組之上 • 現有模組可以讀取(read)該模組,或稱現 在模組對該模組有讀取能力(Readability) 48
  49. 49. 49
  50. 50. 50
  51. 51. exports套件 • 若模組能允許其他模組對指定的公開成員進 行操作,必須在模組描述檔使用exports定義 哪些套件中的類別是可以允許此動作 • 更確切的說法是,指定此模組中哪些套件中 的公開成員可以存取(Accessibility) 51
  52. 52. 52
  53. 53. 53
  54. 54. 54
  55. 55. 55
  56. 56. opens套件或open模組 • 若模組能允許其他模組在反射時,對指定的 非公開成員進行操作,必須在模組描述檔使 用opens定義哪些套件中的類別是可以允許 此動作 56
  57. 57. • 除了使用opens指定要開放的套件之外,也 可以使用 open module,表示開放這個模 組中全部的套件 • 如果使用了open module,那麼module中 的定義就不能再有opens的獨立設定 • 被設定為open module的模組被稱為開放 模組(Open Module) • 沒有open的模組稱為一般模組(Normal module),它們都屬於顯式模組。 57
  58. 58. • java.base模組中的套件使用了exports • 沒有設定opens的套件,也沒有直接open module java.base • 若採取模組化設計,就不允許其他模組在反 射時,對指定的非公開成員進行操作 58
  59. 59. 使用ServiceLoader • 如果Player是種API服務,更實際的場景會 是,Player會是服務的介面之一,它也許是 定義在cc.openhome.api套件之中 • ConsolePlayer是服務的具體實現,可能 定義在cc.openhome.impl套件之中 • MediaMaster是你正在設計的應用程式, 定義在cc.openhome套件之中 • 拆成三個模組 59
  60. 60. 使用ServiceLoader 60
  61. 61. 61
  62. 62. 62
  63. 63. • java.sql模組的module-info.java定義 63
  64. 64. JDK9類別載入器階層 • 在JDK9中,有三種層次的類別載入器負責載 入類別 – System類別載入器(也稱為Application載入器) – Platform類別載入器 – Bootstrap類別載入器 64
  65. 65. JDK9類別載入器階層 • System的父載入器為Platform,而Platform 的父載入器為Bootstrap 65
  66. 66. • System類別載入器可以載入應用程式模組路 徑、類別路徑上的類別 • Platform載入器可以載入整個Java SE 平臺 API、實作類別 • Bootstrap類別載入器是JVM內建的載入器 66
  67. 67. 67
  68. 68. 建立ClassLoader實例 • Bootstrap Loader、Platform Loader與System Loader在程式啟動後,就無法再改變它們的 搜尋路徑 • 可以使用URLClassLoader來產生新的類別 載入器 68
  69. 69. 建立ClassLoader實例 • 由同一類別載入器載入的.class檔案,只會有 一個Class實例 • 如果同一.class檔案由兩個不同的類別載入器 載入,則會有兩份不同的Class實例 69
  70. 70. 70

×