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