亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

淺談Java動態(tài)代理的實現(xiàn)

 更新時間:2021年05月25日 10:04:34   作者:曉之木初  
最近,小組同事做代碼改造時,使用到了動態(tài)代理,自己閱讀時,發(fā)現(xiàn)對代理這種設(shè)計模式都不怎么清楚,導(dǎo)致理解代碼也很困難 自己唯一能看懂的,大概就是handler中的invoke方法 ,文中作出了非常詳細的介紹,需要的朋友可以參考下

一、代理設(shè)計模式

1.1 什么是代理

  • 考慮真實的編程場景,項目中存在一個訪問其他數(shù)據(jù)源的接口,包含一個query()方法
  • 我們已經(jīng)針對這個接口,實現(xiàn)了MySQL、Hive、HBase、MongoDB等作為數(shù)據(jù)源的實現(xiàn)類
  • 但是,在測試過程中,我們發(fā)現(xiàn)這些數(shù)據(jù)源的查詢并不是很穩(wěn)定
  • 最原始的想法: 在所有實現(xiàn)類query()方法中,代碼首部獲取startTime,代碼尾部獲取endTime,通過打印日志的方式,知道每次查詢的耗時
long startTime = System.currentTimeMillis();
logger.info("query mysql start:" + new Date(startTime).toLocaleString());
// 具體的query代碼
...

long endTime = System.currentTimeMillis();
logger.info(String.format("query mysql end: %s, consumed time: %dms", new Date(endTime).toLocaleString(), (endTime-startTime)));
  • 直接修改已經(jīng)實現(xiàn)的方法,存在很多缺點:

(1)現(xiàn)在是打印日志,代碼非常簡單,就算query()方法不是你實現(xiàn)的,你也能很好的完成。
(2)但是如果是其他功能呢?比如,如果查詢失敗,要求你查詢重試

  • 因此,在不改變已經(jīng)實現(xiàn)好的query()方法前提下,去實現(xiàn)日志打印的功能是最好的方法。
  • 進階想法: 我為每個實現(xiàn)類創(chuàng)建一個包裝類。

(1)這個包裝類與實現(xiàn)類一樣,實現(xiàn)了相同的接口。
(2)在query()方法中,直接調(diào)用實現(xiàn)類的query()方法,并在調(diào)用前后進行日志打印
(3)對實現(xiàn)類方法的調(diào)用,都改成對包裝類方法的調(diào)用

long startTime = System.currentTimeMillis();
logger.info("query mysql start:" + new Date(startTime).toLocaleString());
// 使用try-finally
JSONObject[] data = null;
try {
    data = mysql.query();
    return data;
} catch (Exception exception) {
    throw exception;
} finally {
    long endTime = System.currentTimeMillis();
    logger.info(String.format("query mysql end: %s, consumed time: %dms", new Date(endTime).toLocaleString(), (endTime - startTime)));
}
  • 這時,代理模式的概念就變得非常清晰了:不直接調(diào)用實現(xiàn)類的某個方法,而是通過實現(xiàn)類的代理去調(diào)用。
  • 這樣不僅可以實現(xiàn)調(diào)用者與被調(diào)用者之間的解耦合,還可以在不修改調(diào)用者的情況下,豐富功能邏輯。

1.2 代理模式入門

代理模式的UML圖如下

1.subject: 抽象主題角色,是一個接口,定義了一系列的公共對外方法

2.real subject: 真實主題角色,也就是我剛剛提到的實現(xiàn)類,又稱委托類。
委托類實現(xiàn)抽象主題,負責實現(xiàn)具體的業(yè)務(wù)邏輯

3.proxy: 代理主題角色,簡稱代理類。它也實現(xiàn)了抽象主題,用于代理、封裝,甚至增強委托類。
一般通過內(nèi)含委托類,實現(xiàn)對委托類的封裝

4.client: 當訪問具體的業(yè)務(wù)邏輯時,clinet表面是訪問代理類,而實際是訪問被代理類封裝的委托類

在這里插入圖片描述 

代理模式的應(yīng)用場景:目前,就我本人所接觸的使用場景,就是通過代理去打印日志、增強業(yè)務(wù)邏輯 😂

二、Java代理的三種實現(xiàn)

2.1 靜態(tài)代理

  • 所謂的靜態(tài)動態(tài),是相對于字節(jié)碼的生成時機來說的:

(1)靜態(tài)是指字節(jié)碼,即class文件,在編譯時就已經(jīng)生成。
(2)動態(tài)是指字節(jié)碼在運行時動態(tài)生成,而不是編譯時提前生成

  • 剛剛,我們針對數(shù)據(jù)查詢的進階方法,實際就是靜態(tài)代理
  • 通過為每個委托類創(chuàng)建對應(yīng)的代理類,然后編譯時就可以得到代理類的字節(jié)碼

下面是一個具體的靜態(tài)代理實例:

抽象主題:

public interface Animal {
   void eat();
}

委托類:Dog和Cat,實現(xiàn)了抽象接口

public class Dog implements Animal {
    @Override
    public void eat() {
        System.out.println("I like eating bone");
    }
}

public class Cat implements Animal {
    @Override
    public void eat() {
        System.out.println("I like eating fish");
    }
}

代理類:代理類中含有對應(yīng)的委托類,通過調(diào)用委托類的具體實現(xiàn),來封裝委托類

public class DogProxy implements Animal {
    private Dog dog;

    public DogProxy(Dog dog) {
        this.dog = dog;
    }

    @Override
    public void eat() {
        System.out.print("I'm a "+dog.getClass().getSimpleName() +". ");
        dog.eat();
    }
}

public class CatProxy implements Animal {
    private Cat cat;

    public CatProxy(Cat cat) {
        this.cat = cat;
    }

    @Override
    public void eat() {
        System.out.print("I'm a " + cat.getClass().getSimpleName()+". ");
        cat.eat();
    }
}

靜態(tài)代理雖然實現(xiàn)簡單、不更改原始的業(yè)務(wù)邏輯,但是仍然存在以下缺點:

1.如果存在多個委托類,則需要創(chuàng)建多個代理類,這樣則會產(chǎn)生過多的代理類。

2.如果抽象主題增加、刪除、修改方法時,委托類和代理類都需要同時修改,不易維護。

2.2 Java自帶的動態(tài)代理

  • 靜態(tài)代理存在以上缺點,如果我們能在程序運行時,動態(tài)生成對應(yīng)的代理類,則無需創(chuàng)建并維護過多的代理類
  • 使用Java自帶的java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler類,可以實現(xiàn)動態(tài)代理
  • 從類的全路徑可以看出,Java的動態(tài)代理實際利用的是反射機制實現(xiàn)的
  • 其中,Proxy用于創(chuàng)建對應(yīng)接口的代理類。具體代理的是哪個委托類,是由實現(xiàn)InvocationHandler接口的中介類決定的
  • 代理類、委托類、中介類之間的關(guān)系如下

在這里插入圖片描述

Java自帶的動態(tài)代理具體實現(xiàn):

1.創(chuàng)建抽象主題 —— 與靜態(tài)代理類一致,不再展示

2.創(chuàng)建實現(xiàn)類 —— 與靜態(tài)代理類一致,不再展示

3.實現(xiàn)InvocationHandler接口,創(chuàng)建中介類

public class AnimalInvokeHandler implements InvocationHandler {
    private Animal animal;

    public AnimalInvokeHandler(Animal animal) {
        this.animal = animal;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("proxy class: " + proxy.getClass().getName());
        System.out.printf("proxy instanceof  Animal: %b \n", proxy instanceof Animal);
        System.out.printf("---- Call method: %s, class: %s ----\n", method.getName(), animal.getClass().getSimpleName());
        // 通過反射,調(diào)用委托類的方法
        Object result = method.invoke(animal, args);
        return result;
    }
}

4.通過Proxy創(chuàng)建動態(tài)代理類,實現(xiàn)對抽象主題的代理

public static void main(String[] args) {
    // 通過Proxy.newProxyInstance創(chuàng)建代理類
    // 將代理類轉(zhuǎn)為抽象主題,可以動態(tài)的創(chuàng)建實現(xiàn)了該主題的代理類
    // 必須從實現(xiàn)類去獲取需要代理的接口
    // 指定中介類,通過中介類實現(xiàn)代理
    Animal proxy = (Animal) Proxy.newProxyInstance(Main.class.getClassLoader(), Dog.class.getInterfaces(), new AnimalInvokeHandler(new Dog()));
    proxy.eat();
}

執(zhí)行結(jié)果:

在這里插入圖片描述

Java原生的動態(tài)代理,利用反射動態(tài)生成代理類字節(jié)碼ProxyX.class,然后將其強制轉(zhuǎn)化為抽象主題類型,就能實現(xiàn)對該接口的代理

jdk動態(tài)代理之所以只能代理接口是因為代理類本身已經(jīng)extends了Proxy,而java是不允許多重繼承的,但是允許實現(xiàn)多個接口

Java原生動態(tài)代理,又叫jdk動態(tài)代理,具有以下優(yōu)缺點

1.優(yōu)點: jdk動態(tài)代理,避免了靜態(tài)代理需要創(chuàng)建并維護過多的代理類的

2.缺點: jdk動態(tài)代理只能代理接口,因為Java的單繼承原則:代理類本身已經(jīng)繼承了Proxy類,就不能再繼承其他類,只能實現(xiàn)委托類的抽象主題接口。

2.3 cglib實現(xiàn)動態(tài)代理

  • jdk動態(tài)代理存在只能代理接口的問題,是十分不方便的。
  • 考慮以下場景:

一個類中有兩個方法methodA:轉(zhuǎn)賬到其他賬戶,methodB:查詢賬戶余額。
如果用戶訪問methodA,希望先提示用戶檢查賬戶信息是否正確;
如果用戶訪問methodB,希望在用戶查詢完余額后,提示用戶關(guān)注銀行的微信公眾號。

  • 上述類已經(jīng)成功用于業(yè)務(wù)場景了,我們想要實現(xiàn)這些增強功能,最好不要更改其原始代碼,而是通過代理實現(xiàn)功能增強
  • 這時,便可以使用cglib實現(xiàn)對類的動態(tài)代理 —— 小白看了實現(xiàn)后,可能會傾向于說這就是類似Java web的攔截器 😂

三、cglib動態(tài)代理的實現(xiàn)

添加maven依賴

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

簡化版的銀行系統(tǒng)類

public class BankSystem {
    // 轉(zhuǎn)賬
    public boolean transferAccount(double amount, String address) {
        System.out.printf("Send %f dollars to account %s", amount, address);
        // 轉(zhuǎn)賬成功
        return true;
    }

    // 查詢賬戶余額
    public String queryBalance(){
        System.out.printf("Query account balance success");
        return "Account balance: 2400 dollars";
    }
}

為轉(zhuǎn)賬方法創(chuàng)建攔截器

public class TransferInterceptor implements MethodInterceptor {
    /**
    *
    * @param o,表示要增強的對象,其實就是代理中的委托類
    * @param method,被攔截的方法,其實就是委托類中被代理的方法
    * @param objects,被攔截方法的入?yún)?,如果是基本?shù)據(jù)類型需要傳入包裝類型
    * @param methodProxy,對method的代理,通過invokeSuper(),實現(xiàn)對method的調(diào)用
    * @return java.lang.Object
    * @author sunrise
    * @date 2021/5/23 10:43 上午
    **/
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before(objects);
        Object result = methodProxy.invokeSuper(o, objects);
        return result;
    }

    private void before(Object[] args){
        System.out.printf("Please check: you will send %.2f dollar to account %s.\n", args[0], args[1]);
    }
}

為余額查詢方法創(chuàng)建攔截器

public class QueryBalanceInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // 調(diào)用預(yù)發(fā)插敘方法
        Object result = proxy.invokeSuper(obj, args);
        after();
        return result;
    }

    private void after() {
        System.out.printf("Please pay attention to WeChat official account.\n");
    }
}

創(chuàng)建filter,實現(xiàn)方法與攔截器的映射

public class BankFilter implements CallbackFilter {
    @Override
    public int accept(Method method) {
        if ("transferAccount".equals(method.getName())) {
            // 使用攔截器列表中的第1個攔截器
            return 0;
        }
        // 使用攔截器列表中的第2個攔截器
        return 1;
    }
}

使用cglib實現(xiàn)動態(tài)代理 —— 我認為就是方法攔截 😂

public static void main(String[] args) {
    // 新建攔截器
    TransferInterceptor transferInterceptor = new TransferInterceptor();
    QueryBalanceInterceptor queryBalanceInterceptor = new QueryBalanceInterceptor();

    // 創(chuàng)建工具類
    Enhancer enhancer = new Enhancer();
    // 設(shè)置委托類,在cglib中,這是cglib需要繼承的超類
    enhancer.setSuperclass(BankSystem.class);
    // 設(shè)置多個攔截器
    enhancer.setCallbacks(new Callback[]{transferInterceptor, queryBalanceInterceptor});
    // 實現(xiàn)攔截器和方法的映射,即為不同的方法配置不同的攔截器
    enhancer.setCallbackFilter(new BankFilter());

    // 創(chuàng)建代理類
    BankSystem proxy = (BankSystem) enhancer.create();

    // 執(zhí)行轉(zhuǎn)賬,調(diào)用TransferInterceptor
    boolean ok = proxy.transferAccount(1024.28, "lucy");
    System.out.println("transfer money success: " + ok);
    // 查詢余額,調(diào)用QueryBalanceInterceptor
    proxy.queryBalance();
}

cglib總結(jié):

1.優(yōu)點1: cglib基于實現(xiàn)動態(tài)代理,通過ASM字節(jié)碼框架動態(tài)生成委托類的子類,并使用方法攔截器實現(xiàn)對委托類方法的攔截。

2.優(yōu)點2: 基于ASM字節(jié)碼框架動態(tài)生成代理類,比jdk動態(tài)生成代理類更加高效

3.缺點: 通過繼承委托類創(chuàng)建動態(tài)代理類,因此不能代理final委托類或委托類中的final方法。

四、面試常見問題

java中代理的實現(xiàn)

共有三種方法:靜態(tài)代理、JDK動態(tài)代理、cglib動態(tài)代理

  • 靜態(tài)代理: 為每個實現(xiàn)類都創(chuàng)建一個對應(yīng)的代理類,需要創(chuàng)建并維護大量的代理類
  • jdk動態(tài)代理: 通過Proxy.newProxyInstance()為抽象主題創(chuàng)建代理類,被代理的委托類包含在InvocationHandler類中,由InvocationHandler類的invoke方法通過反射實現(xiàn)對委托類方法的調(diào)用
  • cglib動態(tài)代理:通過ASM字節(jié)碼框架,繼承委托類以創(chuàng)建代理類。代理類通過方法攔截器,實現(xiàn)對委托類方法的攔截

三種代理方式的比較

1.靜態(tài)代理,需要創(chuàng)建并維護大量的委托類

2.jdk動態(tài)代理,避免了靜態(tài)類的上述缺點,但只能代理接口(Java單繼承原則,代理類已經(jīng)繼承了Proxy類)

3.cglib動態(tài)代理,可以實現(xiàn)對類的代理,并通過方法攔截器實現(xiàn)對委托類(父類)方法的攔截;使用強大的ASM字節(jié)碼框架,更加高效;通過繼承實現(xiàn)對類的代理,使其無法代理final類或類中的final方法

為何調(diào)用代理類的方法,會自動進入InvocationHandlerinvoke()方法?

  • 通過newProxyInstance()創(chuàng)建代理類時,會為代理類設(shè)置InvocationHandlerh
  • 動態(tài)生成的代理類字節(jié)碼,通過反編譯可以發(fā)現(xiàn),它實現(xiàn)了抽象主題中的每個方法
  • 方法的實現(xiàn),是調(diào)用內(nèi)部成員h.invoke()方法
this.h.invoke(this, method, args);

因此,調(diào)用代理類的方法時,實際上會調(diào)用InvocationHandlerinvoke()方法

到此這篇關(guān)于淺談Java動態(tài)代理的實現(xiàn)的文章就介紹到這了,更多相關(guān)Java動態(tài)代理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論