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 等很多設計目的,都可以通過動態代理優雅地實現。