設計模式-代理模式
代理(Proxy)是一種設計模式,提供了對目標對象另外的訪問方式;即通過代理對象訪問目標對象.這樣做的好處是:可以在目標對象實現的基礎上,增強額外的功能操作,即擴展目標對象的功能.
這裏使用到編程中的一個思想:不要隨意去修改別人已經寫好的代碼或者方法,如果需改修改,可以通過代理的方式來擴展該方法
舉個例子來說明代理的作用:
1. 假設我們想邀請一位明星,那麼並不是直接連接明星,而是聯繫明星的經紀人,來達到同樣的目的.明星就是一個目標對象,他只要負責活動中的節目,而其他瑣碎的事情就交給他的代理人(經紀人)來解決.這就是代理思想在現實中的一個例子
2. 例如你想要帶你的愛人去你去浪漫的土耳其,然後一起去東京和巴黎…… 在出行時你需要購買到達土耳其的機票,而你沒有去機場的售票廳,購買而是在某個旅行網站上購買的。你通過在旅行網站購票完成了你的購票操作,這個旅行網站就是代理。
代理模式的關鍵點是:代理對象與目標對象.代理對象是對目標對象的擴展,並會調用目標對象
而我們常用的代理有:靜態代理、動態代理(JDK代理、cglib代理)
1.1 靜態代理
概念
靜態代理比較簡單,是由程序員編寫的代理類,並在程序運行前就編譯好的,而不是由程序動態產生代理類,這就是所謂的靜態。
靜態代理在使用時,需要定義接口或者父類,被代理對象與代理對象一起實現相同的接口或者是繼承相同父類.
考慮這樣的場景,管理員在網站上執行操作,在生成操作結果的同時需要記錄操作日誌。這是很常見的,此時就可以使用代理模式。
UML圖
實現
//方式一:接口式靜態代理
//1.抽象操作接口
public interface Manager {
void doSomething();
}
//2.實現操作接口的類
public class Admin implements Manager {
public void doSomething() {
System.out.println("Admin do something.");
}
}
//3.以聚合方式實現的代理操作
public class AdminPoly implements Manager{
private Manager manager;
public AdminPoly(Manager manager) {
this.manager = manager;
}
public void doSomething() {
System.out.println("Log:admin操作開始");
admin.doSomething();
System.out.println("Log:admin操作結束");
}
}
//4.測試代碼
public class ProxyPatternAdminDemo {
public static void main(String[] args) {
Admin admin = new Admin();
Manager m = new AdminPoly(admin);
m.doSomething();
}
}
//方式二:繼承式靜態代理
//與上面的方式僅代理類和測試代碼不同
//1.代理類
public class AdminsProxy extends Admins {
@Override
public void doSomething() {
System.out.println("Log:admin操作開始");
super.doSomething();
System.out.println("Log:admin操作開始");
}
}
//2.測試代碼
public class ProxyPatternAdminsDemo {
public static void main(String[] args) {
AdminsProxy proxy = new AdminsProxy();
proxy.doSomething();
}
}
總結
優點:
- 可以做到在符合開閉原則的情況下對目標對象進行功能擴展。
- 協調調用者和被調用者,降低了系統的耦合度。
- 代理對象作爲客戶端和目標對象之間的中介,起到了保護目標對象的作用。
缺點:- 我們得爲每一個服務都得創建代理類,工作量太大,不易管理。同時接口一旦發生改變,代理類也得相應修改。
- 由於在客戶端和真實主題之間增加了代理對象,因此會造成請求的處理速度變慢;
- 實現代理模式需要額外的工作(有些代理模式的實現非常複雜),從而增加了系統實現的複雜度。
1.2 動態代理
動態代理不同於靜態代理的特點是它更爲靈活,因爲動態代理就是在運行期間動態生成代理類。
動態代理分兩種,一種是基於接口實現的Java Proxy(Java自帶的),一種是基於繼承實現的cglib代理。下面會分別給出一個小demo,並且從源碼解析角度來解析二者動態代理的實現。
1.2.1 jdk 代理
假設有五百個不一樣的人要結婚,都交給婚慶公司來操辦,那麼按照靜態代理的思路來做,我們需要寫五百個真實角色,並且代理角色持有這五百個真實角色。這顯然不合邏輯。這時候動態代理就應運而生了。
public interface Marry {
public void marry();
}
public class Walidake implements Marry {
@Override
public void marry() {
System.out.println(this.getClass().getSimpleName() + "結婚啦");
}
}
public class WeddingCompany implements InvocationHandler{
private Object target;
public WeddingCompany(Object target) {
this.target = object;
}
// 生成代理對象
public Object getProxy() {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Class<?>[] interfaces = target.getClass().getInterfaces();
return Proxy.newProxyInstance(loader, interfaces, this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if ("marry".equals(method.getName())) {
System.out.println("婚禮籌備");
method.invoke(object, args);
System.out.println("婚禮結束");
}
return null;
}
}
InvocationHandler相當於一個處理器,在invoke方法中我們能夠操作真實對象,可以附加其他操作。而我們通過Proxy.newProxyInstance(…)方法生成代理。下面invoke參數的解釋說明。
參數 | 說明 |
---|---|
proxy | 指代我們所代理的那個真實對象 |
method | 指代的是我們所要調用真實對象的某個方法的Method對象 |
args | 指代的是調用真實對象某個方法時接受的參數 |
實現InvocationHandler接口並附加操作後,獲取代理角色。
//第一個人
Walidake walidake = new Walidake();
WeddingCompany weddingCompany = new WeddingCompany();
Marry marry = weddingCompany.getInstance(walidake).getProxy();
marry.marry();
System.out.println();
//第二個人
Other other = new Other();
WeddingCompany weddingCompany = new WeddingCompany();
Marry marry2 = weddingCompany.getInstance(other).getProxy();
marry2.marry();
動態代理類的字節碼在程序運行時由Java反射機制動態生成,無需程序員手工編寫。
動態代理類不僅簡化了編程工作,而且提高了軟件系統的可擴展性 。
因爲Java的反射機制可以生成任意類型的動態代理類。
java.lang.reflact包中的Proxy類和InvocationHandler接口 提供了生成動態代理的能力
但是使用JDK動態代理
生成的代理類是繼承了Proxy類的,這就是說明了爲什麼使用JDK動態代理不能實現繼承式動態代理,
原因是Java不允許多繼承,而生成的代理類本身就已經繼承了Proxy類。
**總結:**以上代碼只有標黃的部分是需要自己寫出,其餘部分全都是固定代碼。由於java封裝了newProxyInstance這個方法的實現細節,所以使用起來才能這麼方便,具體的底層原理將會在下一小節說明。
**缺點:**可以看出靜態代理和JDK代理有一個共同的缺點,就是目標對象必須實現一個或多個接口,加入沒有,則可以使用Cglib代理。
1.2.2 cglib代理
cglib是一個強大的高性能的代碼生成包,可以爲那些沒有接口的類創建模仿(moke)對象。上一小節我們說到Java Proxy是通過生成字節碼,再把類加載進內存後實現Proxy進行動態代理的。同樣地,cglib也是通過生成操作字節碼的技術實現動態代理的。但與前者不同的是它並不直接操作字節碼,而是通過一個小而快的字節碼處理框架ASM(Java字節碼操控框架),來轉換字節碼並生成新的類。因此,cglib包要依賴於asm包,需要一起導入。
前提條件:
需要引入cglib的jar文件,由於Spring的核心包中已經包括了Cglib功能,所以也可以直接引入spring-core-3.2.5.jar
目標類不能爲final
目標對象的方法如果爲final/static,那麼就不會被攔截,即不會執行目標對象額外的業務方法
/**
* 目標對象,沒有實現任何接口
*/
public class Singer{
public void sing() {
System.out.println("唱一首歌");
}
}
/**
* Cglib子類代理工廠
*/
public class ProxyFactory implements MethodInterceptor{
// 維護目標對象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
// 給目標對象創建一個代理對象
public Object getProxyInstance(){
//1.工具類
Enhancer en = new Enhancer();
//2.設置父類
en.setSuperclass(target.getClass());
//3.設置回調函數
en.setCallback(this);
//4.創建子類(代理對象)
return en.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("向觀衆問好");
//執行目標對象的方法
Object returnValue = method.invoke(target, args);
System.out.println("謝謝大家");
return returnValue;
}
}
/**
* 測試類
*/
public class Test{
public static void main(String[] args){
//目標對象
Singer target = new Singer();
//代理對象
Singer proxy = (Singer)new ProxyFactory(target).getProxyInstance();
//執行代理對象的方法
proxy.sing();
}
}
動態代理與靜態代理相比較,最大的好處是接口中聲明的所有方法都被轉移到調用處理器一個集中的方法中處理。在接口方法數量比較多的時候,我們可以進行靈活處理,而不需要像靜態代理那樣對每一個方法或方法組合進行處理。Proxy 很美很強大,但是僅支持 interface 代理。Java 的單繼承機制註定了這些動態代理類們無法實現對 class 的動態代理。好在有cglib爲Proxy提供了彌補。class與interface的區別本來就模糊,在java8中更是增加了一些新特性,使得interface越來越接近class,當有一日,java突破了單繼承的限制,動態代理將會更加強大。