上班的路上看到一篇關於代理模式的博客,上班前做下自己的總結,從代理模式的簡介–>用法–>優缺點
代理模式
簡介
用一個通俗易通的例子來介紹下代理模式的場景,就比如在北京,一個房子的主人想要賣掉房子,他不想自己做這個事情,而是將這個事情交給房產中介來做,房產中介再找客戶,籤合同,走相關流程。這裏出現幾個點:房子主人、房產中介、賣房子、客戶;房子主人就是被代理類,房產中介就是代理類、而賣房子是前兩者需要做的事情,客戶就是客戶端。也就是說,房子主人和房產中介需要共同繼承一個類或者接口來完成賣房子的事情。而代理模式又分爲靜態代理和動態代理,接下來我們從這幾種類型出發來完成上述的事情
靜態代理
簡介
靜態代理的話,就是,代理類和被代理類實現一個共同的接口,完成相應的操作,這裏的代理類需要開發者去手動寫出,在編譯期這個代理類就已經確定了,說到這裏可能大家有點不明白,我把代碼附上,做些解釋
怎麼用
賣房子接口:
package com.markus.proxy.staticproxy;
public interface SaleHouse {
public void sale();//賣房子
}
房子主人
package com.markus.proxy.staticproxy;
public class HomeHost implements SaleHouse {
public void sale() {
System.out.println("賣房子");
}
}
房產中介
package com.markus.proxy.staticproxy;
public class HomeAgent implements SaleHouse {
private SaleHouse saleHouse;
public HomeAgent(SaleHouse saleHouse){
this.saleHouse = saleHouse;
}
public void sale() {
preSale();
saleHouse.sale();
postSale();
}
private void preSale(){
System.out.println("找客戶");
}
private void postSale(){
System.out.println("籤合同,拿佣金");
}
}
客戶(測試類):
package com.markus.proxy.staticproxy;
public class Client {
public static void main(String[] args) {
SaleHouse homeHost = new HomeHost();
SaleHouse homeAgent = new HomeAgent(homeHost);
homeAgent.sale();
}
}
測試結果:
小結:
整個邏輯中,可以看到代理類和被代理類都去實現一個接口,而被代理類不需要主動去執行,而是將這個事情交給代理類去實現,並且還可以在方法執行前後可以執行其他東西
總結
優點:
- 代理模式在客戶端與目標對象之間起到一箇中介作用和保護目標對象的作用
- 代理對象可以擴展目標對象的功能
- 代理模式可以將客戶端和目標對象分離,在一定程度上起到了解耦的作用
缺點:
- 因爲代理對象和目標對象實現一樣的接口,所有會產生很多的代理類,類很多的情況下,如果在接口上新增一個方法,會很難維護
動態代理
在代理模式中會有代理類,在靜態代理中,需要開發者寫出大量的代理類,如果接口新增一個方法的話,會很難維護。動態代理是採用反射的方法,在運行期間動態的生成代理類,它在簡化了程序開發的工作,也提高了軟件系統的可擴展性,因爲java反射可以在運行時動態的生成任何類型的動態代理類
jdk動態代理
簡介
在jdk動態代理中,java.lang.reflect包中的proxy類和InvocationHandler接口提供了生成動態代理類的能力
動作接口:
package com.markus.proxy.dynamicproxy;
public interface SaleHouse {
public void sale();
}
被代理類:
package com.markus.proxy.dynamicproxy;
public class HomeHost implements SaleHouse {
public void sale() {
System.out.println("賣房子");
}
}
自定義實現InvocationHandler的類:
package com.markus.proxy.dynamicproxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyInvocationHandler implements InvocationHandler {
private Object instance;
public MyInvocationHandler(Object instance){
this.instance = instance;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
preInvoke();
Object invoke = method.invoke(instance, args);
aftInvoke();
return invoke;
}
private void preInvoke(){
System.out.println("中介找客戶");
}
private void aftInvoke(){
System.out.println("中介籤合同,領佣金");
}
}
測試:
package com.markus.proxy.dynamicproxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Client {
public static void main(String[] args) {
HomeHost homeHost = new HomeHost();
InvocationHandler invocationHandler = new MyInvocationHandler(homeHost);
//三個參數
//classLoader:需要被代理類的類加載期
//interface:需要被代理類實現的接口
//自定義實現的InvocationHandler
//得到被代理類實現的接口
SaleHouse dynamicproxy = (SaleHouse) Proxy.newProxyInstance(HomeHost.class.getClassLoader(),HomeHost.class.getInterfaces(), invocationHandler);
dynamicproxy.sale();
System.out.println(dynamicproxy.getClass());
}
}
測試結果:
當我們賣的房子是高檔房子的話,其中複雜性比賣普通房子高,在靜態代理情況下,我們是不是應該再去寫一個代理類,來處理這個工作,而動態代理的話,只需要實現一次就可以了,代理類自動生成。
靜態代理模式這裏不做演示。我來寫下動態代理模式下的實現吧。
看看擴展後的架構
程序開發上沒有自己寫代理類,就擴展了一個新功能
動態代理類是如何實現的呢?
小插曲:現在博客上的內容咋啥都有,正不正確也都往上寫,也不標註什麼時候用,見別人這麼寫他也這麼寫。害的我找老半天,最終在一篇博客下看到了正解的評論,感謝!!!
言歸正傳:我們如何去查看自動生成的動態代理類呢?
首先在你的測試類裏輸入這樣一行代碼:一定要看清我代碼中的註釋
//1、jdk1.8版本的朋友們,你們先在測試類裏複製上這行代碼,如果沒效果那就用第二行代碼!!!粘貼一個就行
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//2、記住,只有在上一行代碼不好使得情況下再去嘗試下這行代碼
System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
//ps:我是第一行代碼就好使
這時候我們可以看到idea裏生成了一個包
這裏面的兩個類就是動態生成的代理類,點進去看一下
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.sun.proxy;
import com.markus.proxy.dynamicproxy.SaleHouse;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements SaleHouse {
private static Method m1;
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 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 sale() 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"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.markus.proxy.dynamicproxy.SaleHouse").getMethod("sale");
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的動態代理爲什麼需要被代理對象必須要有一個接口了嗎?
這裏需要繼承一個Proxy類,然後實現了被代理類的接口,因爲java是單繼承的,所以必須要實現接口
cglib動態代理
jdk動態代理必須要實現一個接口才能成功生成代理類,那如果沒有接口呢,那麼基於cglib動態代理就不需要實現接口,而是基於類實現的動態代理
首先這是一個外部文件,我們先要引入cglib依賴
<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
</dependencies>
被代理類:
package com.markus.proxy.dynamicproxy1;
public class VillaHost {
public void sale(){
System.out.println("我要賣別墅");
}
}
cglib攔截器
package com.markus.proxy.dynamicproxy1;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxyInterceptor implements MethodInterceptor {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
start();
Object invokeSuper = methodProxy.invokeSuper(o, objects);
end();
return invokeSuper;
}
private void start(){
System.out.println("賣別墅前");
}
private void end(){
System.out.println("賣別墅後");
}
}
測試:
package com.markus.proxy.dynamicproxy1;
import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;
public class Client {
public static void main(String[] args) {
//在指定目錄下生成動態代理類
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"D:\\myProject\\myStudy");
Enhancer enhancer = new Enhancer();
//設置目標類的class文件
enhancer.setSuperclass(VillaHost.class);
//設置回調函數
enhancer.setCallback(new CglibProxyInterceptor());
//創建代理類
VillaHost proxy = (VillaHost) enhancer.create();
proxy.sale();
System.out.println("賣房子的人是誰:"+proxy.getClass());
}
}
測試結果:
總結:
- cglib動態代理其實是生成被代理類的子類,通過攔截器調用intercept方法,這個intercept方法有自定義的攔截器中方法執行。
- cglib是一種高性能、高質量的Code生成庫,它可以在運行期間擴展java類和接口
- 用cglib生成類其實就是被代理類的子類
- cglib生成的代理類不需要接口,它是繼承被代理類,實現factory接口
- 用cglib生成的代理類重寫了父類的各個方法
- 攔截器中的intercept方法的內容其實就是代理類中的方法體
總結
代理分爲靜態代理和動態代理,而動態代理又分爲jdk動態代理和cglib動態代理
靜態代理的話,它需要開發者自己實現定義代理類,也就是說,在編譯期,代理類的字節碼文件就已經存在了,如果想要擴展新的事情的話,就要拓展一個代理類,難以維護。
動態代理的話,就能解決上面的問題,它是在程序運行期間動態的生成代理類,不需要開發者自己拓展。在實現動態代理的時候,有兩種實現方式,一種是jdk動態代理,它要求被代理類必須實現接口,原因我們在觀看代理類源碼的時候已經分析了。第二種方式就是cglib動態代理,它不需要有接口。