代理模式介紹
代理模式是23種常用的設計模式之一,其作用是爲其他對象提供一種代理來控制對這個對象的訪問。主要作用就是加以控制。該模式的好處:在目標對象實現的基礎上擴展目標對象的功能。
應用實例理解:
現實中,例如房子出售,買家與賣家無需接觸,買房和賣房的所有細節操作都交給中介,其他不用管,這裏的中介就是一個代理對象;買火車票不一定非要去火車站才能買,通過火車票代售點也可以買;還有國內是無法直接訪問一些外網的,這時我們就可以使用VPN等軟件來幫助我們去連接外網,至於連接細節不用關心,只需要上網就行。這裏的VPN就起到一箇中間層的作用,即代理對象。還有許多例子就不一一說明了。
代理模式特徵:
代理類與委託類必須實現同樣的接口,代理類主要負責爲委託類預處理消息、過濾消息、轉發消息給委託類以及事後處理消息等。兩者之間通常會存在關聯關係,一個代理類的對象與一個委託類的對象關聯,代理類對象本身並不真正實現服務,而是通過調用委託類對象的相關方法來提供特定的服務。
借用網上一張簡潔明瞭的圖來說明代理起到什麼作用(注:該圖是靜態代理模式):
代理種類:
代理分爲靜態代理和動態代理兩大類。實現動態代理可以用JDK自帶的API來實現,即JDK代理,還有一種就是cglib代理。
- 靜態代理:由程序員創建或由特定工具自動生成源代碼,再對其編譯。程序運行前,代理類的class文件就已存在。
- 動態代理:在程序運行時,運用反射機制動態創建而成。(JDK代理方式)
- 上面僅是對兩者作一個簡單的區別介紹,下面分別介紹兩種代理的使用方式及優缺點。
靜態代理
靜態代理實例:
/** 代理類和委託類實現的統一接口 */
public interface ImpUserDao {
void saveUser(); //保存用戶
}
/** 實現接口的委託類 */
public class UserDao implements ImpUserDao {
@Override
public void saveUser() { //省略User對象,理解就好
System.out.println("保存User對象數據");
}
}
/** 代理類 */
public class ProxyUserDao implements ImpUserDao{
//聲明與之關聯的委託類對象
private ImpUserDao ud;
//傳入實現ImpUserDao接口的類對象,即委託類對象
public ProxyUserDao(ImpUserDao ud) {
this.ud = ud;
}
//攔截UserDao,對其進行控制
@Override
public void saveUser() {
System.out.println("開啓事務...");
ud.saveUser(); //執行的是委託類的方法,代理類本身不提供真正實現
System.out.println("提交事務...");
}
}
/** 測試 */
public class Client {
public static void main(String[] args) {
UserDao ud = new UserDao();
ProxyUserDao proxy = new ProxyUserDao(ud);
proxy.saveUser();//執行代理類方法
}
}
靜態代理解析:
通過代理類,可以在不影響委託類的原有功能基礎上進行擴展,例如開啓事務和提交事務功能是委託類所沒有的,委託類只需要關心怎麼去提交數據,其他事情交給代理類來處理。這樣的好處是可以把核心代碼和輔助代碼分開,而且客戶端無需關注實現類(委託類),只需選擇代理就行。
靜態代理不足之處:
代理類需要和委託類實現同一接口,實現接口方法時還需要把委託類的實現方法放進來,這意味着會出現許多的重複代碼,導致代碼冗餘;每個代理類只能服務於一個類型對象,當每增加一種類型對象時,就需要創建一個對應代理類,隨着業務增長,類會越來越多,上層結構一改動,維護會越來越複雜。簡要來說就是:靜態代理類只能爲特定的接口(Service)服務。如想要爲多個接口服務則需要建立很多個代理類。 正因爲靜態代理有以上缺陷,所以需要採用動態代理來解決以上問題。動態代理最簡潔一點就是可以代理所有類型對象,這避免創建大量代理類,意味着只需一個代理類就行。
動態代理
Java動態代理的實現需要使用java.lang.reflect包下的InvocationHandler接口和Proxy類。InvocationHandler接口是代理實例調用方法時的調用處理程序,簡單來講就是代理實例做的任何事情都由它來包辦,代理類執行任何方法都是在調用該接口的invoke()方法。Proxy類的作用就是提供用於創建動態代理類和實例的靜態方法。
invoke()方法參數說明:
/** 參數說明
* proxy :表示動態代理對象
* method : 委託類要執行的方法
* args : 執行方法的參數 */
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
Proxy類主要方法:
//獲取一個代理類Class對象,需要提供關聯的類加載器和接口。 形參...表示可以傳入多個。
getProxyClass(ClassLoader loader,Class<?>... interfaces)
//獲取一個代理類實例,需要提供指定接口的類加載器、一組接口數組和將方法調用指派到指定的調用處理程序。
newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
//返回指定動態代理實例的調用處理程序。
getInvocationHandler(Object proxy)
//判斷指定的Class對象是否爲一個動態代理類
isProxyClass(Class<?> cl)
// 參數說明
// loader : 定義代理類的類加載器
// interfaces : 代理類要實現的接口列表
// h : 指派方法調用的調用處理程序
動態代理實例:
/** 接口 */
interface UserDaoImp{
void saveUser();
}
/** 實現接口的委託類,即目標類 */
class UserDao implements UserDaoImp{
@Override
public void saveUser() {
System.out.println("---執行savaUser()---");
}
}
/** 自定義InvocationHandler 調用處理程序*/
class MyHandler implements InvocationHandler{
private Object tar; //保存目標類對象
public MyHandler(Object tar) {
this.tar = tar;
}
//代理類調用方法時,統一在此處進行處理
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("---開啓事務---");
method.invoke(tar, args); //調用目標類對象的方法
System.out.println("---關閉事務---");
return null;
}
}
//測試
public class Client {
public static void main(String[] args) throws Exception{
//爲指定接口生成動態代理類Class字節碼對象
Class proClass = Proxy.getProxyClass(UserDaoImp.class.getClassLoader(), UserDaoImp.class);
//獲得構造器,參數類型是InvocationHandler.class
Constructor constr = proClass.getConstructor(InvocationHandler.class);
//傳入自定義InvocationHandler的實例對象,生成對應的代理類對象
UserDaoImp iud = (UserDaoImp)constr.newInstance( new MyHandler(new UserDao()) );
iud.saveUser();
}
}
//委託類對象
UserDao ud = new UserDao();
UserDaoImp udi = (UserDaoImp) Proxy.newProxyInstance(UserDaoImp.class.getClassLoader(), //加載接口的類加載器
ud.getClass().getInterfaces(), //要代理的一組接口
new MyHandler(ud)); //代理實例的調用處理程序
udi.saveUser();
//輸出結果
---開啓事務---
---執行savaUser()---
---關閉事務---
interface UserDaoImp {
void saveUser();
}
interface TestImp{
void test();
}
//實現類,實現了以上兩個接口
public class UserDao implements UserDaoImp, TestImp{
@Override
public void saveUser() {
System.out.println("執行saveUser()...");
}
@Override
public void test() {
System.out.println("執行test()...");
}
}
//所有代理的統一調用處理程序類
public class MyHandler implements InvocationHandler{
private Object target; //實現類對象
public MyHandler() {}
//傳入要代理對象,生成對應代理實例
public Object createProxy(Object target) {
this.target = target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this); //this:表示該代理實例調用的是當前的調用處理器
}
/** 代理類無論執行什麼方法,都會統一調用invoke() */
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("打開事務...");
method.invoke(target, args);
System.out.println("關閉事務...");
return null;
}
}
//測試
public class Client {
public static void main(String[] args) {
//委託類對象
UserDao ud = new UserDao();
//獲取相應的代理實例
UserDaoImp udi = (UserDaoImp)new MyHandler().createProxy(ud);
TestImp tt = (TestImp)new MyHandler().createProxy(ud);
udi.saveUser();
tt.test();
}
}
//輸出結果:
打開事務...
執行saveUser()...
關閉事務...
打開事務...
執行test()...
關閉事務...
動態代理解析:
通過上面的結果來看,如果是採用靜態代理來完成,則需要兩個代理類,還要保證和類實現的接口是一致的。而通過動態代理,連代理類都不用創建,需要代理時再生成對應的代理實例,不僅邏輯上簡潔明瞭,而且代碼量還變得很少;接口中方法都被放在invoke()進行統一處理。但動態代理的缺點也很明顯, 要代理的某個類必須實現接口,而生成的代理類也只能代理接口定義的方法,如果是類本身獨有的方法就沒辦法代理。所以可以去使用cglib代理,cglib可以對類進行代理,而不僅限於接口。有興趣的可以去查找相關資料。以上,就是關於靜態代理和動態代理是如何使用的,以及它們的優缺點。