代理模式(Proxy Pattern)是23種設計模式中的一種,屬于結構型設計模式。代理模式給某一個對象提供一個代理,并由代理對象控制原對象的引用。代理對象在客戶端和目標對象之間起到中介作用。
舉個例子:你要去吃飯,你可以選擇自己在家做飯、吃飯、刷碗,所有的事情都自己做;也可以選擇去餐廳,自己只是吃飯,把做飯和刷碗的活兒都交給代理對象,也就是餐廳的工作人員。
下圖是代理模式的通用類圖。結合例子,就很容易理解了。
代理模式通用類圖
代理模式包含如下角色:
代理模式可以分為靜態代理和動態代理兩種類型,而動態代理中又分為JDK動態代理和CGLIB代理兩種。
在jdk的動態代理機制中,有幾個重要的角色:
每一個動態代理類都必須要實現InvocationHandler這個接口,并且每個代理類的實例都關聯了一個handler,當我們通過代理對象調用一個方法的時候,這個方法的調用就會被轉發為由InvocationHandler這個接口的 invoke 方法來進行調用。
InvocationHandler這個接口的唯一一個方法 invoke 方法:
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
這個方法一共接受三個參數,那么這三個參數分別代表如下:
Proxy這個類的作用就是用來動態創建一個代理對象的類,它提供了許多的方法,但是我們用的最多的就是newProxyInstance 這個方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler) throws IllegalArgumentException
這個方法的作用就是得到一個動態的代理對象,其接收三個參數,我們來看看這三個參數所代表的含義:
所以我們所說的DynamicProxy(動態代理類)是這樣一種class:它是在運行時生成的class,在生成它時你必須提供一組interface給它,然后該class就宣稱它實現了這些 interface。這個DynamicProxy其實就是一個Proxy,它不會做實質性的工作,在生成它的實例時你必須提供一個handler,由它接管實際的工作。
被代理對象:
/** * 抽象主題角色 */interface Subject { void eat();}/** * 真實主題角色 - 你自己 - 專注吃飯 */class YourSelf implements Subject{ @Override public void eat() { System.out.println("自己吃飯"); }}
代理對象:
/** * 代理主題角色 - 餐廳 * 每次生成動態代理類對象時都需要指定一個實現了InvocationHandler接口的調用處理器對象 */class JdkProxySubject implements InvocationHandler { // 這個就是我們要代理的真實對象,也就是真正執行業務邏輯的類 private Object target; // 通過構造方法傳入這個被代理對象 public JdkProxySubject(Object target) { super(); this.target = target; } // 創建代理對象 public Object createProxy() { // 1.得到目標對象的類加載器 ClassLoader classLoader = target.getClass().getClassLoader(); // 2.得到目標對象的實現接口 Class<?>[] interfaces = target.getClass().getInterfaces(); // 3.第三個參數需要一個實現invocationHandler接口的對象 Object newProxyInstance = Proxy.newProxyInstance(classLoader, interfaces, this); return newProxyInstance; } // 當代理對象調用真實對象的方法時,其會自動的跳轉到代理對象關聯的handler對象的invoke方法來進行調用 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("餐廳工作人員做飯......"); Object invoke = method.invoke(target, args); System.out.println("餐廳工作人員刷碗......"); return invoke; }}
測試類:
/** * 測試類 * @author tianxiaopeng@hxy * @date 2023/10/11 11:09 AM */public class ProxyTest { public static void main(String[] args) { // 1.創建對象 YourSelf yourSelf = new YourSelf(); // 2.創建代理對象 JdkProxySubject proxy = new JdkProxySubject(yourSelf); // 3.調用代理對象的增強方法,得到增強后的對象 Subject createProxy = (Subject) proxy.createProxy(); createProxy.eat(); }}
JDK動態代理是通過重寫被代理對象實現的接口中的方法來實現,而CGLIB是通過繼承被代理對象來實現,和JDK動態代理需要實現指定接口一樣,CGLIB也要求代理對象必須要實現MethodInterceptor接口,并重寫其唯一的方法intercept。
CGLib采用了非常底層的字節碼技術,其原理是通過字節碼技術為一個類創建子類,并在子類中采用方法攔截的技術攔截所有父類方法的調用,順勢織入橫切邏輯。(利用ASM開源包,對代理對象類的class文件加載進來,通過修改其字節碼生成子類來處理)
注意:因為CGLIB是通過繼承目標類來重寫其方法來實現的,故而如果是final和private方法則無法被重寫,也就是無法被代理。
<dependency> <groupId>cglib</groupId> <artifactId>cglib-nodep</artifactId> <version>2.2</version></dependency>
net.sf.cglib.proxy.Enhancer:主要增強類,通過字節碼技術動態創建委托類的子類實例。
Enhancer可能是CGLIB中最常用的一個類,和Java1.3動態代理中引入的Proxy類差不多。和Proxy不同的是,Enhancer既能夠代理普通的class,也能夠代理接口。Enhancer創建一個被代理對象的子類并且攔截所有的方法調用(包括從Object中繼承的toString和hashCode方法)。Enhancer不能夠攔截final方法,例如Object.getClass()方法,這是由于Java final方法語義決定的。基于同樣的道理,Enhancer也不能對fianl類進行代理操作。這也是Hibernate為什么不能持久化final class的原因。
net.sf.cglib.proxy.MethodInterceptor:常用的方法攔截器接口,需要實現intercept方法,實現具體攔截處理。
public java.lang.Object intercept(java.lang.Object obj, java.lang.reflect.Method method, java.lang.Object[] args, MethodProxy proxy) throws java.lang.Throwable{}
創建被代理類。
/** * 真實主題角色 - 你自己 - 專注吃飯 */class YourSelf { public void eat(){ System.out.println("自己吃飯"); }}
創建代理類:
/** * 代理主題角色 - 餐廳 */class ProxyCglib implements MethodInterceptor { private Enhancer enhancer = new Enhancer(); public Object getProxy(Class clazz){ //設置需要創建子類的類 enhancer.setSuperclass(clazz); enhancer.setCallback(this); //通過字節碼技術動態創建子類實例 return enhancer.create(); } //實現MethodInterceptor接口方法 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("餐廳工作人員做飯......"); //通過代理類調用父類中的方法 Object result = proxy.invokeSuper(obj, args); System.out.println("餐廳工作人員刷碗......"); return result; }}
測試類:
/** * 測試類 * @author tianxiaopeng@hxy * @date 2023/10/11 11:51 AM */public class CglibTest { public static void main(String[] args) { ProxyCglib proxy = new ProxyCglib(); //通過生成子類的方式創建代理類 YourSelf proxyImp = (YourSelf)proxy.getProxy(YourSelf.class); proxyImp.eat(); }}
結果:
餐廳工作人員做飯......自己吃飯餐廳工作人員刷碗......
CGLib動態代理采用了FastClass機制,其分別為代理類和被代理類各生成一個FastClass,這個FastClass類會為代理類或被代理類的方法分配一個 index(int類型)。這個index當做一個入參,FastClass 就可以直接定位要調用的方法直接進行調用,這樣省去了反射調用,所以調用效率比 JDK 動態代理通過反射調用更高。
但是我們看上面的源碼也可以明顯看到,JDK動態代理只生成一個文件,而CGLIB生成了三個文件,所以生成代理對象的過程會更復雜。
本文鏈接:http://www.www897cc.com/showinfo-26-38516-0.html詳解JDK動態代理和CGLib動態代理
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
上一篇: 手把手教你寫設計方案,你學明白了嗎?
下一篇: 原生CSS嵌套使用,你學明白了嗎?