欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

淺談Java動(dòng)態(tài)代理的實(shí)現(xiàn)

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

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

1.1 什么是代理

  • 考慮真實(shí)的編程場(chǎng)景,項(xiàng)目中存在一個(gè)訪問(wèn)其他數(shù)據(jù)源的接口,包含一個(gè)query()方法
  • 我們已經(jīng)針對(duì)這個(gè)接口,實(shí)現(xiàn)了MySQL、Hive、HBase、MongoDB等作為數(shù)據(jù)源的實(shí)現(xiàn)類
  • 但是,在測(cè)試過(guò)程中,我們發(fā)現(xiàn)這些數(shù)據(jù)源的查詢并不是很穩(wěn)定
  • 最原始的想法: 在所有實(shí)現(xiàn)類query()方法中,代碼首部獲取startTime,代碼尾部獲取endTime,通過(guò)打印日志的方式,知道每次查詢的耗時(shí)
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)實(shí)現(xiàn)的方法,存在很多缺點(diǎn):

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

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

(1)這個(gè)包裝類與實(shí)現(xiàn)類一樣,實(shí)現(xiàn)了相同的接口。
(2)在query()方法中,直接調(diào)用實(shí)現(xiàn)類的query()方法,并在調(diào)用前后進(jìn)行日志打印
(3)對(duì)實(shí)現(xiàn)類方法的調(diào)用,都改成對(duì)包裝類方法的調(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)));
}
  • 這時(shí),代理模式的概念就變得非常清晰了:不直接調(diào)用實(shí)現(xiàn)類的某個(gè)方法,而是通過(guò)實(shí)現(xiàn)類的代理去調(diào)用。
  • 這樣不僅可以實(shí)現(xiàn)調(diào)用者與被調(diào)用者之間的解耦合,還可以在不修改調(diào)用者的情況下,豐富功能邏輯。

1.2 代理模式入門

代理模式的UML圖如下

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

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

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

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

在這里插入圖片描述 

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

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

2.1 靜態(tài)代理

  • 所謂的靜態(tài)動(dòng)態(tài),是相對(duì)于字節(jié)碼的生成時(shí)機(jī)來(lái)說(shuō)的:

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

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

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

抽象主題:

public interface Animal {
   void eat();
}

委托類:Dog和Cat,實(shí)現(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");
    }
}

代理類:代理類中含有對(duì)應(yīng)的委托類,通過(guò)調(diào)用委托類的具體實(shí)現(xiàn),來(lái)封裝委托類

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)代理雖然實(shí)現(xiàn)簡(jiǎn)單、不更改原始的業(yè)務(wù)邏輯,但是仍然存在以下缺點(diǎn):

1.如果存在多個(gè)委托類,則需要?jiǎng)?chuàng)建多個(gè)代理類,這樣則會(huì)產(chǎn)生過(guò)多的代理類。

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

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

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

在這里插入圖片描述

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

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

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

3.實(shí)現(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());
        // 通過(guò)反射,調(diào)用委托類的方法
        Object result = method.invoke(animal, args);
        return result;
    }
}

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

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

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

在這里插入圖片描述

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

jdk動(dòng)態(tài)代理之所以只能代理接口是因?yàn)榇眍惐旧硪呀?jīng)extends了Proxy,而java是不允許多重繼承的,但是允許實(shí)現(xiàn)多個(gè)接口

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

1.優(yōu)點(diǎn): jdk動(dòng)態(tài)代理,避免了靜態(tài)代理需要?jiǎng)?chuàng)建并維護(hù)過(guò)多的代理類的

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

2.3 cglib實(shí)現(xiàn)動(dòng)態(tài)代理

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

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

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

三、cglib動(dòng)態(tài)代理的實(shí)現(xiàn)

添加maven依賴

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

簡(jiǎn)化版的銀行系統(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,表示要增強(qiáng)的對(duì)象,其實(shí)就是代理中的委托類
    * @param method,被攔截的方法,其實(shí)就是委托類中被代理的方法
    * @param objects,被攔截方法的入?yún)?,如果是基本?shù)據(jù)類型需要傳入包裝類型
    * @param methodProxy,對(duì)method的代理,通過(guò)invokeSuper(),實(shí)現(xiàn)對(duì)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,實(shí)現(xiàn)方法與攔截器的映射

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

使用cglib實(shí)現(xiàn)動(dòng)態(tài)代理 —— 我認(rèn)為就是方法攔截 😂

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è)置多個(gè)攔截器
    enhancer.setCallbacks(new Callback[]{transferInterceptor, queryBalanceInterceptor});
    // 實(shí)現(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)點(diǎn)1: cglib基于實(shí)現(xiàn)動(dòng)態(tài)代理,通過(guò)ASM字節(jié)碼框架動(dòng)態(tài)生成委托類的子類,并使用方法攔截器實(shí)現(xiàn)對(duì)委托類方法的攔截。

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

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

四、面試常見(jiàn)問(wèn)題

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

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

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

三種代理方式的比較

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

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

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

為何調(diào)用代理類的方法,會(huì)自動(dòng)進(jìn)入InvocationHandlerinvoke()方法?

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

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

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

相關(guān)文章

最新評(píng)論