<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          快速理解 Java 靜態(tài)代理 / 動態(tài)代理

          共 6582字,需瀏覽 14分鐘

           ·

          2021-05-09 14:54

          點擊下方“IT牧場”,選擇“設(shè)為星標(biāo)”


          理解Java動態(tài)代理需要對Java的反射機(jī)制有一定了解

          什么是代理模式

          在有些情況下,一個客戶不能或者不想直接訪問另一個對象,這時需要找一個中介幫忙完成某項任務(wù),這個中介就是代理對象。

          例如,購買火車票不一定要去火車站買,可以通過 12306 網(wǎng)站或者去火車票代售點買。又如找女朋友、找保姆、找工作等都可以通過找中介完成。

          定義

          由于某些原因需要給某對象提供一個代理以控制對該對象的訪問。

          訪問對象不適合或者不能直接引用目標(biāo)對象,代理對象作為訪問對象目標(biāo)對象之間的中介

          代理模式的主要角色

          • 抽象角色(Subject):通過接口或抽象類聲明真實主題和代理對象實現(xiàn)的業(yè)務(wù)方法。

          • 真實角色(Real Subject):實現(xiàn)了抽象主題中的具體業(yè)務(wù),是代理對象所代表的真實對象,是最終要引用的對象。

          • 代理(Proxy):提供了與真實主題相同的接口,其內(nèi)部含有對真實主題的引用,它可以訪問、控制或擴(kuò)展真實主題的功能。

          • 客戶 : 使用代理角色來進(jìn)行一些操作 .

          優(yōu)點

          • 代理模式在客戶端與目標(biāo)對象之間起到一個中介作用和保護(hù)目標(biāo)對象的作用

          • 代理對象可以擴(kuò)展目標(biāo)對象的功能

          • 代理模式能將客戶端與目標(biāo)對象分離,在一定程度上降低了系統(tǒng)的耦合度,增加了程序的可擴(kuò)展性

          缺點

          • 冗余,由于代理對象要實現(xiàn)與目標(biāo)對象一致的接口,會產(chǎn)生過多的代理類。

          • 系統(tǒng)設(shè)計中類的數(shù)量增加,變得難以維護(hù)。

          使用動態(tài)代理方式,可以有效避免以上的缺點

          靜態(tài)代理

          靜態(tài)代理其實就是最基礎(chǔ)、最標(biāo)準(zhǔn)的代理模式實現(xiàn)方案。

          舉例:

          Rent . java 即抽象角色

          //抽象角色:租房
          public interface Rent {
          public void rent();
          }

          Landlord . java 即真實角色

          //真實角色: 房東,房東要出租房子
          public class Landlord implements Rent{
          public void rent() {
          System.out.println("房屋出租");
          }
          }

          Proxy . java 即代理

          //代理角色:中介
          public class Proxy implements Rent {

          private Landlord landlord;
          public Proxy() { }
          public Proxy(Landlord landlord) {
          this.landlord = landlord;
          }
          //租房
          public void rent(){
          seeHouse();
          landlord.rent();
          fare();
          }
          //看房
          public void seeHouse(){
          System.out.println("帶房客看房");
          }
          //收中介費
          public void fare(){
          System.out.println("收中介費");
          }
          }

          Client . java 即客戶

          //客戶類,一般客戶都會去找代理!
          public class Client {
          public static void main(String[] args) {
          //房東要租房
          Landlord landlord = new Landlord();
          //中介幫助房東
          Proxy proxy = new Proxy(landlord);
          //客戶找中介
          proxy.rent();
          }
          }

          結(jié)果:

          帶房客看房
          房屋出租
          收中介費

          Process finished with exit code 0

          在這個過程中,客戶接觸的是中介,看不到房東,但是依舊租到了房東的房子。同時房東省了心,客戶省了事。

          靜態(tài)代理享受代理模式的優(yōu)點,同時也具有代理模式的缺點,那就是一旦實現(xiàn)的功能增加,將會變得異常冗余和復(fù)雜,秒變光頭。

          為了保護(hù)頭發(fā),就出現(xiàn)了動態(tài)代理模式!

          動態(tài)代理

          動態(tài)代理的出現(xiàn)就是為了解決傳統(tǒng)靜態(tài)代理模式的中的缺點。

          具備代理模式的優(yōu)點的同時,巧妙的解決了靜態(tài)代理代碼冗余,難以維護(hù)的缺點。

          在Java中常用的有如下幾種方式:

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

          • cglib 動態(tài)代理

          • javasist 動態(tài)代理

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

          上例中靜態(tài)代理類中,中介作為房東的代理,實現(xiàn)了相同的租房接口。

          例子

          1. 首先實現(xiàn)一個InvocationHandler,方法調(diào)用會被轉(zhuǎn)發(fā)到該類的invoke()方法。

          2. 然后在需要使用Rent的時候,通過JDK動態(tài)代理獲取Rent的代理對象。

          class RentInvocationHandler implements InvocationHandler {

          private Rent rent;

          public RentInvocationHandler(Rent rent) {
          this.rent = rent;
          }

          @Override
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          seeHouse();
          Object result = method.invoke(rent, args);
          fare();
          return result;
          }

          //看房
          public void seeHouse(){
          System.out.println("帶房客看房");
          }
          //收中介費
          public void fare(){
          System.out.println("收中介費");
          }
          //動態(tài)獲取代理
          public Object getProxy() {
          return Proxy.newProxyInstance(this.getClass().getClassLoader(),
          rent.getClass().getInterfaces(),this); //核心關(guān)鍵
          }
          }

          客戶使用動態(tài)代理調(diào)用

          public class Client {
          public static void main(String[] args) {
          Landlord landlord = new Landlord();
          //代理實例的調(diào)用處理程序
          RentInvocationHandler pih = new RentInvocationHandler(landlord);
          Rent proxy = (Rent)pih.getProxy(); //動態(tài)生成對應(yīng)的代理類!
          proxy.rent();
          }
          }

          運(yùn)行結(jié)果和前例相同

          分析

          上述代碼的核心關(guān)鍵是Proxy.newProxyInstance方法,該方法會根據(jù)指定的參數(shù)動態(tài)創(chuàng)建代理對象。

          它三個參數(shù)的意義如下:

          1. loader,指定代理對象的類加載器

          2. interfaces,代理對象需要實現(xiàn)的接口,可以同時指定多個接口

          3. handler,方法調(diào)用的實際處理者,代理對象的方法調(diào)用都會轉(zhuǎn)發(fā)到這里

          Proxy.newProxyInstance會返回一個實現(xiàn)了指定接口的代理對象,對該對象的所有方法調(diào)用都會轉(zhuǎn)發(fā)給InvocationHandler.invoke()方法。

          因此,在invoke()方法里我們可以加入任何邏輯,比如修改方法參數(shù),加入日志功能、安全檢查功能等等等等……

          小結(jié)

          顯而易見,對于靜態(tài)代理而言,我們需要手動編寫代碼代理實現(xiàn)抽象角色的接口。

          而在動態(tài)代理中,我們可以讓程序在運(yùn)行的時候自動在內(nèi)存中創(chuàng)建一個實現(xiàn)抽象角色接口的代理,而不需要去單獨定義這個類,代理對象是在程序運(yùn)行時產(chǎn)生的,而不是編譯期。

          對于從Object中繼承的方法,JDK Proxy會把hashCode()equals()toString()這三個非接口方法轉(zhuǎn)發(fā)給InvocationHandler,其余的Object方法則不會轉(zhuǎn)發(fā)。

          CGLIB動態(tài)代理

          JDK動態(tài)代理是基于接口的,如果對象沒有實現(xiàn)接口該如何代理呢?CGLIB代理登場

          CGLIB(Code Generation Library)是一個基于ASM的字節(jié)碼生成庫,它允許我們在運(yùn)行時對字節(jié)碼進(jìn)行修改和動態(tài)生成。CGLIB通過繼承方式實現(xiàn)代理。

          使用cglib需要引入cglib的jar包,如果你已經(jīng)有spring-core的jar包,則無需引入,因為spring中包含了cglib。

          <dependency>
          <groupId>cglib</groupId>
          <artifactId>cglib</artifactId>
          <version>3.3.0</version>
          </dependency>

          例子

          來看示例,假設(shè)我們有一個沒有實現(xiàn)任何接口的類Landlord

          public class Landlord{
          public void rent() {
          System.out.println("房屋出租");
          }
          }

          因為沒有實現(xiàn)接口,所以使用通過CGLIB代理實現(xiàn)如下:

          首先實現(xiàn)一個MethodInterceptor,方法調(diào)用會被轉(zhuǎn)發(fā)到該類的intercept()方法

          public class RentMethodInterceptor implements MethodInterceptor {
          private Object target;//維護(hù)一個目標(biāo)對象
          public RentMethodInterceptor(Object target) {
          this.target = target;
          }
          //為目標(biāo)對象生成代理對象
          public Object getProxyInstance() {
          //工具類
          Enhancer en = new Enhancer();
          //設(shè)置父類
          en.setSuperclass(target.getClass());
          //設(shè)置回調(diào)函數(shù)
          en.setCallback(this);
          //創(chuàng)建子類對象代理
          return en.create();
          }
          @Override
          public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
          System.out.println("看房");
          // 執(zhí)行目標(biāo)對象的方法
          Object returnValue = method.invoke(target, objects);
          System.out.println("中介費");
          return null;
          }
          }

          客戶通過CGLIB動態(tài)代理獲取代理對象

          public class Client {
          public static void main(String[] args) {
          Landlord target = new Landlord();
          System.out.println(target.getClass());
          //代理對象
          Landlord proxy = (Landlord) new RentMethodInterceptor(target).getProxyInstance();
          System.out.println(proxy.getClass());
          //執(zhí)行代理對象方法
          proxy.rent();
          }
          }

          運(yùn)行輸出結(jié)果和前例相同

          分析

          對于從Object中繼承的方法,CGLIB代理也會進(jìn)行代理,如hashCode()equals()toString()等,但是getClass()wait()等方法不會,因為它是final方法,CGLIB無法代理。

          其實CGLIB和JDK代理的思路大致相同

          上述代碼中,通過CGLIB的Enhancer來指定要代理的目標(biāo)對象、實際處理代理邏輯的對象。

          最終通過調(diào)用create()方法得到代理對象,對這個對象所有非final方法的調(diào)用都會轉(zhuǎn)發(fā)給MethodInterceptor.intercept()方法

          intercept()方法里我們可以加入任何邏輯,同JDK代理中的invoke()方法

          通過調(diào)用MethodProxy.invokeSuper()方法,我們將調(diào)用轉(zhuǎn)發(fā)給原始對象,具體到本例,就是Landlord的具體方法。CGLIG中MethodInterceptor的作用跟JDK代理中的InvocationHandler很類似,都是方法調(diào)用的中轉(zhuǎn)站。

          final類型

          CGLIB是通過繼承的方式來實現(xiàn)動態(tài)代理的,有繼承就不得不考慮final的問題。我們知道final類型不能有子類,所以CGLIB不能代理final類型,遇到這種情況會拋出類似如下異常:

          java.lang.IllegalArgumentException: Cannot subclass final class cglib.HelloConcrete

          同樣的,final方法是不能重載的,所以也不能通過CGLIB代理,遇到這種情況不會拋異常,而是會跳過final方法只代理其他方法。

          其他方案

          • 使用ASM在被代理類基礎(chǔ)上生成新的字節(jié)碼形成代理類

          • 使用javassist在被代理類基礎(chǔ)上生成新的字節(jié)碼形成代理類

          javassist也是常用的一種動態(tài)代理方案,ASM速度非常快,這里不在進(jìn)行展開。

          尾聲

          動態(tài)代理是Spring AOP(Aspect Orient Programming, 面向切面編程)的實現(xiàn)方式,了解動態(tài)代理原理,對理解Spring AOP大有幫助。

          • 如spring等這樣的框架,要增強(qiáng)具體業(yè)務(wù)的邏輯方法,不可能在框架里面去寫一個靜態(tài)代理類,太蠢了,只能按照用戶的注解或者xml配置來動態(tài)生成代理類。

          • 業(yè)務(wù)代碼內(nèi),當(dāng)需要增強(qiáng)的業(yè)務(wù)邏輯非常通用(如:添加log,重試,統(tǒng)一權(quán)限判斷等)時,使用動態(tài)代理將會非常簡單,如果每個方法增強(qiáng)邏輯不同,那么靜態(tài)代理更加適合。

          • 使用靜態(tài)代理時,如果代理類和被代理類同時實現(xiàn)了一個接口,當(dāng)接口方法有變動時,代理類也必須同時修改,代碼將變得臃腫且難以維護(hù)。



          source: https://www.cnblogs.com/aduner/p/14646877.html

          干貨分享

          最近將個人學(xué)習(xí)筆記整理成冊,使用PDF分享。關(guān)注我,回復(fù)如下代碼,即可獲得百度盤地址,無套路領(lǐng)取!

          ?001:《Java并發(fā)與高并發(fā)解決方案》學(xué)習(xí)筆記;?002:《深入JVM內(nèi)核——原理、診斷與優(yōu)化》學(xué)習(xí)筆記;?003:《Java面試寶典》?004:《Docker開源書》?005:《Kubernetes開源書》?006:《DDD速成(領(lǐng)域驅(qū)動設(shè)計速成)》?007:全部?008:加技術(shù)群討論

          關(guān)注我

          喜歡就點個"在看"唄^_^

          瀏覽 47
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩中文字幕在线观看视频 | 精品视频一区二区三区四区 | 天天射天天爽 | 婷婷色五月在线观看视频 | 內射网站|