Java 學習筆記--反射與代理機制(靜態、動態)

前言

反射與代理機制是Java中比較高級的一種特性,它完全是站在Java虛擬機的角度去看待各種類的運行,特別是在Java EE中運用廣泛。在學習之前,應該思考幾個問題:

給定一個類的名字(字符串形式),怎麼創建該類的對象?
什麼是反射機制?
Java靜態代理和動態代理的異同有哪些?
Java靜態代理和動態代理的優缺點是?

一、反射機制(reflection)

當Java程序在運行過程中需要識別每一個對象的類型,這種識別方法有兩種:

RTTI(Run-Time Type Identification)
Java反射機制

1.RTTI(Run-Time Type Identification)

首先舉個例子來了解第一種方法RTTI,例如有一個類Shape,然後它有3個子類Circle、Square、Triangle,都重新實現了方法draw(),類圖如下:

Java 學習筆記--反射與代理機制(靜態、動態)

Shape[] orchestra = {
 new Circle(),
 new Square(),
 new Triangle()
 };

觀察以上代碼,現在創建一個數組,聲明時是父類的類型,但是存儲的數據是具體實現的子類。

若需要將元素從數組中取出來時,Java虛擬機會自動將元素類型轉換為Shape,這就是RTTI最基本的使用形式。因為在Java當中所有的類型轉換都是在運行時進行操作,並且經過正確性檢查的。

大部分代碼儘可能少地瞭解對象的具體類型,而是隻與對象家族中的一個通用表示打交道(如這個例子中的Shape)。這樣的代碼形式容易理解且便於維護。

Java反射機制

重點還是回到Java程序在運行過程中需要識別每一個對象的類型進行識別所使用的方法,第一種已經初步瞭解了,接下來著重瞭解第二種方法——Java反射機制。

(1)定義

Java反射機制是在運行狀態中,能夠知道任何一個類的所有方法和屬性;可以調用任何一個對象的方法或屬性,這種動態獲取信息以及動態調用對象方法的功能稱為Java語言的反射機制。

(2)類Class

為了易於讀者理解,在介紹類Class之前,先介紹一個眾所熟知的基礎而又特殊的類Object,Java中常說萬物皆對象,而這個Object相當於萬物之父,可以表示任意的對象,也是所有對象的父類。來了解它的基礎方法:

Java 學習筆記--反射與代理機制(靜態、動態)

在熟悉了Object類後,就要引出Java中另一個基礎而又特殊的類——Class。在每裝載一個新類的時候,Java虛擬機就會在Java堆中創建一個Class的實例,這個實例就代表這個Class類型,通過實例獲取類型信息。該類中一些常用的方法如下:

Java 學習筆記--反射與代理機制(靜態、動態)

瞭解完Class類常用的三個方法後,我們可以意識到實際上此類就是幫組獲得某一個類的所有方法、域、構造方法,獲取後就可以調用使用類實例。

//創建Class類的一個對象,返回一個類的引用
Class class = Class.forName("Airplane");//返回一個類型
​
//通過類的引用創建實例
class.newInstance

需要注意的是Class類這個類型的對象表達的是一個類的引用。這個forName()是Class類的一個靜態方法,這樣class對象代表的是Airplane這個類型,得到這個類型引用了,接下來需要創建此類型,還是Class類的靜態方法 —newInstance(),此方法返回值實際上是一個實例,即之前指定好的Airplane,創建實例過程中一般是調用Airplane類的默認構造方法。

所以,通過以上兩行代碼就可以利用一個類名字符串去創建對應的類實例,來查看一個完整的例子:

class Airplane{
 public String toString(){
 return "in airplane";
 }
​
 publc class CreateInstance{
 public static void main(String[] args) throws Exception{
 //創建Class類的一個對象,描述了類Airplane
 Class class = Class.forName("Airplane");
 System.out.println(class);
​
 //創建實例的另外一個方法
 Object object = class.newInstance;
 System.out.println(object.toString());
 }
 }
}
​
輸出:
 class Airplane
 in airplane

我們知道傳統創建對象是通過new來操作,即Airplane air = new Airplane(),但是現在你知道了可以利用一個類的名字去創建這個類的實例,這樣更加靈活。

(4)Method類的invoke

上例已獲得類實例,那麼如何調用該類的方法呢?

Java反射機制中已提供了這種途徑:有一個類Method中的invoke方法,該方法對帶有指定參數的指定對象,調用Method對象中的表示的方法,即可獲取目標對象的方法。看下例:

public class ClassA{
 public void add(Integer param1, Integer param2){
 System.out.println(param1.intValue() + param2.intValue())
 }
​
 public void StringAdd(String abc){
 System.out.println("out" + abc);
 }
​
 public static void main(String[] args){
 try{
 Method mth = ClassA.class.getMethod("add", new Class[]{Integer.class, Integer.class});
 mth.invoke(ClassA.class.newInstance(), new Integer(1), new Interger(2));
​
 Method mth1 = ClassA.class.getMethod("StringAdd", new Class[]{Integer.class, Integer.class});
 mth1.invoke(ClassA.class.newInstance(), "--test"); 
 }catch(Exception e){}
 }
}
輸出:
 3
 out--test

可以看出ClassA這個類中包含兩個基本方法add、StringAdd,重點是try catch塊中的代碼,又接觸了Class類的一個新方法getMethod,為了可以調用ClassA中的add方法,則獲取ClassA類的Method對象,其中傳入的參數是方法名和方法參數。接著調用Method對象的invoke方法,第一個參數是方法歸屬類的實例對象,用法已經熟悉了,然後是調用add方法需要傳入的參數。這樣兩行代碼完成了對指定對象方法的調用,下一個方法的使用如法炮製,不再贅述。

所以通過Method類可以表示任意一個類中的方法,再調用Method類的invoke方法可以調用指定對象的方法。

二. Java靜態代理

首先來了解“代理”的含義,在Java中有些類或者對象不願直接被訪問、控制,它們通過中間的一箇中介渠道來進行訪問、控制。舉個生活中的例子,比如打官司過程律師就是代理,租房子時中介就是代理。在Java中任意對象都可以有代理,只是取決於什麼時候使用,這就引出代理模式。

代理模式介紹

在某些情況下,一個客戶不想或者不能直接引用另一個對象,而代理對象可以在客戶端和目標對象之間起到中介的作用。例如權限原因導致客戶端對象不能直接訪問目標對象,此時可以通過中介來架起兩者的橋樑。

代理模式的作用是為其它對象提供一種代理去控制或訪問目標對象.

Java 學習筆記--反射與代理機制(靜態、動態)

代理模式角色及使用實例

查看上圖,左上角是客戶端Client,它想要調用目標對象的requrast方法,左下角ProxySubject是一個代理對象,與右下角RealSubject真是對象擁有同樣的訪問接口,但是它還要做其它一些事情。以上我們清楚認識到了代理模式涉及到的角色:

  1. 抽象角色:聲明真實對象和代理對象的共同接口。即這個真實對象和代理角色都能夠提供哪些方法。
  2. 代理角色:代理對象角色內部包含對真實對象的引用,從而操作真實對象,同時代理對象提供與真實對象相同的接口以便在任何時刻都能夠替代真實對象。同時,代理對象可以在執行真實對象操作時,附加其它的操作,相當於對真實對象進行封裝,即在調用真實對象的方法時,可以在調用之前做一些預處理操作,在調用之後做一些後處理,諸如之類。
  3. 真實角色:代理角色所代表的真實對象,使我們最終要引用的對象。
//真實對象和代理對象的共同接口
abstact class Subject{
 public abstact void request();
}
​
//真實角色
class RealSubject extends Subject{
 public void requrst(){
 System.out.println("From Real Subject");
 }
}
​
//代理角色
class ProxySubject extends Subject{
 //代理角色對象內部含有對真實對象的引用
 private RealSubject realSubject;
 @Override
 public void requeat(){
 //在真實角色操作之前的預處理操作
 preRequest();
 if(null == realSubject){
 realSubject = new RealSubject();
 }
 //真實角色完成的操作
 realSubject.request();
 //在真實角色操作之後的後處理
 postRequest();
 }
​
 private void preRequest(){
 System.out.println("pre Request");
 }
 private void postRequest(){
 System.out.println("post Request");
 }
}
​
//客戶端
public class Client{
 public static void main(String[] args){
 Subject subject = new ProxySubject();
 subject.request();
 }
}
​
 

以上代碼重點放在代理類和其操作,首先抽象角色Subject聲明好代理角色和真實角色共有的方法,然後代理角色ProxySubject 和真實角色RealSubject 分別具體實現。最後,我們在main方法中創建了一個代理對象,通過代理角色這個中介間接調用了真實角色的操作(因為代理角色ProxySubject 內部持有真實角色RealSubject 的引用),並且代理角色中還可以做一些預處理和後處理操作。以上就是Java靜態代理的一個基本例子。

Java靜態代理的優缺點

優點:簡單明確,業務類只需要關注業務邏輯本身,保證了業務類的重用性,這是代理的共有優點。

缺點:代理對象的一個接口只服務於一種類型的對象,如果要代理的方法很多,勢必要為每一種方法都進行代理,靜態代理在程序規模稍大時就無法勝任了。不僅如此,後期增加抽象角色中的方法,會導致所以代理類和真實類都要實現此方法,增加了代碼維護的複雜度。

三. Java動態代理

Java動態代理顧名思義是相較於上一節的靜態代理,必定改進了靜態代理存在的一些缺點。首先來了解涉及到的類:

類Proxy

它是Java動態代理機制的主類,它提供了一組靜態方法來為一組接口動態地生成代理類及其對象。來查看主要方法:

Java 學習筆記--反射與代理機制(靜態、動態)

類InvocationHandler 及實例

這是調用處理器接口,它自定義了一個invoke方法,用於集中處理在動態代理類對象上的方法調用,通常在該方法中實現對委託類的代理訪問。

Object invoke(Object proxy, Method method, Object[] args) 

該方法負責集中處理動態代理類上的所有方法調用,第一個參數是代理類實例,第二個參數是委託類的方法對象,第三個參數是調用方法集合。調用處理器根據這三個參數進行預處理或分派到委託類實例上執行。

invoke這個方法很強大,它可以調用任意一個代理對象的方法,傳入參數,然後執行該方法。來看一個例子:

//抽象角色
interface Subject{
 public void request();
}
​
//真實角色
class RealSubject implements Subject{
 public void requrst(){
 System.out.println("From Real Subject");
 }
}
​
//代理角色,必須實現InvocationHandler
class DynamicSubject implements InvocationHandler{
 private Object sub;
 public DynamicSubject(){}
 public DynamicSubject(Object obj){
 sub = obj;
 }
 public Object invoke(Object proxy, Method method, Object[] args)throws Throwable{
 System.out.println("before calling " + method);
 method.invoke(sub, args);
 System.out.println("after calling " + method);
 return null;
 }
}
​
//客戶端
public class Client{
 public static void main(String[] args){
 //指定被代理類
 RealSubject rs = new RealSubject();
 InvocationHandler ds = new InvocationHandler(rs);
 //一次性生成代理
 Class cls = rs.getClass();
 Subject subject = (Subject)Proxy.newProxyInstance(cls.getClassLoader(), cla.getInterfaces(),ds);
 subject.request();
 }
}
​

首先來看代理類DynamicSubject,它需要實現的接口是InvocationHandler,在invoke方法中操作真實類RealSubject。在靜態方法中,調用真實類的重點是持有真實類的引用,在動態中查看invoke方法的參數,它還持有真實類具體方法Method的引用。同樣的,在調用真實類的具體方法前後可以進行一些預處理和後處理操作。

接著查看main方法中時如何使用代理類:

(1)首先實例化一個真實類RealSubject 的對象,它將會被用來指定代理類;

(2)接著實例化一個代理類DynamicSubject ,同時將真實類對象傳給代理類的構造方法,表示這個代理類代理的是哪一個真實類,最後賦值給調用的處理器InvocationHandler ,因為代理類實現了此接口,這也是一個“向上轉型”;

(3)獲取這個真實類實例化對象的類型Class;

(4)獲取Subject實例化對象,但是此對象是通過Proxy類的newProxyInstance來產生的,三個參數分別是真實類對象的類裝載器、真實類對象的接口,調用處理器。

(5)最後調用抽象角色Subject的指定接口方法。這樣後續會調用到代理類的invoke方法,而invoke方法裡間接調用了真實類的指定方法,最後完成這代理模式的真實類調用。

動態代理的特點

包:如果所代理的接口都是public的,那麼將被定義在頂層包(即包路徑為空), 如果所代理的接口中有非public 的接口,那麼它將被定義在該接口所在包,這樣設計的目的是為了最大程度的保證動態代理類不會因為包管理的問題二無法被成功定義並訪問。

類修飾符:該代理類具有final 和 public 修飾符,意味著它可以被所有的類訪問,但是不能被再度繼承;

類名:格式是“$ProxyN”,其中 N 是一個逐一遞增的阿拉伯數字,代表Proxy類第N此生成的動態代理類,值得注意的是:並不是每次調用 Proxy 類的靜態方法創建動態代理類都會使得N 值增加,原因是如果對同一組接口(包括接口排列順序相同)試圖重複創建動態代理類,它會返回先前已經創建好的代理類的類對象,而不是重複創建一個新的代理類。這樣可以節省不必要的重複代碼,提高了代理類的創建效率。

類繼承關係:該類的繼承關係如圖:

Java 學習筆記--反射與代理機制(靜態、動態)

動態代理的優缺點

優點:動態代理與靜態代理想比較,最大的好處是接口中聲明的所有方法都被轉移到調用處理器一個集中的方法中處理(InvocationHandler.invoke)。這樣,在接口方法數量較多的時候也可以進行靈活處理,而不需要像靜態代理那樣對每一個接口中的方法進行中轉。(上面的例子接口中只有一個方法,如果有2個及以上的方法,靜態代理和動態代理的差別會很明顯的顯示出來)

缺點: Proxy類已經設計得非常優美,但是它始終無法擺脫僅支持 interface代理的限制。

資源共享:黑馬程序員Java教學視頻以及資料包

私信回覆:Java

(注意英文大小寫要一致哦)


分享到:


相關文章: