目錄

  • 代理模式是什麼?
  • 靜態代理
  • 動態代理
  • JDK動態代理
  • CGLIB代理

代理模式是什麼?

代理模式的作用是:為其他對象提供一種代理以控制對這個對象的訪問。在某些情況下,一個客户不想或者不能直接引用一個對象,此時可以通過一個稱之為“代理”的第三者來實現間接引用。代理對象可以在客户端和目標對象之間起到中介的作用,並且可以通過代理對象去掉客户不應該看到的內容和服務或者添加客户需要的額外服務。 通過引入一個新的對象來實現對真實對象的操作或者將新的對象作為真實對象的一個替身,這種實現機制即為代理模式,通過引入代理對象來間接訪問一個對象,這就是代理模式的模式動機。

代理模式中的角色:

  • 代理類(代理主題)
  • 目標類(真實主題)
  • 代理類和目標類的公共接口(抽象主題):客户端在使用代理類時就像在使用目標類,不被客户端所察覺,所以代理類和目標類要有共同的行為,也就是實現共同的接口。

代理模式的類圖:

Spring源碼剖析5:JDK和cglib動態代理原理詳解_#AOP

簡單來説就是 我們使用代理對象來代替對真實對象(real object)的訪問,這樣就可以在不修改原目標對象的前提下,擴展目標對象的功能。

代理模式有靜態代理和動態代理兩種實現方式

靜態代理

靜態代理中,我們對目標對象的每個方法的增強都是手動完成的(後面會具體演示代碼),非常不靈活(比如接口一旦新增加方法,目標對象和代理對象都要進行修改)且麻煩(需要對每個目標類都單獨寫一個代理類)。

1.定義發送短信的接口

public interface SmsService {
    String send(String message);
}

2.實現發送短信的接口

public class SmsServiceImpl implements SmsService {
    public String send(String message) {
        System.out.println("send message:" + message);
        return message;
    }
}

3.創建代理類並同樣實現發送短信的接口

public class SmsServiceImpl implements SmsService {
    public String send(String message) {
        System.out.println("send message:" + message);
        return message;
    }
}

3.創建代理類並同樣實現發送短信的接口

public class SmsProxy implements SmsService {

    private final SmsService smsService;

    public SmsProxy(SmsService smsService) {
        this.smsService = smsService;
    }

    @Override
    public String send(String message) {
        //調用方法之前,我們可以添加自己的操作
        System.out.println("before method send()");
        smsService.send(message);
        //調用方法之後,我們同樣可以添加自己的操作
        System.out.println("after method send()");
        return null;
    }
}

4.實際使用

public class Main {
    public static void main(String[] args) {
        SmsService smsService = new SmsServiceImpl();
        SmsProxy smsProxy = new SmsProxy(smsService);
        smsProxy.send("java");
    }
}

運行上述代碼之後,控制枱打印出:

before method send()
send message:java
after method send()
動態代理

動態代理更加靈活。我們不需要對每個目標類都單獨創建一個代理類(JDK動態代理機制),並且也不需要代理類實現接口,我們可以直接代理目標類( CGLIB 動態代理機制)。從 JVM 角度來説,動態代理是在運行時動態生成類字節碼,並加載到 JVM 中的。

JDK動態代理

JDK動態代理只能代理接口,底層是採用實現接口的方式實現的

例子:一個接口和一個實現類。

package com.powernode.mall.service;

/**
 * 訂單接口
 **/
public interface OrderService {
    /**
     * 生成訂單
     */
    void generate();

    /**
     * 查看訂單詳情
     */
    void detail();

    /**
     * 修改訂單
     */
    void modify();
}
package com.powernode.mall.service.impl;

import com.powernode.mall.service.OrderService;


public class OrderServiceImpl implements OrderService {
    @Override
    public void generate() {
        try {
            Thread.sleep(1234);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("訂單已生成");
    }

    @Override
    public void detail() {
        try {
            Thread.sleep(2541);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("訂單信息如下:******");
    }

    @Override
    public void modify() {
        try {
            Thread.sleep(1010);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("訂單已修改");
    }
}
package com.powernode.mall;

import com.powernode.mall.service.OrderService;
import com.powernode.mall.service.impl.OrderServiceImpl;

import java.lang.reflect.Proxy;

public class Client {
    public static void main(String[] args) {
        // 第一步:創建目標對象
        OrderService target = new OrderServiceImpl();
        // 第二步:創建代理對象
        OrderService orderServiceProxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), 調用處理器對象);
        // 第三步:調用代理對象的代理方法
        orderServiceProxy.detail();
        orderServiceProxy.modify();
        orderServiceProxy.generate();
    }
}

以上第二步創建代理對象是需要大家理解的:
OrderService orderServiceProxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), 調用處理器對象);

這行代碼做了兩件事:

  • 第一件事:在內存中生成了代理類的字節碼
  • 第二件事:創建代理對象

Proxy類全名:java.lang.reflect.Proxy。這是JDK提供的一個類(所以稱為JDK動態代理)。主要是通過這個類在內存中生成代理類的字節碼。

其中newProxyInstance()方法有三個參數:

  • 第一個參數:類加載器。在內存中生成了字節碼,要想執行這個字節碼,也是需要先把這個字節碼加載到內存當中的。所以要指定使用哪個類加載器加載。
  • 第二個參數:接口類型。代理類和目標類實現相同的接口,所以要通過這個參數告訴JDK動態代理生成的類要實現哪些接口。
  • 第三個參數:調用處理器。這是一個JDK動態代理規定的接口,接口全名:java.lang.reflect.InvocationHandler。顯然這是一個回調接口,也就是説調用這個接口中方法的程序已經寫好了,就差這個接口的實現類了。

所以接下來我們要寫一下java.lang.reflect.InvocationHandler接口的實現類,並且實現接口中的方法,代碼如下:

package com.powernode.mall.service;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class TimerInvocationHandler implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return null;
    }
}

有了目標對象我們就可以在invoke()方法中調用目標方法了。代碼如下:

package com.powernode.mall.service;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;


public class TimerInvocationHandler implements InvocationHandler {
    // 目標對象
    private Object target;

    // 通過構造方法來傳目標對象
    public TimerInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 目標執行之前增強。
        long begin = System.currentTimeMillis();
        // 調用目標對象的目標方法
        Object retValue = method.invoke(target, args);
        // 目標執行之後增強。
        long end = System.currentTimeMillis();
        System.out.println("耗時"+(end - begin)+"毫秒");
        // 一定要記得返回哦。
        return retValue;
    }
}
package com.powernode.mall;

import com.powernode.mall.service.OrderService;
import com.powernode.mall.service.TimerInvocationHandler;
import com.powernode.mall.service.impl.OrderServiceImpl;

import java.lang.reflect.Proxy;


public class Client {
    public static void main(String[] args) {
        // 創建目標對象
        OrderService target = new OrderServiceImpl();
        // 創建代理對象
        OrderService orderServiceProxy = (OrderService) Proxy.newProxyInstance(target.getClass().getClassLoader(),
                                                                                target.getClass().getInterfaces(),
                                                                                new TimerInvocationHandler(target));
        // 調用代理對象的代理方法
        orderServiceProxy.detail();
        orderServiceProxy.modify();
        orderServiceProxy.generate();
    }
}

大家可能會比較好奇:那個InvocationHandler接口中的invoke()方法沒看見在哪裏調用呀?

注意:當你調用代理對象的代理方法的時候,註冊在InvocationHandler接口中的invoke()方法會被調用。也就是上面代碼第24 25 26行,這三行代碼中任意一行代碼執行,註冊在InvocationHandler接口中的invoke()方法都會被調用。

執行結果:

Spring源碼剖析5:JDK和cglib動態代理原理詳解_#java_02

學到這裏可能會感覺有點懵,折騰半天,到最後這不是還得寫一個接口的實現類嗎?沒省勁兒呀?

你要這樣想就錯了!!!

我們可以看到,不管你有多少個Service接口,多少個業務類,這個TimerInvocationHandler接口是不是隻需要寫一次就行了,代碼是不是得到複用了!!!!

而且最重要的是,以後程序員只需要關注核心業務的編寫了,像這種統計時間的代碼根本不需要關注。因為這種統計時間的代碼只需要在調用處理器中編寫一次即可。

到這裏,JDK動態代理的原理就結束了。

CGLIB代理

CGLIB動態代理既可以代理接口,也可以代理類,底層是使用繼承實現,所以既然是繼承,那被代理的目標類就不能用final修飾。

我們準備一個沒有實現接口的類(目標類),如下:

package com.powernode.mall.service;


public class UserService {

    public void login(){
        System.out.println("用户正在登錄系統....");
    }

    public void logout(){
        System.out.println("用户正在退出系統....");
    }
}

使用CGLIB在內存中為UserService類生成代理類,並創建對象:

package com.powernode.mall;

import com.powernode.mall.service.UserService;
import net.sf.cglib.proxy.Enhancer;


public class Client {
    public static void main(String[] args) {
        // 創建字節碼增強器
        Enhancer enhancer = new Enhancer();
        // 告訴cglib要繼承哪個類
        enhancer.setSuperclass(UserService.class);
        // 設置回調接口
        enhancer.setCallback(方法攔截器對象);
        // 生成源碼,編譯class,加載到JVM,並創建代理對象
        UserService userServiceProxy = (UserService)enhancer.create();

        userServiceProxy.login();
        userServiceProxy.logout();

    }
}

和JDK動態代理原理差不多,在CGLIB中需要提供的不是InvocationHandler,而是net.sf.cglib.proxy.MethodInterceptor

編寫MethodInterceptor接口實現類:

回調已經寫完了,可以修改客户端程序了:

package com.powernode.mall;

import com.powernode.mall.service.TimerMethodInterceptor;
import com.powernode.mall.service.UserService;
import net.sf.cglib.proxy.Enhancer;


public class Client {
    public static void main(String[] args) {
        // 創建字節碼增強器
        Enhancer enhancer = new Enhancer();
        // 告訴cglib要繼承哪個類
        enhancer.setSuperclass(UserService.class);
        // 設置回調接口
        enhancer.setCallback(new TimerMethodInterceptor());
        // 生成源碼,編譯class,加載到JVM,並創建代理對象
        UserService userServiceProxy = (UserService)enhancer.create();

        userServiceProxy.login();
        userServiceProxy.logout();

    }
}

執行結果:

Spring源碼剖析5:JDK和cglib動態代理原理詳解_#Spring_03

至此,CGLIB代理就寫完了。