(十七)關於動態代理,你能說出動態代理的幾種方式?

Java魚仔發表於2020-11-04

微信搜尋《Java魚仔》,每天一個知識點不錯過

所有內容以及歷史知識點均會更新到github上,歡迎star

(一)每天一個知識點

給我講講多執行緒鎖的升級原理是什麼?

(二)詳解

動態代理是指代理類不是寫在程式碼中的,而是在程式碼執行過程中產生的,Java提供了兩種方式來實現動態代理,分別是基於Jdk的動態代理和基於Cglib的動態代理。

2.1 基於Jdk的動態代理

首先我們來搭建一套實現動態代理的實現,以租房為例,我新建一個租房的介面:

public interface Room {
    void rent();
}

然後下一個實際的租房實現類

public class RealRoom implements Room {
    private String roomname;
    public RealRoom(String roomname) {
        this.roomname = roomname;
    }
    public void rent() {
        System.out.println("租了"+roomname);
    }
}

基於Jdk的動態代理核心在於InvocationHandler 介面,首先我們新建一個類ProxyHandler來實現這個InvocationHandler 介面,並實現裡面的invoke方法

public class ProxyHandler implements InvocationHandler {
    Object object;
    public ProxyHandler(Object object) {
        this.object = object;
    }
    //proxy 代理物件
    //method 要實現的方法
    //args 方法的引數    
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理執行之前:"+method.getName());
        Object invoke = method.invoke(object, args);
        System.out.println("代理執行之後:"+method.getName());
        return invoke;
    }
}

InvocationHandler 是一個介面,每個代理的例項都有一個與之關聯的 InvocationHandler 實現類,如果代理的方法被呼叫,那麼代理便會通知和轉發給內部的 InvocationHandler 實現類invoke,由它實現處理內容。

接下來寫一個方法來實現動態代理

public static void main(String[] args) {
    Room room=new RealRoom("碧桂園");
    //obj.getClass().getClassLoader()類載入器
    //obj.getClass().getInterfaces() 目標類實現的介面
    //InvocationHandler物件
    InvocationHandler invocationHandler=new ProxyHandler(room);
    Room proxyRoom = (Room) Proxy.newProxyInstance(room.getClass().getClassLoader(), room.getClass().getInterfaces(), invocationHandler);
    proxyRoom.rent();
}

這段程式碼的另外一個核心是Proxy.newProxyInstance,該方法需要三個引數,目的是執行期間生成代理類,每個引數的功能已經寫在了註釋中。這段程式碼的意思就是Proxy 動態產生的代理物件會呼叫 InvocationHandler 實現類invoke。

最終我們不需要RealRoom去呼叫rent方法,通過代理類就可以實現這個rent方法。

最後結果如下:

代理執行之前:rent
租了碧桂園
代理執行之後:rent

執行時就會在控制檯上列印出來,這也是Spring AOP的核心

JDK動態代理類實現了InvocationHandler介面,重寫的invoke方法。

JDK動態代理的基礎是反射機制(method.invoke(物件,引數))Proxy.newProxyInstance()

2.2 基於Cglib的動態代理

基於Jdk的動態代理侷限性在於代理的類必須要實現介面,而基於CGlib的動態代理則沒有這個限制:

搭建CGlib環境我們首先要引入一個CGlib的jar包:

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

此時我們不再需要介面,直接新建一個CGRoom類:

public class CGRoom {
    public void rent(String roomName){
        System.out.println("租了"+roomName);
    }
}

CGlib實現動態代理的核心在於MethodInterceptor介面:

public class MyMethodInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("代理執行之前:"+method.getName());
        Object object=methodProxy.invokeSuper(o,objects);
        System.out.println("代理執行之後:"+method.getName());
        return object;
    }

這個介面只有一個intercept()方法,攔截被代理物件,這個方法有4個引數:

1)表示增強的物件,即實現這個介面類的一個物件;

2)表示要被攔截的方法;

3)表示要被攔截方法的引數;

4)表示要觸發父類的方法物件;

最後生成代理類物件並輸出執行結果

public static void main(String[] args) {
    //建立Enhancer物件,類似於JDK動態代理的Proxy類,下一步就是設定幾個引數
    Enhancer enhancer=new Enhancer();
    //設定目標類的位元組碼檔案
    enhancer.setSuperclass(CGRoom.class);
    //設定回撥函式
    enhancer.setCallback(new MyMethodInterceptor());
    //建立代理物件
    CGRoom proxy= (CGRoom) enhancer.create();
   proxy.rent("碧桂園");
}

執行結果:

代理執行之前:rent
租了碧桂園
代理執行之後:rent

(三)總結

兩種代理方式各有優劣,在使用方面,JDK動態代理只能基於介面進行實現,而CGLIb對代理的目標物件無限制,無需實現介面。

在依賴方面,Java原生支援JDK動態代理,而CGlib的實現還需要引入相關依賴包。

相關文章