面試官:用最簡單的方法給我講講什麼是靜態代理,動態代理

小時候很喜歡看小故事大道理,用通俗易懂的小故事去解釋抽象難懂的大道理,並受到啓發,瞭解真諦。今天我們就來試着講一講 代理

故事前情提要:老師佈置了一道作業,要求同學們做完了交給他。

靜態代理

首先,我們先創建一個學生接口,同學們都需要寫完交作業

public interface Student {
    void handWork();
}

實現類表示作業寫完了要交作業了

public class StudentImpl implements Student {

    private String name;

    public StudentImpl(String name){
        this.name = name;
    }


    public void handWork() {
        System.out.println(name + "交作業");
    }
}

有個同學叫小王,寫完了正常交作業

    public static void main(String[] args) {
        StudentImpl student = new StudentImpl("小王");
        student.handWork();
    }

平時也是這麼做的,最終打印出來“小王交作業”。

可是有一天,小王忽然有事請假了,他的作業做完了,誰去幫他交呢。沒辦法,只能委託玩的最好的小夥伴,小李來交作業了,並告訴他,我把交作業的事情全權交於你處理了。你一定不要辜負我的信任吶。

於是乎,小李成了小王的代理

public class StudentProxy implements Student {

    Student student;

    public StudentProxy(StudentImpl impl){
        this.student = impl;
    }

    public void handWork() {
        student.handWork();
    }
}

作業寫完了,小王不在,小李被小王委以重任,他就幫小王交了作業

public class StaticProxyTest {

    public static void main(String[] args) {
        StudentImpl student = new StudentImpl("小王");
        StudentProxy studentProxy = new StudentProxy(student);
        studentProxy.handWork();
    }
}

這就是靜態代理,所謂靜態也就是在程序運行前就已經存在代理類的字節碼文件,代理類和委託類的關係在運行前就確定了。

小王已經告知了小李需要做什麼(交作業),他們都是做作業交作業,倘若有一天小王被老是罰了只有他自己需要交作業,或者小王想喝奶茶,那如果按照上邊的邏輯,小李也要受罰,也要被迫喝奶茶了。

JDK動態代理

這次作業是老師罰你自己寫的,我纔不寫呢

public class StudentInvocationHandler implements InvocationHandler {

    Object target;

    public StudentInvocationHandler(Object target){
        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理執行" + method.getName() + "方法");
        Object invoke = method.invoke(target, args);
        return invoke;
    }
}

invoke方法可以實現代理邏輯,invoke方法的三個參數含義:

  • proxy:代理對象,就是bind方法生成的對象。
  • method:當前調度的方法
  • args:調度方法的參數

當我們使用了代理對象調度方法後,它就會進入到invoke方法裏面。

小李拿到小王的授權,表示什麼都能幫你做,只可惜小王只有一個交作業的事情,其實什麼喝奶茶,吃辣條我也可以代理

public class ProxyTest {

    public static void main(String[] args) {
        StudentImpl student = new StudentImpl("小王");
        StudentInvocationHandler studentInvocationHandler = new StudentInvocationHandler(student);
        Student studentProxy = (Student)Proxy.newProxyInstance(Student.class.getClassLoader(), new Class[]{Student.class}, studentInvocationHandler);
        studentProxy.handWork();
        //趁機喝小王一杯奶茶,也是可以的
       // studentProxy.drink();
    }
}

建立代理對象和真實對象的關係

Student studentProxy = (Student)Proxy.newProxyInstance(Student.class.getClassLoader(), new Class[]{Student.class}, studentInvocationHandler);

其中newProxyInstance方法包含了3個參數。

  • ClassLoader:類加載器,我們採用了target本身的類加載器。

  • Class<?>[]:是把生成的動態代理對象下掛在哪些接口下,這個寫法是放在target實現的接口下。

  • InvocationHandler:是定義實現方法邏輯的代理類,它必須實現InvocationHandler接口的invoke方法,它就是代理邏輯方法的現實方法。

最終打印結果:代理執行handWork方法
小王交作業

動態代理類的源碼是在程序運行期間由JVM根據反射等機制動態的生成,所以不存在代理類的字節碼文件。代理類和委託類的關係是在程序運行時確定。需要實現java.lang.reflect.InvocationHandler的invoke方法。也需要提供接口方可用JDK動態代理

CGLIB動態代理

如果沒有接口,那麼JDK動態代理就會拋錯JdkProxyExample

需要寫一個接口Student,再寫一個StudentImpl實現類,小王也很無奈,我沒有接口,就一個類,但是我還要你代理我,幫我交作業

public class StudentCglib {
    
    public void handWork(String name) {
        System.out.println(name + "交作業");
    }
}

小李也恭敬不如從命了,畢竟小王說,他請假回來要和我開黑

public class CglibProxy implements MethodInterceptor {

    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("調用真實對象前");
        //CGLIB反射調用真實對象方法
        Object result = methodProxy.invokeSuper(proxy, args);
        System.out.println("調用真實對象後");
        return result;
    }

}

這裏採用了CGLIB的加強者Enhander,通過設置超類的方法(setSuperclass),然後通過setCallBack方法設置哪個類爲它的代理類。其中,參數爲this就意味着是當前對象,那就要求用this這個對象實現接口MethodInterceptor的方法——intercept,然後返回代理對象。
那麼此時當前類的intercept方法就是其代理邏輯方法,包含四個參數

  • proxy 代理對象
  • method 方法
  • args 方法參數
  • methodProxy 方法代理

沒有接口,我也可以代理

public class CglibTest {

    public static void main(String[] args) {

        //CGLIB enhancer增強類對象
        Enhancer enhancer = new Enhancer();
        //設置增強類型
        enhancer.setSuperclass(StudentCglib.class);
        //定義代理邏輯對象爲當前對象,要求當前對象實現MethodInterceptor方法
        enhancer.setCallback(new CglibProxy());
        //生成並返回代理對象
        StudentCglib proxy = (StudentCglib)enhancer.create();
        proxy.handWork("小王");
    }
}

最終結果: 調用真實對象前
小王交作業
調用真實對象後

最終,小王小李幸福的生活在了一起

JDK動態代理和CGLIB字節碼生成的區別?

  1. JDK動態代理只能對實現了接口的類生成代理,而不能針對類
  2. CGLIB是針對類實現代理,主要是對指定的類生成一個子類,覆蓋其中的方法,因爲是繼承,所以該類或方法最好不要聲明成final

看完這篇,包你瞭解JDK動態代理
看完這篇,包你瞭解CGLIB動態代理

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章