JAVA(五)反射機制/Annotation

Leo.cheng發表於2014-02-08
成鵬致遠 | lcw.cnblog.com |2014-02-04

反射機制

1.認識Class類

  1. 在正常情況下,必須知道一個類的完整路徑之後才可以例項化物件,但是在 java中也允許通過一個物件來找到其所在的類的資訊,那麼這實際上就是 Class類的功能
  2. 此時,所有的操作都是反著來的
  3. Object類的支援
    1. 在Object類中定義了以下的方法,此方法將被所有子類繼承:public final Class getClass()
    2. 以上的方法返回值的型別是一個“Class”類,實際上此類是 Java反射的源頭,實際上所謂反射就是:可以通過物件反射求出類的名稱
  4. Class類
    1. Class本身表示一個類的本身,通過 Class可以完整的得到一個類中的完整結構,包括此類中的方法定義,屬性定義等
    2. 此類在 JDK文件中沒有發現任何的構造方法,所以此類的構造方法被私有化了
  5. 例項化 Class類物件,方法有三種
    1. 第一種:通過 forName()方法
    2. 第二種:類.class
    3. 第三種:物件.getClass()
  6. 一旦可以例項化 Class類之後,就可以進行反射的進一步操作
  7. 小結
    1. 瞭解 Class類的作用:反射的源頭
    2. 三個 Class類的例項化方式,其中要以“forName()”重點掌握,類.class

2.Class類的作用

  1. Class主要是反射的源頭,不光可以取得物件所在類的資訊,也可以直接通過 Class類的方法進行物件的例項化操作,正常情況下,使用關鍵字 new為物件例項化,如果現在已經例項化好了 Class物件,則就可以通過 Class類中提供的 newInstance()例項化物件
  2. 即使不使用關鍵字 new物件也可以進行例項化操作,反射的作用
  3. 【注意】在操作中類中必須存在無參構造方法,否則無法例項化
  4. 一般使用反射例項化物件的時候,類中都最好存在一個無參構造,這樣的操作比較合理
  5. 如果要想呼叫有參構造,則必須按照以下的步驟進行:
    1. 通過 Class類中的 getConstructors()取得本類中的全部構造方法
    2. 向構造方法中傳遞一個物件陣列進去,裡面包含了構造方法中所需的各個引數
    3. 之後通過 Constructor例項化物件
  6. 小結
    1. 本節的功能是 Class用的最多的功能,而且在開發中使用者也會經常使用到的開發模式
    2. 在使用 Class例項化物件的時候,必須保證類中存在一個無參構造,否則無法使用

3.反射應用:取得類的結構

  1. 通過反射得到一個類的完整結構,需要使用到 java.lang.reflect包中的幾個類
    1. Constructor:表示類中的構造方法
    2. Field:表示類中的屬性
    3. Method:表示類中的方法
  2. 取得一個類所實現的全部介面
    1. 使用 Class類中的 getInterfaces()方法
    2. 此方法返回一個 Class類的物件陣列,之後就可以直接利用 Class類中的 getName()方法輸出即可
  3. 取得類所繼承的父類
    1. 一個類可以實現多個介面,但是隻能繼承一個父類
    2. 如果要想取得一個類的父類,可以直接使用 Class類中的 getSuperclass()方法
    3. 此方法返回的是 Class例項,和之前得到的介面一樣,可以通過 敷衍Name()方法取得名稱
  4. Constructor類中的幾個方法
    1. 取得修飾符:public int getModifiers()
    2. 取得方法名稱:public String getName()
    3. 取得引數的型別:public Class<?>[] getParameterTypes()
  5. 還原修飾符:直接使用 Modifier類中的 public static String toString(int mod)
  6. 取得類中的方法
    1. 要想取得一個類的全部方法,可以使用 Class類中的 getDeclaredMethods()方法
    2. 此方法返回一個 Method類的物件陣列,而如果要想進一步取得方法的具體資訊,例如:方法的引數,丟擲的異常宣告等等,則就必須依靠 Method類
    3. 輸出本類中的全部方法:public Method[] getDeclaredMethods() throws SecurityException
    4. 輸出全部的方法:public Method[] getMethods() throws SecurityException
    5. 取得全部的返回值:public Class<?> getReturnType()
    6. 取得全部的引數:public Class<?> getParameterTypes()
    7. 取得修飾符:public int getModifiers()
    8. 取得異常資訊:public Class<?>[] getExceptionTypes()
  7. 在一般的開發工具中經常看見隨筆提示功能,實際上此功能就是利用以上函式完成的
  8. 取得類中的屬性
    1. 得到實現的介面或父類中的公共屬性:public Field[] getFields() throws SecurityException
    2. 得到本類中的全部屬性:public Field[] getDeclaredFields() throws SecurityException
    3. 以上方法返回的都是 Field的陣列,每一個 Field物件就表示類中的一個屬性
  9. 小結
    1. 在實際的操作中,取得類的資訊的操作程式碼使用者並不會經常開發
    2. 一定要熟悉 java.lang.reflect包的作用,反射機制
    3. 如何取得屬性、方法、構造的名稱,修飾符等等

4.Java反射機制深入研究

  1. 通過反射呼叫類中的方法
    1. 在正常情況下一個類的物件產生之後,就可以直接呼叫類中的方法了,如果要想呼叫的話,則肯定必須清楚的知道要呼叫的方法名稱是什麼,之後通過 Class類中的 public Method getMethod(String name, Class<?>... parameterTypes)
    2. 得到一個方法的 Method物件是,之後通過此 Method物件來執行方法(invoke()),但是在方法呼叫的時候,因為會牽扯到方法中的引數的問題,所以通過 getMethod()取得的時候,必須設定需要的引數型別
    3. 執行的時候還需要傳遞引數進去,而且還需要例項化物件
  2. 通過反射呼叫類 setter及 getter
    1. setter及 getter方法是一個標準的屬性的訪問方法,如果一個類的屬性被封裝,則必須通過 setter及 getter方法設定和取得
    2. 實際上此方法的操作之所以要這樣規定,主要原因是由於反射機制可以給予支援的
  3. 通過反射呼叫屬性
    1. 如果要操作一個類中的屬性,則可以通過 Filed完成,而不必麻煩的通過 setter及 getter
    2. 在訪問私有屬性的時候,必須讓這個屬性可見:public void setAccessible(boolean flag) throws SecurityException,將其內容設定成 true即可
    3. 此操作呼叫與 setter及 getter無關,但是為了保證程式的安全性,最好還是通過 setter及 getter方法完成呼叫
  4. 通過反射運算元組
    1. 反射機制不光只能用在類中,也可以應用在任意的引用資料型別上,當然這就包含了陣列,陣列使用 Array類完成
    2. Class類中存在一個方法:public Class<?> getComponentType()
    3. 得到陣列指定下標的內容:get(Object array, int index)
    4. 修改內容:set(Object array ,int index)
    5. 開闢新陣列:newInstance(Class<?> componentType, int... dimensions)
  5. 小結
    1. 重點理解 setter及 getter方法的呼叫問題,原理,程式碼最好可以清楚的掌握
    2. 使用反射操作屬性是不建議直接使用的

5.動態代理

  1. 代理設計:一個操作的介面有兩個子類,其中一個是真實主題的實現類,另外一個是代理類,代理實現類要完成比真實主題實現類更多的內容,而且本身還需要處理一些與具體業務有關的程式程式碼
  2. 之前寫過的代理實際上稱為靜態代理,因為一個代理類只能為一個介面服務,那麼如果現在有很多個介面的話,那麼則肯定代理類就很多了;而且所有的代理操作除了呼叫的方法不一樣之外,其它的操作都一樣,則此時肯定是重複的程式碼
  3. InvocationHandler介面:public interface InvocationHandler{public Object invoke(Object proxy, Method method, Object[] args) throws Throwable}
    1. Object proxy:被代理的物件
    2. Method method:要呼叫的方法
    3. Object args[]:方法呼叫時所需要的引數
  4. 可以將 InvocationHandler介面的子類想象成一個代理的最終操作類,替換掉 ProxySubject
  5. Proxy類
    1. Proxy類是專門完成代理的操作類,可以通過此類為一個或多個介面動態地生成實現類
    2. public static Object newProxyInstance(ClassLoader loader, Class<?>[] interface, InvocationHandler h) throws IIIlegalArgumentException
      1. ClassLoader loader:類載入器
      2. Class<?>[] interfaces:得到全部的介面
      3. InvocationHandler h:得到 InvocationHandler介面的子類例項
  6. 類載入器
    1. 在 Proxy類中的 newProxyInstance()方法中需要一個 ClassLoader類的例項,ClassLoader實際上對應的是類載入器
    2. 在Java中主要有以下三種類載入器
      1. Bootstrap ClassLoader:此載入器採用 C++編寫,一般開發中是看不到的
      2. Extension ClassLoader:用來進行擴充套件類的載入,一般對應的是 jre\lib\ext目錄中的類
      3. AppClassLoader:載入 classpath指定的類,是最常使用的一種載入器
    3. 如果要想得到一個載入器的物件,則肯定使用 class類完成
  7. 小結
    1. 使用動態代理,可以完成代理的功能,而且可以代理全部的介面

6.高階工廠設計模式

  1. 工廠設計模式最大的好處是可以在應用進行解耦合操作
  2. 之前寫過的工廠設計模式存在的問題:如果想擴充一個子類,則肯定要修改工廠類,如果希望在擴充子類時不用修改工廠類的話,則就必須使用反射完成
  3. 如果直接使用反射,則需要輸入完整的“包.類”名稱,肯定很麻煩,所以,此時可以通過一些配置檔案的方式儲存這些完整的類路徑
    1. 所以此時可以通過屬性檔案的形式配置所要的子類資訊
  4. 程式執行的時候,就可以通過屬性檔案的內容讀取出來,之後直接操作屬性檔案的 key,就可以避免輸入過長的類路徑
  5. 配置檔案與程式程式碼相分離,這種設計思路是以後開發基本思路,當然,最新的設計理念:是在程式中直接通過註釋的方式進行配置
  6. 小結
    1. 反射機制對程式開發所帶來的好處
    2. 程式程式碼與配置檔案相分離的理論

Annotation

1.系統內建Annotation

  1. Annotation:在 JDK1.5之後增加的一個新特性,這種特性被稱為後設資料特性,在 JDK1.5之後稱為註釋,即:使用註釋的方式加入一些程式的資訊
  2. java.lang.annotation.Annotation介面是所有的 Annotation都必須實現的介面
  3. 在 JDK1.5之後,系統中已經建立了如下的三個內建的 Annotation型別,使用者直接使用即可
    1. @Override:覆寫的 Annotation
    2. @Deprecated:不贊成使用的 Annotation
    3. @SuppressWarnings:壓制安全警告的 Annotation
  4. @Override:表示方法覆寫的正確性,使用 @Override註釋可以保證程式正確的執行
  5. @Deprecated:使用 @Deprecated註釋的 Annotation本身是不建議使用的一個操作
  6. @SuppressWarnings:用於壓制警告資訊
    1. 可以壓制一個警告資訊,也可以同時壓制多個警告資訊,只需要以陣列的形式出現即可
    2. 在設定註釋資訊的時候,是以 key->value的形式出現的,所以 @SuppressWarnings也可以直接使用“value={"unchecked","deprecation"}”的方式設定
  7. 小結
    1. 系統內建的三個 Annotation的作用,可以發現通過註釋可以完成一些程式碼的其它功能

2.自定義Annotation

  1. 定義簡單的 Annotation:[public] @interface Annotation 名稱{資料型別 變數名稱();}
  2. 之後就可以直接在程式中使用“@Annotation 名稱”的格式了
  3. 還可以向 Annotation中設定引數,使用變數接收引數
    1. 在使用時就必須清楚的指定變數的內容
    2. 也可以使用明確的標記,表明內容賦給哪個引數
    3. 也可以設定多個引數
    4. 還可以設定一個陣列進去
    5. 以上全部的 Annotation中有一個特點,所有的引數內容需要在使用註釋的時候設定上去,那麼也可以為一個引數設定預設的內容,在宣告的時候使用 default即可,當再去使用此 Annotation的時候就可以不用設定內容
  4. 在操作中,對於一個 Annotation而言,有時候會固定其取值範圍,只能取固定的幾個值,那麼這個時候實際上就需要依靠列舉,那麼外部使用此 Annotation的時候也需要從列舉固定取值
  5. Retention和RetentionPolicy
    1. 在 Annotation,可以使用 Retention定義一個 Annotation的儲存範圍
    2. Retention定義中存在了一個 RetentionPolicy的變數,此變數用於指定 Annotation的儲存範圍,RetentionPolicy包含三種範圍
      1. 在三個範圍中,最需要關心的就是 RUNTIME範圍,因為在執行時候起作用,在以後反射與 Annotation的操作中,也必須使用此種範圍
  6. 內建 Annotation的 RetentionPolicy
    1. 三個內建的 Annotation
    2. Override:定義採用的是 @Retention(value=SOURCE),只能在原始檔中出現
    3. Deprecated:定義採用的是 @Retention(value=RUNTIME),可以在執行時出現
    4. SuppressWarnings:定義採用的是 @Retention(value=SOURCE),只能在原始檔中出現
  7. 小結
    1. Annotation定義格式,及變數的型別:列舉、陣列、普通變數
    2. Retention、RetentionPolicy的作用

3.反射與 Annotation

  1. 一個 Annotation如果要想讓其變得有意義,則必須結合反射機制取得 Annotation中設定的全部內容
  2. 系統內建的三個 Annotation,只有 Deprecated的 Annotation的定義範圍是 RUNTIME範圍
  3. 在 Class類中存在與 Annotation操作有關的方法
    1. 如果在一個元素中存在註釋,則取得全部註釋:getAnnotation()
    2. 判斷一個元素上是否存在註釋:isAnnotationPresent()
  4. 小結
    1. Annotation在實際的開發中,不管如何使用,其最終肯定是結合反射機制
    2. 也就是說可以通過 Annotation設定一些內容到一個方法上去,以完成特定的功能

4.深入Annotation

  1. 之前定義的 Annotation,如果沒有明確的說明可以在任意的位置上使用
  2. 如果現在需要指定其使用的範圍的話,則就必須使用 @Target註釋
  3. ElementType的儲存範圍
  4. 如果希望一個 Annotation可以在類及方法上同時使用的話,則必須設定多個範圍
  5. @Documented:可以在任何的 Annotation上使用
    1. 所有的 Annotation預設情況下都是使用 @Document進行註釋的
    2. 生成的 javadoc的時候可以通過 @Document設定一些說明資訊
  6. @Inherited:此註釋表示一個 Annotation是否可以被繼承下來
  7. 如果在父類上使用 @Inherited,則其子類可以將此 Annotation繼承下來
  8. 小結
    1. 熟悉 Documented註釋的作用:加入說明資訊
    2. 熟悉 Target的作用,並使用 Target註釋指定註釋的使用位置
    3. 如果一個 Annotation要想被子類繼承下來則使用 Inherited註釋說明
    4. 反射機制對於操作 Annotation是最重要的
 

 

相關文章