JDK動態代理介紹與使用

一、介紹

  JDK動態代理是代理模式的一種實現方式。因爲它是基於接口來做代理的,所以也常被稱爲接口代理。在JDK動態代理中有兩個重要的角色:

  • InvocationHandler(Interface)
    用戶實現這個接口,來編寫代理類處理的核心邏輯。
  • Proxy(Class)
    用來創建一個代理實例,此時需要用到上面自定義的InvocationHandler。

二、功能

  動態代理擁有代理模式的基本功能,如:調用真實方法的預處理模塊化通用功能。除此之外,還可以在運行時動態創建代理對象,無需針對每個接口編寫代理邏輯(針對每個接口都編寫對應的處理邏輯,叫做靜態代理)。

三、使用步驟

  1. 編寫目標接口、目標實現類
  2. 自定義InvocationHandler接口實現類,編寫代理處理邏輯
    我們來看看InvocationHandler這個接口的唯一一個方法 invoke 方法
/**
 * proxy:代理對象,一般用不到
 * method:指代的是我們所要調用真實對象的某個方法的Method對象
 * args:指代的是調用真實對象某個方法時接受的參數
 **/
Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
  1. 創建代理對象
    創建代理對象時,需要關聯一個InvocationHandler對象。這樣當我們通過代理對象調用目標方法的時候,這個方法的調用就會被轉發到InvocationHandler這個接口的 invoke 方法中。

我們一般把創建代理對象的方法,直接寫在自定義的InvocationHandler實現類中。

  1. 使用代理調用目標接口中的方法

四、示例

需求:調用對象的每個方法時,在調用前、調用後、調用異常等都打印出一行日誌。
對於這種需求,我們就需要把打印日誌的功能模塊化起來,不能在每個方法中都編寫這種打印日誌的代碼,那樣會把通用功能業務功能混合在一起,後續不好維護。

1. 編寫目標接口、目標類

目標接口(因爲JDK動態代理是基於接口實現的,所以被代理的目標類,一定要實現一個接口。)

public interface UserService {
    String getUserName(Long userId);

    void say(String msg);
}

目標類

public class UserServiceImpl implements UserService {
    @Override
    public String getUserName(Long userId) {
        return "user" + userId;
    }

    @Override
    public void say(String msg) {
        System.out.println("say " + msg);
    }
}
2. 自定義InvocationHandler
public class LogInvocationHandler implements InvocationHandler {
    /**
     * 1. 目標類
     */
    private final Object target;

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

    /**
     * 2. 代理邏輯
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //調用目標方法
        Object result = null;
        try {
            //前置通知
            System.out.println(method.getName() + "方法開始調用...");
            result = method.invoke(target, args);
            //返回通知, 可以訪問到方法的返回值
            System.out.println(method.getName() + "方法返回值:" + result);
        } catch (Exception e) {
            e.printStackTrace();
            //異常通知, 可以訪問到方法出現的異常
            System.out.println(method.getName() + "方法調用出現了異常");
        }
        //後置通知.
        System.out.println(method.getName() + "方法調用完成!");
        return result;
    }

    /**
     * 3. 獲取目標類代理
     */
    public Object getProxy() {
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                target.getClass().getInterfaces(),
                this);
    }
}

自定義的InvocationHandler實現類的三部曲(非常重要):

  1. 重寫invoke方法,編寫代理核心邏輯
  2. 保存目標對象(就是上面的target對象,在創建代理時會用到)
  3. 提供創建代理的方法
    使用Proxy類newProxyInstance方法實現,需要的三個參數:類加載器、目標類接口、代理邏輯處理類(自定義的InvocationHandler)

這樣使用代理對象調用接口方法時,就可以轉發到代理處理類的invoke方法中了。

3. 使用代理調用目標方法
@Test
public void dynamicProxyTest() {
    UserService userService = new UserServiceImpl();

    LogInvocationHandler logInvocationHandler = new LogInvocationHandler(userService);
    UserService userServiceProxy = (UserService) logInvocationHandler.getProxy();

    userServiceProxy.getUserName(1L);
    System.out.println("=====================");
    userServiceProxy.say("hello");
}

打印結果在這裏插入圖片描述
可以看到,使用代理對象userServiceProxy來調用接口方法時,請求都轉發到了代理處理類的invoke方法中,在invoke方法中使用反射調用目標方法,最終轉發到了target目標對象中。

相關文章:CGLIB動態代理介紹

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