你好!今天我們將考慮一個相當重要且有趣的話題:Java 中動態代理類的創建。這不是很簡單,所以我們將嘗試使用示例來弄明白 :) 那麼,最重要的問題是:什麼是動態代理以及它們的用途是什麼?代理類是原始類之上的一種“附加組件”,它允許我們在必要時更改原始類的行為。“改變行為”是什麼意思,它是如何運作的?考慮一個簡單的例子。假設我們有一個Person接口和一個實現這個接口的 簡單Man類
遇到這種情況怎麼辦?我們需要一些東西:
例如,它被積極地用於與安全相關的流行技術和框架中。想像一下,您有 20 個方法只能由登錄到您的程序的用戶執行。使用您學到的技術,您可以輕鬆地向這 20 種方法添加檢查以查看用戶是否輸入了有效憑據,而無需在每種方法中復制驗證碼。 或者假設您想創建一個記錄所有用戶操作的日誌。使用代理也很容易做到這一點。即使是現在,您也可以簡單地向我們上面的示例添加代碼,以便在您調用invoke()時顯示方法名稱,這將生成我們程序的超級簡單日誌 :) 最後,請注意一個重要的限制。代理對象使用接口,而不是類。為接口創建代理。看看這段代碼:
public interface Person {
public void introduce(String name);
public void sayAge(int age);
public void sayWhereFrom(String city, String country);
}
public class Man implements Person {
private String name;
private int age;
private String city;
private String country;
public Man(String name, int age, String city, String country) {
this.name = name;
this.age = age;
this.city = city;
this.country = country;
}
@Override
public void introduce(String name) {
System.out.println("My name is " + this.name);
}
@Override
public void sayAge(int age) {
System.out.println("I am " + this.age + " years old");
}
@Override
public void sayWhereFrom(String city, String country) {
System.out.println("I'm from " + this.city + ", " + this.country);
}
// ...getters, setters, etc.
}
我們的Man類有 3 個方法:introduce、sayAge 和 sayWhereFrom。想像一下,我們把這個類作為現成 JAR 庫的一部分,我們不能簡單地重寫它的代碼。但是我們也需要改變它的行為。例如,我們不知道可能會在我們的對像上調用哪個方法,但我們希望我們的人說“嗨!” (沒有人喜歡不禮貌的人)調用任何方法時。 
-
調用處理器
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class PersonInvocationHandler implements InvocationHandler {
private Person person;
public PersonInvocationHandler(Person person) {
this.person = person;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Hi!");
return null;
}
}
我們只需要實現一個接口方法:invoke()。而且,順便說一句,它做了我們需要的事情:它攔截了對我們對象的所有方法調用並添加了必要的行為(在invoke()方法內,我們向控制台輸出“Hi!”)。
- 原始對象及其代理。
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
// Create the original object
Man arnold = new Man("Arnold", 30, "Thal", "Austria");
// Get the class loader from the original object
ClassLoader arnoldClassLoader = arnold.getClass().getClassLoader();
// Get all the interfaces that the original object implements
Class[] interfaces = arnold.getClass().getInterfaces();
// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
// Call one of our original object's methods on the proxy object
proxyArnold.introduce(arnold.getName());
}
}
這看起來不是很簡單!我專門為每一行代碼添加了註釋。讓我們仔細看看發生了什麼。在第一行中,我們簡單地創建我們將為其創建代理的原始對象。以下兩行可能會給您帶來困難:
// Get the class loader from the original object
ClassLoader arnoldClassLoader = arnold.getClass().getClassLoader();
// Get all the interfaces that the original object implements
Class[] interfaces = arnold.getClass().getInterfaces();
實際上,這裡並沒有什麼特別的事情發生 :) 在第四行中,我們使用了特殊的Proxy類及其靜態的newProxyInstance()方法:
// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
這個方法只是創建我們的代理對象。我們將在上一步中收到的有關原始類的信息(其ClassLoader及其接口列表)以及先前創建的InvocationHandler對像傳遞給該方法。最主要的是不要忘記將我們原來的arnold對像傳遞給調用處理程序,否則將沒有任何東西可以“處理”:) 我們最終得到了什麼?我們現在有一個代理對象:proxyArnold。它可以調用Person接口的任何方法。為什麼?因為我們在這裡給了它所有接口的列表:
// Get all the interfaces that the original object implements
Class[] interfaces = arnold.getClass().getInterfaces();
// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
現在它知道了Person接口 的所有方法。此外,我們向代理傳遞了一個配置為與arnold對像一起使用的PersonInvocationHandler對象:
// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
現在,如果我們在代理對像上調用Person接口 的任何方法,我們的處理程序會攔截該調用並改為執行它自己的invoke()方法。讓我們嘗試運行main()方法!控制台輸出:
Hi!
出色的!我們看到調用了PersonInvocationHandler()的invoke()方法,而不是原來的Person.introduce()方法:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Hi!");
return null;
}
“你好!” 顯示在控制台上,但這並不是我們想要的行為:/我們試圖實現的是首先顯示“Hi!” 然後調用原來的方法本身。換句話說,方法調用
proxyArnold.introduce(arnold.getName());
應該顯示“嗨!我叫阿諾德”,而不是簡單的“嗨!” 我們怎樣才能做到這一點?這並不復雜:我們只需要對我們的處理程序和invoke()方法採取一些自由:) 注意傳遞給此方法的參數:
public Object invoke(Object proxy, Method method, Object[] args)
invoke ()方法可以訪問最初調用的方法及其所有參數(Method 方法、Object[] args)。換句話說,如果我們調用proxyArnold.introduce(arnold.getName())方法以便調用invoke()方法而不是introduce()方法,那麼在該方法中我們可以訪問原始的introduce()方法及其論點!因此,我們可以這樣做:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class PersonInvocationHandler implements InvocationHandler {
private Person person;
public PersonInvocationHandler(Person person) {
this.person = person;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Hi!");
return method.invoke(person, args);
}
}
現在在invoke()方法中,我們添加了對原始方法的調用。如果我們現在嘗試運行之前示例中的代碼:
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
// Create the original object
Man arnold = new Man("Arnold", 30, "Thal", "Austria");
// Get the class loader from the original object
ClassLoader arnoldClassLoader = arnold.getClass().getClassLoader();
// Get all the interfaces that the original object implements
Class[] interfaces = arnold.getClass().getInterfaces();
// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
// Call one of our original object's methods on the proxy object
proxyArnold.introduce(arnold.getName());
}
}
然後我們會看到現在一切正常:) 控制台輸出:
Hi! My name is Arnold
你什麼時候可能需要這個?實際上,經常。“動態代理”設計模式在流行技術中被積極使用……哦,對了,忘了說動態代理是一種設計模式!恭喜,你又學到了一個!:) 
// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
這裡我們專門為Person接口 創建了一個代理。如果我們嘗試為該類創建一個代理,即更改引用類型並嘗試轉換為Man類,它將無法工作。
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
proxyArnold.introduce(arnold.getName());
線程“main”中的異常 java.lang.ClassCastException:com.sun.proxy.$Proxy0 無法轉換為 Man 擁有接口是絕對必要的。代理與接口一起工作。這就是今天的全部:) 好吧,現在解決一些任務就好了!:) 直到下一次!
GO TO FULL VERSION