![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHL9UERPJTUU9UeRRVT3V1MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL0ETO4ATN1QTM2ETMwkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
設計模式中有一種模式叫代理模式,Spring架構離不開動态代理技術,Android hook技術用到了反射 + 動态代理,Framework中我們也經常看到各種proxy,如ApplicationThreadProxy, ActivityManagerProxy。
那麼,今天就來說下Java中的代理模式和動态代理。
目錄:
- 代理模式
- 靜态代理
- 動态代理
- 代理模式的優缺點
- 代理模式的使用場景
- 動态代理原理分析
1. 代理模式
代理模式是常用的java設計模式,它的特征是代理類與委托類有相同的接口,代理類主要負責為委托類預處理消息、過濾消息、把消息轉發給委托類,以及事後處理消息等。代理類與委托類之間通常會存在關聯關系,一個代理類的對象與一個委托類的對象關聯,代理類的對象本身并不真正實作服務,而是通過調用委托類的對象的相關方法,來提供特定的服務。簡單的說就是,我們在通路實際對象時,是通過代理對象來通路的,代理模式就是在通路實際對象時引入一定程度的間接性,因為這種間接性可以附加多種用途。
舉一個通俗點的例子:
我們男生都愛看NBA,比如詹姆斯轉會湖人,球隊老闆不會直接找詹姆斯,而是找他的經紀人商談,這中間就攔了一道。詹姆斯可以打球,代言,他的經紀人也具有他一樣的屬性:打球,代言,但是真正打球,代言的是詹姆斯本人。
畫一個草圖了解下:
代理模式根據建立代理類的時間點,又可以分為靜态代理和動态代理。
2. 靜态代理
- 2.1 概念
由程式員建立或特定工具自動生成源代碼,也就是在編譯時就已經将接口,被代理類,代理類等确定下來。在程式運作之前,代理類的.class檔案就已經生成。
- 2.2 例子
private interface IProxy {
void playBasketball();
void endorsement();
}
private static final class James implements IProxy {
@Override
public void playBasketball() {
System.out.println("James play basketball");
}
@Override
public void endorsement() {
System.out.println("James endorsement");
}
}
private static final class Middleman implements IProxy {
final James james;
Middleman(James james) {
this.james = james;
}
@Override
public void playBasketball() {
System.out.println("Let's talk about the money, let James play basketball");
james.playBasketball();
}
@Override
public void endorsement() {
System.out.println("Let's talk about the money, let James endorsement");
james.playBasketball();
}
}
public static void main(String[] args) {
Middleman middleman = new Middleman(new James());
middleman.playBasketball();
middleman.endorsement();
}
執行輸出:
Let's talk about the money, let James play basketball
James play basketball
Let's talk about the money, let James endorsement
James play basketball
3. 動态代理
- 3.1 概念
代理類在程式運作時建立的代理被成為動态代理。 我們上面靜态代理的例子中,代理類(Middleman)是自己定義好的,在程式運作之前就已經編譯完成。然而動态代理,代理類并不是在Java代碼中定義的,而是在運作時根據我們在Java代碼中的"訓示"動态生成的。相比于靜态代理, 動态代理的優勢在于可以很友善的對代理類的函數進行統一的處理,而不用修改每個代理類中的方法。
- 3.2 "java.lang.reflect.Proxy"類
現在要生成某一個對象的代理對象,這個代理對象通常也要編寫一個類來生成,是以首先要編寫用于生成代理對象的類。在java中如何用程式去生成一個對象的代理對象呢,java在JDK1.5之後提供了一個"java.lang.reflect.Proxy"類,通過"Proxy"類提供的一個newProxyInstance方法用來建立一個對象的代理對象,如下所示:
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
newProxyInstance()方法用來傳回一個代理對象,這個方法總共有3個參數,ClassLoader loader用來指明生成代理對象使用哪個類裝載器,Class<?>[] interfaces用來指明生成哪個對象的代理對象,通過接口指定,InvocationHandler h用來指明産生的這個代理對象要做什麼事情。是以我們隻需要調用newProxyInstance方法就可以得到某一個對象的代理對象了。
- 3.3 例子
private interface IProxy {
void playBasketball();
void endorsement();
}
private static final class James implements IProxy {
@Override
public void playBasketball() {
System.out.println("James play basketball");
}
@Override
public void endorsement() {
System.out.println("James endorsement");
}
}
private static final class MiddlemanProxy {
private IProxy james = new James();
IProxy getProxy() {
return (IProxy) Proxy.newProxyInstance(James.class.getClassLoader(), james.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("playBasketball")) {
System.out.println("Let's talk about the money, let James play basketball");
return method.invoke(james, args);
} else if (method.getName().equals("endorsement")) {
System.out.println("Let's talk about the money, let James endorsement");
return method.invoke(james, args);
}
return null;
}
});
}
}
public static void main(String[] args) {
MiddlemanProxy middlemanProxy = new MiddlemanProxy();
IProxy proxy = middlemanProxy.getProxy();
proxy.playBasketball();
proxy.endorsement();
}
執行輸出:
Let's talk about the money, let James play basketball
James play basketball
Let's talk about the money, let James endorsement
James endorsement
可以看到,效果與靜态代理一樣,後面将分析動态代理的實作原理。
4. 代理模式的優缺點
優點:
- 代理模式可以将代理對象和真實被調用的目标對象隔離。
- 一定程度上降低了系統耦合度,擴充性好。
- 保護目标對象。
- 增強目标對象。
缺點:
- 代理模式會造成系統設計中類的數量增加。
- 在用戶端和目标對象中間增加一個代理對象,會造成請求處理速度變慢。
- 增加系統的複雜度。
5. 代理模式的使用場景
- 1.遠端(Remote)代理:為一個位于不同的位址空間的對象提供一個局域代表對象。這個不同的位址空間可以是在本機器中,也可是在另一台機器中。遠端代理又叫做大使(Ambassador)。好處是系統可以将網絡的細節隐藏起來,使得用戶端不必考慮網絡的存在。客戶完全可以認為被代理的對象是局域的而不是遠端的,而代理對象承擔了大部份的網絡通訊工作。由于客戶可能沒有意識到會啟動一個耗費時間的遠端調用,是以客戶沒有必要的心理準備。
- 2.虛拟(Virtual)代理:懶加載,根據需要建立一個資源消耗較大的對象,使得此對象隻在需要時才會被真正建立。使用虛拟代理模式的好處就是代理對象可以在必要的時候才将被代理的對象加載,代理可以對加載的過程加以必要的優化。當一個子產品的加載十分耗費資源的情況下,虛拟代理的好處就非常明顯。
- 3.Copy-on-Write代理:虛拟代理的一種。把複制(克隆)拖延到隻有在用戶端需要時,才真正采取行動。
- 4.保護(Protect or Access)代理:控制對一個對象的通路,如果需要,可以給不同的使用者提供不同級别的使用權限。保護代理的好處是它可以在運作時間對使用者的有關權限進行檢查,然後在核實後決定将調用傳遞給被代理的對象。
- 5.Cache代理:為某一個目标操作的結果提供臨時的存儲空間,以便多個用戶端可以共享這些結果。
- 6.防火牆(Firewall)代理:保護目标,不讓惡意使用者接近。
- 7.同步化(Synchronization)代理:使幾個使用者能夠同時使用一個對象而沒有沖突。
- 8.智能引用(Smart Reference)代理:當一個對象被引用時,提供一些額外的操作,比如将對此對象調用的次數記錄下來等。
6. 動态代理原理分析
以Proxy.newProxyInstance()作為入口分析。
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
// InvocationHandler不能為空
Objects.requireNonNull(h);
// 将代理接口clone生成intfs
final Class<?>[] intfs = interfaces.clone();
// 擷取系統的安全管理類
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
// 檢查通路權限
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
// 關鍵的一句,擷取代理類的Class對象
Class<?> cl = getProxyClass0(loader, intfs);
try {
if (sm != null) {
// 校驗權限
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
// 擷取代理類的構造器
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
// 生成代理對象
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
可以看到,利用了反射的機制,關鍵一句:Class<?> cl = getProxyClass0(loader, intfs)
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
return proxyClassCache.get(loader, interfaces);
}
這裡産生了代理類,後面代碼中的構造器也是通過這裡産生的類來獲得,可以看出這個類的産生就是整個動态代理的關鍵,由于是動态生成的類檔案,這裡不具體進入分析如何産生的這個類檔案,隻需要知道這個類檔案是緩存在java虛拟機中的。
我們可以來看看系統生成的代理類class是什麼樣的:
public static void main(String[] args) {
MiddlemanProxy middlemanProxy = new MiddlemanProxy();
IProxy proxy = middlemanProxy.getProxy();
proxy.playBasketball();
proxy.endorsement();
byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", James.class.getInterfaces());
FileOutputStream fos = null;
try {
fos = new FileOutputStream(new File("Proxy0.class"));
fos.write(classFile);
fos.flush();
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
看看生成的Proxy0.class:
public final class $Proxy0 extends Proxy implements IProxy {
private static Method m1;
private static Method m4;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final void endorsement() throws {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void playBasketball() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m4 = Class.forName("io.kzw.advance.csdn_blog.TestDynamicProxy$IProxy").getMethod("endorsement");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("io.kzw.advance.csdn_blog.TestDynamicProxy$IProxy").getMethod("playBasketball");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
可以看到上面JDK生成的代理類class,和靜态代理類的結構差不多,隻不過調用目标對象是通過反射的方式。