代理設計模式之動態代理與靜態代理
為什麼需要代理:
打一個最簡單的比方,我現在想要學習,那麼就必須得把書包拿過來,把書掏出來,準備好紙筆,然後開始學習,等學完了我還得收拾書,把書塞回書包裡,還得整理一下書包,這是一個完整的學習的過程,但是我很懶,不想動彈,只想學習,那可能就得讓媽媽幫我把書包拿過來,把書開啟,我只管學習就好了,學完以後,媽媽再幫我把書整理好放回去.(我這是打個什麼破比方...),在這裡,媽媽就代表了一個代理物件,要學習的人是我,而我只管學習,這樣效率才最高,至於其他的交給代理物件(媽媽)做就好了,畫一個醜陋的的圖表示一下:
再聯想我們最開始接觸jdbc操作資料庫的時候,業務層每一個方法,都需要1.開啟資料庫連線,2.執行我們想要的操作
/**
* 簡單業務層介面,只有一個save方法
*/
interface UserService{
public void saveUser();
}
////////////////////////////////////////////////////////
/**
* 業務層實現類,實現save方法
*/
class UserServiceImpl implements UserService{
@Override
public void saveUser() {
System.out.println( "1:開啟資料庫連線");
System.out.println("2:儲存使用者資訊");
System.out.println("3:關閉資料庫連線");
}
}
我們可以看到其實這個方法的實現是有問題的,核心業務與輔助業務寫在了一個方法中,不但業務冗餘了不說,像開關資料庫連線這樣的公共操作也大量的重複,這時候就出現了代理模式的思想,我們可以使用代理模式的思想改寫一下上面的程式碼:
package com.wang.proxy;
/**
* 簡單業務層介面,只有一個save方法
*/
interface UserService{
public void saveUser();
}
/**
* 代理類
*/
class UserServiceProxy implements UserService{
private UserService userService;
public UserServiceProxy(UserService userService) {
super();
this.userService = userService;
}
public void open(){
System.out.println("1:開啟資料庫連線");
}
public void close(){
System.out.println("3:關閉資料庫連線");
}
@Override
public void saveUser() {
this.open();
userService.saveUser();
this.close();
}
}
/**
* 業務層實現類,實現save方法
*/
class UserServiceImpl implements UserService{
@Override
public void saveUser() {
System.out.println("2:儲存使用者資訊");
}
}
/**
* 測試類
*/
public class TestProxy {
public static void main(String[] args) {
UserService userService =new UserServiceProxy(new UserServiceImpl());
userService.saveUser();
}
}
通過測試程式碼列印結果,和上面沒有使用代理的程式碼是完全一樣,但是通過修改可以清晰地看到,業務層程式碼變得很純很純的,只剩下最核心的儲存使用者資訊的程式碼.通過代理模式,我們可以抽取出核心業務與輔助業務,但是問題隨之而來了,我這裡編寫的UserServiceProxy是挺不錯,可是它只能服務與UserService這個介面的物件啊,如果我有一千個業務,那豈不是要編寫一千個代理類,畢竟一千個人心中就有一千個哈姆雷特啊,其實這種代理模式就是靜態代理,它的缺點很明顯,靜態代理只能服務於一種型別的物件,不利於業務的擴充套件,那麼我們就想了,能不能設計一個代理類可以服務於所有的業務物件呢?於是,這時候,動態代理就閃亮登場了.
如果要實現動態代理,那麼你要編寫的那個代理類就需要實現一個InvocationHandle介面.這個介面所在位置是java.lang.reflect.InvocationHandler.看到reflect我們就能知道,動態代理肯定是通過反射來實現的了,這個介面中有一個方法:
Object invoke(Object proxy, Method method, Object[] args) :在代理例項上處理方法呼叫並返回結果。
invoke方法其實是反射裡邊的一個方法,在這個方法中有三個引數:
Ojbect proxy:表示需要代理的物件
Method method:表示要操作的方法
Object[] args:method方法所需要傳入的引數(可能沒有為,null.也可能有多個)
如果要想讓代理設計真正可用,我們還必須有一個代理類物件產生,這有用到了另一個類:java.lang.reflect.Proxy.我的中文jdk文件對他的描述是:
Proxy
提供用於建立動態代理類和例項的靜態方法,它還是由這些方法建立的所有動態代理類的超類。
在這個類下面,我們找到了這樣一個方法:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
該方法返回一個指定介面的代理類例項,該介面可以將方法呼叫指派到指定的呼叫處理程式.方法中有三個引數:
引數:
loader
- 定義代理類的類載入器
interfaces
- 代理類要實現的介面列表
h
- 指派方法呼叫的呼叫處理程式
返回:
一個帶有代理類的指定呼叫處理程式的代理例項,它由指定的類載入器定義,並實現指定的介面
ok,有了這些知識,我們就可以來編寫一個萬能的動態代理類了.
class ServiceProxy implements InvocationHandler {
private Object target=null;//儲存真實業務物件
/**
* 返回動態代理類的物件,這樣使用者才可以利用代理類物件去操作真實物件
* @param obj 包含有真實業務實現的物件
* @return 返回代理物件
*/
public Object getProxy(Object obj) {
this.target=obj;//儲存真實業務物件
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result=method.invoke(target, args);//通過反射呼叫真實業務物件的業務方法,並且返回
return result;
}
}
現在來測試一下好不好使:
/**
* 測試類
*/
public class TestProxy {
public static void main(String[] args) {
UserService service=(UserService) new ServiceProxy().getProxy(new UserServiceImpl());
service.saveUser();
}
}
//列印結果:
// 2.儲存使用者資訊
看!!!!完美,你說什麼?為什麼沒有開啟資料庫連線,和關閉資料庫連線呢? 簡單,我們直接在ServiceProxy中加入兩個方法,open()和close(),一個放到method.invoke()上面,一個放到下面,就可以了,注意,我們現在編寫的是一個萬能的動態代理類了,沒有和任何的業務層介面關聯,所以接下來你就可以為所欲為了.
下面來總結一下:
動態代理和靜態代理相比較,最大的好處就是介面中宣告的所有的方法都被轉移到一個集中的方法中去處理,就是invocke()方法.這樣在介面中宣告的方法比較多的情況下我們可以進行靈活處理,而不需要像靜態代理那樣每一個方法進行中轉。
動態代理只能代理介面,代理類都需要實現InvocationHandler類,實現invoke方法。該invoke方法就是呼叫被代理介面的所有方法時需要呼叫的,該invoke方法的返回值是被代理介面的一個實現類。
那麼有沒有可能我們可以不依賴介面呢?這時候就需要CGLIB實現動態代理了,這個jar包可以讓我們擺脫介面的煩惱,感興趣的自己去查一下吧,反正我也沒用,不會...