Java核心技術----反射機制和動態代理分析

1.概述

(1)反射機制: Java 語言提供的一種基礎功能,賦予程序在運行時自省(introspect,官方用語)的能力。通過反射我們可以直接操作類或者對象,比如獲取某個對象的類定義,獲取類聲明的屬性和方法,調用方法或者構造對象,甚至可以運行時修改類定義。

  • 反射,它就像是一種魔法,引入運行時自省能力,賦予了 Java 語言令人意外的活力,通過運行時操作元數據或對象,Java 可以靈活地操作運行時才能確定的信息。
  • 反編譯:.class–>.java
  • 通過反射機制訪問java對象的屬性,方法,構造方法等;
//sun爲我們提供了那些反射機制中的類:

java.lang.Class;                
java.lang.reflect.Constructor; java.lang.reflect.Field;        
java.lang.reflect.Method;
java.lang.reflect.Modifier;

(2)動態代理: 一種方便運行時動態構建代理、動態處理代理方法調用的機制,很多場景都是利用類似機制做到的,比如用來包裝 RPC 調用、面向切面的編程(AOP)。

  • 實現動態代理的方式很多,比如 JDK 自身提供的動態代理,就是主要利用了上面提到的反射機制。還有其他的實現方式,比如利用傳說中更高性能的字節碼操作機制,類似 ASM、cglib(基於 ASM)、Javassist 等。
  • 動態代理,則是延伸出來的一種廣泛應用於產品開發中的技術,很多繁瑣的重複編程,都可以被動態代理機制優雅地解決。
  • 動態代理應用非常廣泛,雖然最初多是因爲 RPC 等使用進入我們視線,但是動態代理的使用場景遠遠不僅如此,它完美符合 Spring AOP 等切面編程。

2.具體分析

(1)反射機制具體實現

參考資料

1.獲取class

//第一種方式:  
Class c1 = Class.forName("Employee");  
//第二種方式:  
//java中每個類型都有class 屬性.  
Class c2 = Employee.class;  
//第三種方式:  
//java語言中任何一個java對象都有getClass 方法  
Employee e = new Employee();  
Class c3 = e.getClass(); //c3是運行時類 (e的運行時類是Employee)  

2.創建此Class 對象所表示的類的一個新實例

Object o = c3.newInstance(); //調用了Employee的無參數構造方法.  

3.獲取相關屬性

//3.1 獲取所有的屬性
Field[] fs = c3.getDeclaredFields();
for(Field f : fs){
    //獲得屬性的修飾符,例如public,static等等  
    System.out.println(Modifier.toString(f.getModifiers()));
    //屬性的類型的名字  
    System.out.println(f.getType().getSimpleName());
    //屬性的名字
    System.out.println(f.getName());
}

//3.2 獲取特定屬性
try {
    //獲取id屬性  
    Field f = c3.getDeclaredField("age");
    //實例化這個類賦給o  
    Object o = c3.newInstance();  
    //使用反射機制可以打破封裝性,導致了java對象的屬性不安全。
    f.setAccessible(true);
    //給o對象的age屬性賦值  
    f.set(o, 18);
    System.out.println(f.get(o));
} catch (SecurityException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
} catch (NoSuchFieldException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
} catch (InstantiationException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
} catch (IllegalAccessException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}

4.獲取獲取方法,和構造方法

方法關鍵字 含義
getDeclaredMethods() 獲取所有的方法
getReturnType() 獲取方法返回類型
getParameterTypes() 獲得方法的傳入參數類型
getDeclaredMethod(“方法名”,參數類型.class,……) 獲得特定的方法
構造方法關鍵字 含義
getDeclaredConstructors() 獲取所有的構造方法
getDeclaredConstructor(參數類型.class,……) 獲取特定的構造方法
父類和父接口 含義
getSuperclass() 獲取某類的父類
getInterfaces() 獲取某類實現的接口

示例:

ControllContext context = new ControllContext();
Class c = context.getClass();
//獲取特定方法
Method m1 = c.getDeclaredMethod("setName", String.class);
Method m2 = c.getDeclaredMethod("getName");
//調用對象的方法
m1.invoke(context, "dd");
String name = (String)m2.invoke(context);

注意點:
1.在方法調用中,參數類型必須正確,這裏需要注意的是不能使用包裝類替換基本類型,比如不能使用Integer.class代替int.class。
2.static方法調用時,不必得到對象示例 staticMethod.invoke(c,"chb");//這裏不需要newInstance
3.反射提供的 AccessibleObject.setAccessible​(boolean flag)。它的子類也大都重寫了這個方法,這裏的所謂 accessible 可以理解成修飾成員的 public、protected、private,這意味着我們可以在運行時修改成員訪問限制!
4.setAccessible 的應用場景非常普遍,遍佈我們的日常開發、測試、依賴注入等各種框架中。

  • 比如,在 O/R Mapping 框架中,我們爲一個 Java 實體對象,運行時自動生成 setter、getter 的邏輯,這是加載或者持久化數據非常必要的,框架通常可以利用反射做這個事情,而不需要開發者手動寫類似的重複代碼。
  • 另一個典型場景就是繞過 API 訪問控制。我們日常開發時可能被迫要調用內部 API 去做些事情,比如,自定義的高性能 NIO 框架需要顯式地釋放 DirectBuffer,使用反射繞開限制是一種常見辦法。

5.在 Java 9 以後,這個方法的使用可能會存在一些爭議,因爲 Jigsaw 項目新增的模塊化系統,出於強封裝性的考慮,對反射訪問進行了限制。Jigsaw 引入了所謂 Open 的概念,只有當被反射操作的模塊和指定的包對反射調用者模塊 Open,才能使用 setAccessible;否則,被認爲是不合法(illegal)操作。如果我們的實體類是定義在模塊裏面,我們需要在模塊描述符中明確聲明:

module MyEntities {
    // Open for reflection
    opens com.mycorp to java.persistence;
}

6.目前,Java 9 仍然保留了兼容 Java 8 的行爲,但是很有可能在未來版本,完全啓用前面提到的針對 setAccessible 的限制,即只有當被反射操作的模塊和指定的包對反射調用者模塊 Open,才能使用 setAccessible,我們可以使用下面參數顯式設置。

--illegal-access={ permit | warn | deny }

(2)動態代理

  • 代理可以看作是對調用目標的一個包裝,這樣我們對目標代碼的調用不是直接發生的,而是通過代理完成。其實很多動態代理場景,我認爲也可以看作是裝飾器(Decorator)模式的應用
  • 通過代理可以讓調用者與實現者之間解耦。比如進行 RPC 調用,框架內部的尋址、序列化、反序列化等,對於調用者往往是沒有太大意義的,通過代理,可以提供更加友善的界面。

JDK動態代理的示例:
http://www.importnew.com/21807.html

public class MyDynamicProxy {
    public static  void main (String[] args) {
        HelloImpl hello = new HelloImpl();
        MyInvocationHandler handler = new MyInvocationHandler(hello);
        // 構造代碼實例
        Hello proxyHello = (Hello) Proxy.newProxyInstance(HelloImpl.class.getClassLoader(), HelloImpl.class.getInterfaces(), handler);
        // 調用代理方法
        proxyHello.sayHello();
    }
}

interface Hello {
    void sayHello();
}
class HelloImpl implements  Hello {
    @Override
    public void sayHello() {
        System.out.println("Hello World");
    }
}
 class MyInvocationHandler implements InvocationHandler {
    private Object target;
    public MyInvocationHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println("Invoking sayHello");
        Object result = method.invoke(target, args);
        return result;
    }
}
  • 首先,實現對應的 InvocationHandler;然後,以接口 Hello 爲紐帶,爲被調用目標構建代理對象,進而應用程序就可以使用代理對象間接運行調用目標的邏輯,代理爲應用插入額外邏輯(這裏是 println)提供了便利的入口。
  • 從 API 設計和實現的角度,這種實現仍然有侷限性,因爲它是以接口爲中心的,相當於添加了一種對於被調用者沒有太大意義的限制。我們實例化的是 Proxy 對象,而不是真正的被調用類型,這在實踐中還是可能帶來各種不便和能力退化。
  • 如果被調用者沒有實現接口,而我們還是希望利用動態代理機制,那麼可以考慮其他方式。我們知道 Spring AOP 支持兩種模式的動態代理,JDK Proxy 或者 cglib,如果我們選擇 cglib 方式,你會發現對接口的依賴被克服了
  • cglib 動態代理 採取的是創建目標類的子類的方式,因爲是子類化,我們可以達到近似使用被調用者本身的效果。在 Spring 編程中,框架通常會處理這種情況,當然我們也可以顯式指定

JDK Proxy 或者 cglib對比:
JDK Proxy 的優勢:

  • 最小化依賴關係,減少依賴意味着簡化開發和維護,JDK 本身的支持,可能比 cglib 更加可靠。

  • 平滑進行 JDK 版本升級,而字節碼類庫通常需要進行更新以保證在新版 Java 上能夠使用。

  • 代碼實現簡單。

基於類似 cglib 框架的優勢:

  • 有的時候調用目標可能不便實現額外接口,從某種角度看,限定調用者實現接口是有些侵入性的實踐,類似 cglib 動態代理就沒有這種限制。

  • 只操作我們關心的類,而不必爲其他相關類增加工作量。

  • 高性能。

AOP 切面編程通過(動態)代理機制可以讓開發者從這些繁瑣事項中抽身出來,大幅度提高了代碼的抽象程度和複用度。從邏輯上來說,我們在軟件設計和實現中的類似代理,如 Facade、Observer 等很多設計目的,都可以通過動態代理優雅地實現。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章