做外貿(mào)要自己建網(wǎng)站嗎網(wǎng)頁免費制作網(wǎng)站
文章目錄
- 第五章 結構型模式
- 5.1 代理模式
- 5.1.1 代理模式介紹
- 5.1.2 代理模式原理
- 5.1.3 靜態(tài)代理實現(xiàn)
- 5.1.4 JDK動態(tài)代理
- 5.1.4.1 JDK動態(tài)代理實現(xiàn)
- 5.1.4.2 類是如何動態(tài)生成的
- 5.1.4.3 代理類的調用過程
- 5.1.5 cglib動態(tài)代理
- 5.1.5.1 cglib動態(tài)代理實現(xiàn)
- 5.1.5.2 cglib代理流程
- 5.1.6 代理模式總結
- 5.1.6.1 三種代理模式實現(xiàn)方式的對比
- 5.1.6.2 代理模式優(yōu)缺點
- 5.1.6.2 代理模式使用場景
個人主頁:道友老李
歡迎加入社區(qū):道友老李的學習社區(qū)
第五章 結構型模式
我們已經(jīng)學習過了設計模式中的創(chuàng)建型模式. 創(chuàng)建型模式主要解決對象的創(chuàng)建問題,封裝復雜的創(chuàng)建過程,解耦對象的創(chuàng)建代碼和使用代碼.
- 單例模式用來創(chuàng)建全局唯一對象
- 工廠模式用來創(chuàng)建不同但是相關類型的對象(繼承同一父類或者接口的一組子類),由給定的參數(shù)來決定創(chuàng)建哪種類型的對象.
- 建造者模式是用來創(chuàng)建復雜對象,可以通過設置不同的可選參數(shù),定制化地創(chuàng)建不同的對象.
- 原型模式針對創(chuàng)建成本比較大的對象,利用對已有對象進行復制的方式進行創(chuàng)建,以達到節(jié)省創(chuàng)建時間的目的.
從本節(jié)課開始我們來學習結構型設計模式, 結構型模式主要總結了一些類和對象組合在一起的經(jīng)典結構,這些經(jīng)典結構可以解決對應特定場景的問題.
一共包括七種:代理模式、橋接模式、裝飾者模式、適配器模式、門面(外觀)模式、組合模式、和享元模式。
5.1 代理模式
5.1.1 代理模式介紹
在軟件開發(fā)中,由于一些原因,客戶端不想或不能直接訪問一個對象,此時可以通過一個稱為"代理"的第三者來實現(xiàn)間接訪問.該方案對應的設計模式被稱為代理模式.
代理模式(Proxy Design Pattern ) 原始定義是:讓你能夠提供對象的替代品或其占位符。代理控制著對于原對象的訪問,并允許將請求提交給對象前后進行一些處理。
- 現(xiàn)實生活中的代理: 海外代購
-
軟件開發(fā)中的代理
代理模式中引入了一個新的代理對象,代理對象在客戶端對象和目標對象之間起到了中介的作用,它去掉客戶不能看到的內容和服務或者增加客戶需要的額外的新服務.
5.1.2 代理模式原理
代理(Proxy)模式分為三種角色:
- 抽象主題(Subject)類: 聲明了真實主題和代理主題的共同接口,這樣就可以保證任何使用真實主題的地方都可以使用代理主題,客戶端一般針對抽象主題類進行編程。
- 代理(Proxy)類 : 提供了與真實主題相同的接口,其內部含有對真實主題的引用,它可以在任何時候訪問、控制或擴展真實主題的功能。
- 真實主題(Real Subject)類: 實現(xiàn)了抽象主題中的具體業(yè)務,是代理對象所代表的真實對象,是最終要引用的對象。
5.1.3 靜態(tài)代理實現(xiàn)
這種代理方式需要代理對象和目標對象實現(xiàn)一樣的接口。
-
優(yōu)點:可以在不修改目標對象的前提下擴展目標對象的功能。
-
缺點:
-
冗余。由于代理對象要實現(xiàn)與目標對象一致的接口,會產(chǎn)生過多的代理類。
-
不易維護。一旦接口增加方法,目標對象與代理對象都要進行修改。
-
舉例:保存用戶功能的靜態(tài)代理實現(xiàn)
//接口類: IUserDao
public interface IUserDao {void save();
}
//目標對象:UserDaoImpl
public class UserDaoImpl implements IUserDao {@Overridepublic void save() {System.out.println("保存數(shù)據(jù)");}
}//靜態(tài)代理對象:UserDaoProxy 需要實現(xiàn)IUserDao接口
public class UserDaoProxy implements IUserDao {private IUserDao target;public UserDaoProxy(IUserDao target) {this.target = target;}@Overridepublic void save() {System.out.println("開啟事務"); //擴展額外功能target.save();System.out.println("提交事務");}
}//測試類
public class TestProxy {@Testpublic void testStaticProxy(){//目標對象UserDaoImpl userDao = new UserDaoImpl();//代理對象UserDaoProxy proxy = new UserDaoProxy(userDao);proxy.save();}
}
5.1.4 JDK動態(tài)代理
5.1.4.1 JDK動態(tài)代理實現(xiàn)
動態(tài)代理利用了JDK API,動態(tài)地在內存中構建代理對象,從而實現(xiàn)對目標對象的代理功能.動態(tài)代理又被稱為JDK代理或接口代理.
靜態(tài)代理與動態(tài)代理的區(qū)別:
- 靜態(tài)代理在編譯時就已經(jīng)實現(xiàn)了,編譯完成后代理類是一個實際的class文件
- 動態(tài)代理是在運行時動態(tài)生成的,即編譯完成后沒有實際的class文件,而是在運行時動態(tài)生成類字節(jié)碼,并加載到JVM中.
JDK中生成代理對象主要涉及的類有
- java.lang.reflect Proxy,主要方法為
static Object newProxyInstance(ClassLoader loader, //指定當前目標對象使用類加載器Class<?>[] interfaces, //目標對象實現(xiàn)的接口的類型InvocationHandler h //事件處理器
)
//返回一個指定接口的代理類實例,該接口可以將方法調用指派到指定的調用處理程序。
-
java.lang.reflect InvocationHandler,主要方法為
Object invoke(Object proxy, Method method, Object[] args) // 在代理實例上處理方法調用并返回結果。
舉例:保存用戶功能的靜態(tài)代理實現(xiàn)
/*** 代理工廠-動態(tài)生成代理對象**/
public class ProxyFactory {private Object target; //維護一個目標對象public ProxyFactory(Object target) {this.target = target;}//為目標對象生成代理對象public Object getProxyInstance(){//使用Proxy獲取代理對象return Proxy.newProxyInstance(target.getClass().getClassLoader(), //目標類使用的類加載器target.getClass().getInterfaces(), //目標對象實現(xiàn)的接口類型new InvocationHandler(){ //事件處理器/*** invoke方法參數(shù)說明* @param proxy 代理對象* @param method 對應于在代理對象上調用的接口方法Method實例* @param args 代理對象調用接口方法時傳遞的實際參數(shù)* @return: java.lang.Object 返回目標對象方法的返回值,沒有返回值就返回null*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("開啟事務");//執(zhí)行目標對象方法method.invoke(target, args);System.out.println("提交事務");return null;}});}}//測試
public static void main(String[] args) {IUserDao target = new UserDaoImpl();System.out.println(target.getClass());//目標對象信息IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance();System.out.println(proxy.getClass()); //輸出代理對象信息proxy.save(); //執(zhí)行代理方法
}
5.1.4.2 類是如何動態(tài)生成的
Java虛擬機類加載過程主要分為五個階段:加載、驗證、準備、解析、初始化。其中加載階段需要完成以下3件事情:
- 通過一個類的全限定名來獲取定義此類的二進制字節(jié)流
- 將這個字節(jié)流所代表的靜態(tài)存儲結構轉化為方法區(qū)的運行時數(shù)據(jù)結構
- 在內存中生成一個代表這個類的
java.lang.Class
對象,作為方法區(qū)這個類的各種數(shù)據(jù)訪問入口
由于虛擬機規(guī)范對這3點要求并不具體,所以實際的實現(xiàn)是非常靈活的,關于第1點,獲取類的二進制字節(jié)流(class字節(jié)碼)就有很多途徑:
-
從本地獲取
-
從網(wǎng)絡中獲取
-
運行時計算生成,這種場景使用最多的是動態(tài)代理技術,在 java.lang.reflect.Proxy 類中,就是用了 ProxyGenerator.generateProxyClass 來為特定接口生成形式為
*$Proxy
的代理類的二進制字節(jié)流
所以,動態(tài)代理就是想辦法,根據(jù)接口或目標對象,計算出代理類的字節(jié)碼,然后再加載到JVM中使用
5.1.4.3 代理類的調用過程
我們通過借用阿里巴巴的一款線上監(jiān)控診斷產(chǎn)品 Arthas(阿爾薩斯) ,對動態(tài)生成的代理類代碼進行查看。
代理類代碼如下:
package com.sun.proxy;import com.mashibing.proxy.example01.IUserDao;
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 IUserDao {private static Method m1;private static Method m3;private static Method m2;private static Method m0;public $Proxy0(InvocationHandler invocationHandler) {super(invocationHandler);}static {try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));m3 = Class.forName("com.mashibing.proxy.example01.IUserDao").getMethod("save", new Class[0]);m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);return;}catch (NoSuchMethodException noSuchMethodException) {throw new NoSuchMethodError(noSuchMethodException.getMessage());}catch (ClassNotFoundException classNotFoundException) {throw new NoClassDefFoundError(classNotFoundException.getMessage());}}public final boolean equals(Object object) {try {return (Boolean)this.h.invoke(this, m1, new Object[]{object});}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}public final String toString() {try {return (String)this.h.invoke(this, m2, null);}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}public final int hashCode() {try {return (Integer)this.h.invoke(this, m0, null);}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}public final void save() {try {this.h.invoke(this, m3, null);return;}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}
}
簡化后的代碼
package com.sun.proxy;import com.mashibing.proxy.example01.IUserDao;
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 IUserDao {private static Method m3;public $Proxy0(InvocationHandler invocationHandler) {super(invocationHandler);}static {try {m3 = Class.forName("com.mashibing.proxy.example01.IUserDao").getMethod("save", new Class[0]);return;}}public final void save() {try {this.h.invoke(this, m3, null);return;}}
}
-
動態(tài)代理類對象 繼承了 Proxy 類,并且實現(xiàn)了被代理的所有接口,以及equals、hashCode、toString等方法
-
代理類的構造函數(shù),參數(shù)是
InvocationHandler
實例,Proxy.newInstance
方法就是通過這個構造函數(shù)來創(chuàng)建代理實例的 -
類和所有方法都被
public final
修飾,所以代理類只可被使用,不可以再被繼承 -
每個方法都有一個 Method 對象來描述,Method 對象在static靜態(tài)代碼塊中創(chuàng)建,以
m + 數(shù)字
的格式命名 -
調用方法的時候通過
this.h.invoke(this, m3, null));
實際上 h.invoke就是在調用ProxyFactory中我們重寫的invoke方法@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("開啟事務");//執(zhí)行目標對象方法method.invoke(target, args);System.out.println("提交事務");return null; }
5.1.5 cglib動態(tài)代理
5.1.5.1 cglib動態(tài)代理實現(xiàn)
cglib (Code Generation Library ) 是一個第三方代碼生成類庫,運行時在內存中動態(tài)生成一個子類對象從而實現(xiàn)對目標對象功能的擴展。cglib 為沒有實現(xiàn)接口的類提供代理,為JDK的動態(tài)代理提供了很好的補充。
- 最底層是字節(jié)碼
- ASM是操作字節(jié)碼的工具
- cglib基于ASM字節(jié)碼工具操作字節(jié)碼(即動態(tài)生成代理,對方法進行增強)
- SpringAOP基于cglib進行封裝,實現(xiàn)cglib方式的動態(tài)代理
使用cglib 需要引入cglib 的jar包,如果你已經(jīng)有spring-core的jar包,則無需引入,因為spring中包含了cglib 。
- cglib 的Maven坐標
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.2.5</version>
</dependency>
示例代碼
目標類
public class UserServiceImpl {public List<User> findUserList(){return Collections.singletonList(new User("tom",18));}
}public class User {private String name;private int age;.....
}
cglib代理類,需要實現(xiàn)MethodInterceptor接口,并指定代理目標類target
public class UserLogProxy implements MethodInterceptor {private Object target;public UserLogProxy(Object target) {this.target = target;}public Object getLogProxy(){//增強器類,用來創(chuàng)建動態(tài)代理類Enhancer en = new Enhancer();//設置代理類的父類字節(jié)碼對象en.setSuperclass(target.getClass());//設置回調: 對于代理類上所有的方法的調用,都會調用CallBack,而Callback則需要實現(xiàn)intercept()方法進行攔截en.setCallback(this);//創(chuàng)建動態(tài)代理對象并返回return en.create();}/*** 實現(xiàn)回調方法* @param o 代理對象* @param method 目標對象中的方法的Method實例* @param args 實際參數(shù)* @param methodProxy 代理對象中的方法的method實例* @return: java.lang.Object*/@Overridepublic Object intercept(Object o, Method method, Object[] args,MethodProxy methodProxy) throws Throwable {Calendar calendar = Calendar.getInstance();SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");System.out.println(formatter.format(calendar.getTime()) + " [" +method.getName() + "] 查詢用戶信息...]");Object result = methodProxy.invokeSuper(o, args);return result;}
}
public class Client {public static void main(String[] args) {//目標對象UserServiceImpl userService = new UserServiceImpl();System.out.println(userService.getClass());//代理對象UserServiceImpl proxy = (UserServiceImpl) new UserLogProxy(userService).getLogProxy();System.out.println(proxy.getClass());List<User> userList = proxy.findUserList();System.out.println("用戶信息: "+userList);}
}
5.1.5.2 cglib代理流程
5.1.6 代理模式總結
5.1.6.1 三種代理模式實現(xiàn)方式的對比
-
jdk代理和CGLIB代理
使用CGLib實現(xiàn)動態(tài)代理,CGLib底層采用ASM字節(jié)碼生成框架,使用字節(jié)碼技術生成代理類,在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能對聲明為final的類或者方法進行代理,因為CGLib原理是動態(tài)生成被代理類的子類。
在JDK1.6、JDK1.7、JDK1.8逐步對JDK動態(tài)代理優(yōu)化之后,在調用次數(shù)較少的情況下,JDK代理效率高于CGLib代理效率,只有當進行大量調用的時候,JDK1.6和JDK1.7比CGLib代理效率低一點,但是到JDK1.8的時候,JDK代理效率高于CGLib代理。所以如果有接口使用JDK動態(tài)代理,如果沒有接口使用CGLIB代理。
-
動態(tài)代理和靜態(tài)代理
動態(tài)代理與靜態(tài)代理相比較,最大的好處是接口中聲明的所有方法都被轉移到調用處理器一個集中的方法中處理(InvocationHandler.invoke)。這樣,在接口方法數(shù)量比較多的時候,我們可以進行靈活處理,而不需要像靜態(tài)代理那樣每一個方法進行中轉。
如果接口增加一個方法,靜態(tài)代理模式除了所有實現(xiàn)類需要實現(xiàn)這個方法外,所有代理類也需要實現(xiàn)此方法。增加了代碼維護的復雜度。而動態(tài)代理不會出現(xiàn)該問題
5.1.6.2 代理模式優(yōu)缺點
優(yōu)點:
- 代理模式在客戶端與目標對象之間起到一個中介作用和保護目標對象的作用;
- 代理對象可以擴展目標對象的功能;
- 代理模式能將客戶端與目標對象分離,在一定程度上降低了系統(tǒng)的耦合度;
缺點:
- 增加了系統(tǒng)的復雜度;
5.1.6.2 代理模式使用場景
-
功能增強
當需要對一個對象的訪問提供一些額外操作時,可以使用代理模式
-
遠程(Remote)代理
實際上,RPC 框架也可以看作一種代理模式,GoF 的《設計模式》一書中把它稱作遠程代理。通過遠程代理,將網(wǎng)絡通信、數(shù)據(jù)編解碼等細節(jié)隱藏起來??蛻舳嗽谑褂?RPC 服務的時候,就像使用本地函數(shù)一樣,無需了解跟服務器交互的細節(jié)。除此之外,RPC 服務的開發(fā)者也只需要開發(fā)業(yè)務邏輯,就像開發(fā)本地使用的函數(shù)一樣,不需要關注跟客戶端的交互細節(jié)。
-
防火墻(Firewall)代理
當你將瀏覽器配置成使用代理功能時,防火墻就將你的瀏覽器的請求轉給互聯(lián)網(wǎng);當互聯(lián)網(wǎng)返回響應時,代理服務器再把它轉給你的瀏覽器。
-
保護(Protect or Access)代理
控制對一個對象的訪問,如果需要,可以給不同的用戶提供不同級別的使用權限。