自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

面試官問 Spring AOP 中兩種代理模式的區(qū)別,我懵圈了

開發(fā)
代理模式是一種結(jié)構(gòu)型設(shè)計模式。為對象提供一個替身,以控制對這個對象的訪問。即通過代理對象訪問目標(biāo)對象,并允許在將請求提交給對象前后進(jìn)行一些處理。

本文轉(zhuǎn)載自微信公眾號「 JavaKeeper」,作者海星 。轉(zhuǎn)載本文請聯(lián)系 JavaKeeper公眾號。

[[330874]]

基本介紹

代理模式是一種結(jié)構(gòu)型設(shè)計模式。為對象提供一個替身,以控制對這個對象的訪問。即通過代理對象訪問目標(biāo)對象,并允許在將請求提交給對象前后進(jìn)行一些處理。

被代理的對象可以是遠(yuǎn)程對象、創(chuàng)建開銷大的對象或需要安全控制的對象。

代理模式主要有三種不同的形式:

  • 靜態(tài)代理:由程序員創(chuàng)建代理類或特定工具自動生成源代碼再對其編譯。在程序運(yùn)行前代理類的 .class 文件就已經(jīng)存在了
  • 動態(tài)代理(JDK 代理、接口代理):在程序運(yùn)行時運(yùn)用反射機(jī)制動態(tài)創(chuàng)建而成,動態(tài)就是在程序運(yùn)行時生成的,而不是編譯時。
  • cglib 代理(可以在內(nèi)存動態(tài)的創(chuàng)建對象,而不是實(shí)現(xiàn)接口,屬于動態(tài)代理的范疇)

問題為什么要控制對于某個對象的訪問呢?舉個例子:有這樣一個消耗大量系統(tǒng)資源的巨型對象, 你只是偶爾需要使用它, 并非總是需要。

圖:refactoringguru.cn

你可以實(shí)現(xiàn)延遲初始化:在實(shí)際有需要時再創(chuàng)建該對象。對象的所有客戶端都要執(zhí)行延遲初始代碼。不幸的是, 這很可能會帶來很多重復(fù)代碼。

在理想情況下, 我們希望將代碼直接放入對象的類中, 但這并非總是能實(shí)現(xiàn):比如類可能是第三方封閉庫的一部分。

解決方案

代理模式建議新建一個與原服務(wù)對象接口相同的代理類, 然后更新應(yīng)用以將代理對象傳遞給所有原始對象客戶端。代理類接收到客戶端請求后會創(chuàng)建實(shí)際的服務(wù)對象, 并將所有工作委派給它。

圖:refactoringguru.cn

代理將自己偽裝成數(shù)據(jù)庫對象, 可在客戶端或?qū)嶋H數(shù)據(jù)庫對象不知情的情況下處理延遲初始化和緩存查詢結(jié)果的工作。

這有什么好處呢?如果需要在類的主要業(yè)務(wù)邏輯前后執(zhí)行一些工作, 你無需修改類就能完成這項(xiàng)工作。由于代理實(shí)現(xiàn)的接口與原類相同, 因此你可將其傳遞給任何一個使用實(shí)際服務(wù)對象的客戶端。

代理模式結(jié)構(gòu)

圖:refactoringguru.cn

  1. 服務(wù)接口 (Service Interface) 聲明了服務(wù)接口。代理必須遵循該接口才能偽裝成服務(wù)對象。
  2. 服務(wù) (Service) 類提供了一些實(shí)用的業(yè)務(wù)邏輯。
  3. 代理 (Proxy) 類包含一個指向服務(wù)對象的引用成員變量。代理完成其任務(wù) (例如延遲初始化、 記錄日志、 訪問控制和緩存等) 后會將請求傳遞給服務(wù)對象。通常情況下, 代理會對其服務(wù)對象的整個生命周期進(jìn)行管理。
  4. 客戶端 (Client) 能通過同一接口與服務(wù)或代理進(jìn)行交互, 所以你可在一切需要服務(wù)對象的代碼中使用代理。

打游戲有代練、買賣房子有中介代理、再比如一般公司投互聯(lián)網(wǎng)廣告也可以找代理公司,這里的代練、中介、廣告代理公司扮演的角色都是代理。

這里舉個更接近程序員的例子,比如有些變態(tài)的公司不允許在公司刷微博,看視頻,可以通過一層代理來限制我們訪問這些網(wǎng)站。

廢話不多說,先來個靜態(tài)代理。

靜態(tài)代理

1、定義網(wǎng)絡(luò)接口

  1. public interface Internet { 
  2.     void connectTo(String serverHost) throws Exception; 

2、真正的網(wǎng)絡(luò)連接

  1. public class RealInternet implements Internet{ 
  2.  
  3.     @Override 
  4.     public void connectTo(String serverHost) throws Exception { 
  5.         System.out.println("Connecting to "+ serverHost); 
  6.     } 

3、公司的網(wǎng)絡(luò)代理

  1. public class ProxyInternet implements Internet { 
  2.  
  3.     //目標(biāo)對象,通過接口聚合 
  4.     private Internet internet; 
  5.  
  6.     // 通過構(gòu)造方法傳入目標(biāo)對象 
  7.     public ProxyInternet(Internet internet){ 
  8.         this.internet = internet; 
  9.     } 
  10.     //網(wǎng)絡(luò)黑名單 
  11.     private static List<String> bannedSites; 
  12.  
  13.     static 
  14.     { 
  15.         bannedSites = new ArrayList<String>(); 
  16.         bannedSites.add("bilibili.com"); 
  17.         bannedSites.add("youtube.com"); 
  18.         bannedSites.add("weibo.com"); 
  19.         bannedSites.add("qq.com"); 
  20.     } 
  21.  
  22.     @Override 
  23.     public void connectTo(String serverhost) throws Exception { 
  24.         // 添加限制功能 
  25.         if(bannedSites.contains(serverhost.toLowerCase())) 
  26.         { 
  27.             throw new Exception("Access Denied:"+serverhost); 
  28.         } 
  29.         internet.connectTo(serverhost); 
  30.     } 

4、客戶端驗(yàn)證

  1. public class Client { 
  2.  
  3.     public static void main(String[] args) { 
  4.         Internet internet = new ProxyInternet(new RealInternet()); 
  5.         try { 
  6.             internet.connectTo("so.com"); 
  7.             internet.connectTo("qq.com"); 
  8.         } catch (Exception e) { 
  9.             System.out.println(e.getMessage()); 
  10.         } 
  11.     } 

5、輸出

  1. Connecting to so.com 
  2. Access Denied:qq.com 

不能訪問娛樂性網(wǎng)站,但是可以用 360 搜索,SO 靠譜,哈哈

靜態(tài)代理類優(yōu)缺點(diǎn)

優(yōu)點(diǎn):

在不修改目標(biāo)對象的前提下,可以通過代理對象對目標(biāo)對象功能擴(kuò)展

代理使客戶端不需要知道實(shí)現(xiàn)類是什么,怎么做的,而客戶端只需知道代理即可(解耦合),對于如上的客戶端代碼,RealInterner() 可以應(yīng)用工廠將它隱藏。

缺點(diǎn):

代理類和委托類實(shí)現(xiàn)了相同的接口,代理類通過委托類實(shí)現(xiàn)了相同的方法。這樣就出現(xiàn)了大量的代碼重復(fù)。如果接口增加一個方法,除了所有實(shí)現(xiàn)類需要實(shí)現(xiàn)這個方法外,所有代理類也需要實(shí)現(xiàn)此方法。增加了代碼維護(hù)的復(fù)雜度。

代理對象只服務(wù)于一種類型的對象,如果要服務(wù)多類型的對象。勢必要為每一種對象都進(jìn)行代理,靜態(tài)代理在程序規(guī)模稍大時就無法勝任了。

動態(tài)代理

靜態(tài)代理會產(chǎn)生很多靜態(tài)類,所以我們要想辦法可以通過一個代理類完成全部的代理功能,這就引出了動態(tài)代理。

JDK原生動態(tài)代理

  • 代理對象,不需要實(shí)現(xiàn)接口,但是目標(biāo)對象要實(shí)現(xiàn)接口,否則不能用動態(tài)代理
  • 代理對象的生成,是通過 JDK 的 API(反射機(jī)制),動態(tài)的在內(nèi)存中構(gòu)建代理對象

在 Java 中要想實(shí)現(xiàn)動態(tài)代理機(jī)制,需要 java.lang.reflect.InvocationHandler 接口和 java.lang.reflect.Proxy 類的支持

Coding

1、網(wǎng)絡(luò)接口不變

  1. public interface Internet { 
  2.     void connectTo(String serverHost) throws Exception; 

2、真正的網(wǎng)絡(luò)連接,也不會改變

  1. public class RealInternet implements Internet{ 
  2.  
  3.     @Override 
  4.     public void connectTo(String serverHost) throws Exception { 
  5.         System.out.println("Connecting to "+ serverHost); 
  6.     } 

3、動態(tài)代理,需要實(shí)現(xiàn) InvocationHandler,我們用 Lambda 表達(dá)式簡化下

  1. public class ProxyFactory { 
  2.  
  3.     /** 
  4.      * 維護(hù)一個目標(biāo)對象 
  5.      **/ 
  6.     private Object target; 
  7.  
  8.     /** 
  9.      * 構(gòu)造器,初始化目標(biāo)對象 
  10.      **/ 
  11.     public ProxyFactory(Object target) { 
  12.         this.target = target; 
  13.     } 
  14.  
  15.     public Object getProxyInstance() { 
  16.  
  17.         /** 
  18.          被代理對象target通過參數(shù)傳遞進(jìn)來, 
  19.          通過target.getClass().getClassLoader()獲取ClassLoader對象, 
  20.          然后通過target.getClass().getInterfaces()獲取它實(shí)現(xiàn)的所有接口, 
  21.          再將target包裝到實(shí)現(xiàn)了InvocationHandler接口的對象中。 
  22.          通過newProxyInstance函數(shù)我們就獲得了一個動態(tài)代理對象。 
  23.          */ 
  24.         return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { 
  25.             @Override 
  26.             public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
  27.                 if(bannedSites.contains(args[0].toString().toLowerCase())) 
  28.                 { 
  29.                     throw new Exception("Access Denied:"+args[0]); 
  30.                 } 
  31.                 //反射機(jī)制調(diào)用目標(biāo)對象的方法 
  32.                 Object obj = method.invoke(target, args); 
  33.                 return obj; 
  34.             } 
  35.         }); 
  36.     } 
  37.  
  38.     private static List<String> bannedSites; 
  39.  
  40.     static 
  41.     { 
  42.         bannedSites = new ArrayList<String>(); 
  43.         bannedSites.add("bilibili.com"); 
  44.         bannedSites.add("youtube.com"); 
  45.         bannedSites.add("weibo.com"); 
  46.         bannedSites.add("qq.com"); 
  47.     } 

4、客戶端

  1. public class Client { 
  2.  
  3.     public static void main(String[] args) { 
  4.         Internet internet = new ProxyInternet(new RealInternet()); 
  5.         try { 
  6.             internet.connectTo("#"); 
  7.             internet.connectTo("qq.com"); 
  8.         } catch (Exception e) { 
  9.             System.out.println(e.getMessage()); 
  10.         } 
  11.     } 

動態(tài)代理的方式中,所有的函數(shù)調(diào)用最終都會經(jīng)過 invoke 函數(shù)的轉(zhuǎn)發(fā),因此我們就可以在這里做一些自己想做的操作,比如日志系統(tǒng)、事務(wù)、攔截器、權(quán)限控制等。

cglib代理

靜態(tài)代理和 JDK 代理模式都要求目標(biāo)對象實(shí)現(xiàn)一個接口,但有時候目標(biāo)對象只是一個單獨(dú)的對象,并沒有實(shí)現(xiàn)任何接口,這個時候就可以使用目標(biāo)對象子類來實(shí)現(xiàn)代理,這就是 cglib 代理。

  • cglib(Code Generation Library)是一個基于ASM的字節(jié)碼生成庫,它允許我們在運(yùn)行時對字節(jié)碼進(jìn)行修改和動態(tài)生成。cglib 通過繼承方式實(shí)現(xiàn)代理。它廣泛的被許多AOP的框架使用,比如我們的 Spring AOP。
  • cglib 包的底層是通過使用字節(jié)碼處理框架 ASM 來轉(zhuǎn)換字節(jié)碼并生成新的類。
  • cglib 代理也被叫做子類代理,它是在內(nèi)存中構(gòu)建一個子類對象從而實(shí)現(xiàn)目標(biāo)對象功能擴(kuò)展。

Coding

添加 cglib 依賴

  1. <dependency> 
  2.     <groupId>cglib</groupId> 
  3.     <artifactId>cglib</artifactId> 
  4.     <version>3.3.0</version> 
  5. </dependency> 

1、不需要接口

  1. public class RealInternet{ 
  2.  
  3.     public void connectTo(String serverHost) { 
  4.         System.out.println("Connecting to "+ serverHost); 
  5.     } 

2、代理工廠類

  1. public class ProxyFactory implements MethodInterceptor { 
  2.  
  3.     private Object target; 
  4.  
  5.     public ProxyFactory(Object target){ 
  6.         this.target = target; 
  7.     } 
  8.  
  9.     @Override 
  10.     public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { 
  11.         System.out.println("cglib 代理開始,可以添加邏輯"); 
  12.         Object obj = method.invoke(target,objects); 
  13.         System.out.println("cglib 代理結(jié)束"); 
  14.         return obj; 
  15.     } 
  16.  
  17.  
  18.     public Object getProxyInstance(){ 
  19.         //工具類,類似于JDK動態(tài)代理的Proxy類 
  20.         Enhancer enhancer = new Enhancer(); 
  21.         //設(shè)置父類 
  22.         enhancer.setSuperclass(target.getClass()); 
  23.         //設(shè)置回調(diào)函數(shù) 
  24.         enhancer.setCallback(this); 
  25.         //創(chuàng)建子類對象,即代理對象 
  26.         return enhancer.create(); 
  27.     } 

3、客戶端

  1. public class Client { 
  2.  
  3.     public static void main(String[] args) { 
  4.  
  5.         //目標(biāo)對象 
  6.         RealInternet target = new RealInternet(); 
  7.         //獲取代理對象,并且將目標(biāo)對象傳遞給代理對象 
  8.         RealInternet internet = (RealInternet) new ProxyFactory(target).getProxyInstance(); 
  9.         internet.connectTo("so.cn"); 
  10.     } 

4、輸出

  1. cglib 代理開始,可以添加邏輯 
  2. Connecting to so.cn 
  3. cglib 代理結(jié)束 

代理模式適合應(yīng)用場景

使用代理模式的方式多種多樣, 我們來看看最常見的幾種。

  • 延遲初始化 (虛擬代理):如果你有一個偶爾使用的重量級服務(wù)對象, 一直保持該對象運(yùn)行會消耗系統(tǒng)資源時, 可使用代理模式。
  • 你無需在程序啟動時就創(chuàng)建該對象, 可將對象的初始化延遲到真正有需要的時候。
  • 訪問控制 (保護(hù)代理):如果你只希望特定客戶端使用服務(wù)對象, 這里的對象可以是操作系統(tǒng)中非常重要的部分, 而客戶端則是各種已啟動的程序 (包括惡意程序), 此時可使用代理模式。

代理可僅在客戶端憑據(jù)滿足要求時將請求傳遞給服務(wù)對象。

  • 本地執(zhí)行遠(yuǎn)程服務(wù) (遠(yuǎn)程代理):適用于服務(wù)對象位于遠(yuǎn)程服務(wù)器上的情形。
  • 在這種情形中, 代理通過網(wǎng)絡(luò)傳遞客戶端請求, 負(fù)責(zé)處理所有與網(wǎng)絡(luò)相關(guān)的復(fù)雜細(xì)節(jié)。
  • 記錄日志請求 (日志記錄代理):適用于當(dāng)你需要保存對于服務(wù)對象的請求歷史記錄時。代理可以在向服務(wù)傳遞請求前進(jìn)行記錄。
  • 緩存請求結(jié)果 (緩存代理):適用于需要緩存客戶請求結(jié)果并對緩存生命周期進(jìn)行管理時, 特別是當(dāng)返回結(jié)果的體積非常大時。
  • 代理可對重復(fù)請求所需的相同結(jié)果進(jìn)行緩存, 還可使用請求參數(shù)作為索引緩存的鍵值。比如請求圖片、文件等資源時,先到代理緩存取,如果沒有就去公網(wǎng)取并緩存到代理服務(wù)器
  • 智能引用:可在沒有客戶端使用某個重量級對象時立即銷毀該對象。

代理會將所有獲取了指向服務(wù)對象或其結(jié)果的客戶端記錄在案。代理會時不時地遍歷各個客戶端, 檢查它們是否仍在運(yùn)行。如果相應(yīng)的客戶端列表為空, 代理就會銷毀該服務(wù)對象, 釋放底層系統(tǒng)資源。

代理還可以記錄客戶端是否修改了服務(wù)對象。其他客戶端還可以復(fù)用未修改的對象。

AOP 中的代理模式

AOP(面向切面編程)主要的的實(shí)現(xiàn)技術(shù)主要有 Spring AOP 和 AspectJ

AspectJ 的底層技術(shù)就是靜態(tài)代理,用一種 AspectJ 支持的特定語言編寫切面,通過一個命令來編譯,生成一個新的代理類,該代理類增強(qiáng)了業(yè)務(wù)類,這是在編譯時增強(qiáng),相對于下面說的運(yùn)行時增強(qiáng),編譯時增強(qiáng)的性能更好。(AspectJ 的靜態(tài)代理,不像我們前邊介紹的需要為每一個目標(biāo)類手動編寫一個代理類,AspectJ 框架可以在編譯時就生成目標(biāo)類的“代理類”,在這里加了個冒號,是因?yàn)閷?shí)際上它并沒有生成一個新的類,而是把代理邏輯直接編譯到目標(biāo)類里面了)

Spring AOP 采用的是動態(tài)代理,在運(yùn)行期間對業(yè)務(wù)方法進(jìn)行增強(qiáng),所以不會生成新類,對于動態(tài)代理技術(shù),Spring AOP 提供了對 JDK 動態(tài)代理的支持以及 CGLib 的支持。

默認(rèn)情況下,Spring對實(shí)現(xiàn)了接口的類使用 JDK Proxy方式,否則的話使用CGLib。不過可以通過配置指定 Spring AOP 都通過 CGLib 來生成代理類。

具體邏輯在 org.springframework.aop.framework.DefaultAopProxyFactory類中,使用哪種方式生成由AopProxy 根據(jù) AdvisedSupport 對象的配置來決定源碼如下:

  1. public class DefaultAopProxyFactory implements AopProxyFactory, Serializable { 
  2.     public DefaultAopProxyFactory() { 
  3.     } 
  4.  
  5.     public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { 
  6.         if (!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) { 
  7.             return new JdkDynamicAopProxy(config); 
  8.         } else { 
  9.             Class<?> targetClass = config.getTargetClass(); 
  10.             if (targetClass == null) { 
  11.                 throw new AopConfigException("TargetSource cannot determine target class: Either an interface or a target is required for proxy creation."); 
  12.             } else { 
  13.                 //如果目標(biāo)類是接口且是代理類, 使用JDK動態(tài)代理類,否則使用Cglib生成代理類 
  14.                 return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass) ? new ObjenesisCglibAopProxy(config) : new JdkDynamicAopProxy(config)); 
  15.             } 
  16.         } 
  17.     } 
  18.  
  19.     private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) { 
  20.     } 

具體內(nèi)容就不展開了,后邊整理 SpringAOP 的時候再深入。

參考與感謝https://refactoringguru.cn/design-patterns/proxy https://www.geeksforgeeks.org/proxy-design-pattern/

 

責(zé)任編輯:武曉燕 來源: JavaKeeper
相關(guān)推薦

2022-04-10 18:10:24

CURD鏈表

2022-10-12 14:39:27

Streammappeek

2021-11-05 06:57:50

HTTPHTTPS端口

2025-03-07 02:00:00

webpackPlugin開發(fā)

2022-04-29 08:17:38

RPC遠(yuǎn)程代理代理模式

2020-09-09 14:49:19

面試官數(shù)據(jù)結(jié)構(gòu)

2023-11-27 08:17:05

SpringJava

2020-12-01 11:50:49

數(shù)據(jù)庫Redis面試

2019-08-23 09:20:35

Spring 5編程Java

2021-03-24 10:25:24

優(yōu)化VUE性能

2021-11-08 09:18:01

CAS面試場景

2022-11-04 08:47:52

底層算法數(shù)據(jù)

2023-01-26 02:16:17

2021-12-25 22:31:10

MarkWord面試synchronize

2015-05-06 10:05:22

javajava框架spring aop

2019-05-10 10:50:04

Spring AOPJDK動態(tài)代理CGLIB動態(tài)代理

2022-01-05 08:56:20

Vue修飾符面試

2021-05-28 07:12:58

Mybatis面試官Java

2009-06-29 18:11:40

JSP設(shè)計模式

2023-11-28 17:49:51

watch?computed?性能
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號