博客 / 詳情

返回

Java 代理模型學習

前言

最近在寫項目的時候,又寫到很久沒寫的 AOP 切面實現一個需求,又想到上次同學面試的時候被問到了 Spring AOP 的實現原理是什麼,以前就知道是用了代理模式,但是也沒有進行過多的去研究,剛好碰到了也就研究一下代理模式。

什麼是代理模式

代理模式就是通過一個代理對象來間接訪問目標對象,這樣可以在不改變目標對象的情況下,為它添加一些額外的功能或行為。簡單來説,代理就是“替身”,它在幕後幫目標對象做一些額外的事。要擴展功能,就不需要更改源目標的代碼了,只需要在代理類增加就可以了。

graph LR
    A[訪客] --> B[代理類 增加額外功能]
    B --> C[目標類]

代理模式的分類

  1. 靜態代理:代理類在編譯時就已經確定,通常需要手動創建代理類。
  2. 動態代理:代理類在運行時動態生成,不需要提前創建,代理對象的創建由代理框架(如 JDK 動態代理或 CGLIB)控制。
特點 靜態代理 動態代理
代理類創建時機 代理類在編譯時已經確定,需要手動創建代理類 代理類在運行時動態生成,不需要提前創建
實現方式 手動編寫代理類,代理類與目標類關係固定 使用代理框架(如 JDK 動態代理、CGLIB)在運行時創建代理類
目標類要求 目標類必須實現接口(或繼承某個類) JDK 動態代理:目標類必須實現接口;CGLIB:不需要接口

靜態代理的實現

靜態代理就是在程序編譯時就創建好代理類,並讓代理類和目標類實現相同的接口。代理類通過調用目標類的方法來完成任務,同時可以在調用前後添加一些額外的操作。

classDiagram
    UserService <|.. UserServiceImpl
    UserServiceProxy --> UserServiceImpl : delegates to
    UserService : +save()
    UserService : +update()
    UserService : +delete()
    UserServiceImpl : +save()
    UserServiceImpl : +update()
    UserServiceImpl : +delete()
    UserServiceProxy : +save()
    UserServiceProxy : +update()
    UserServiceProxy : +delete()

示例:靜態代理模式

接口定義:

public interface UserService {
    void save(String username);
    void update(Long id, String username);
    void delete(Long id);
}

目標類實現:

public class UserServiceImpl implements UserService {
    @Override
    public void save(String username) {
        System.out.println("增加用户: " + username);
    }

    @Override
    public void update(Long id, String username) {
        System.out.println("編輯用户: " + username);
    }

    @Override
    public void delete(Long id) {
        System.out.println("刪除用户: " + id);
    }
}

靜態代理類:

public class UserServiceProxy implements UserService {
    private UserServiceImpl userService;

    public UserServiceProxy(UserServiceImpl userService) {
        this.userService = userService;
    }
    @Override
    public void save(String userName) {
        // 在調用實際方法之前,做一些操作(如記錄日誌)
        System.out.println("日誌:在添加用户之前");
        userService.save(userName);  // 調用真實對象的方法
        System.out.println("日誌:在添加用户之後");
    }

    @Override
    public void update(Long id, String userName) {
        System.out.println("日誌:在更新用户之前");
        userService.update(id, userName); 
        System.out.println("日誌:在更新用户之後");
    }

    @Override
    public void delete(Long id) {
        System.out.println("日誌:在刪除用户之前");
        userService.delete(id);  // 調用真實對象的方法
        System.out.println("日誌:在刪除用户之後");
    }
}

使用代理

public class Main {
    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceImpl();
        UserService userServiceProxy = new UserServiceProxy(userService);

        userServiceProxy.save("yunzhi");
        userServiceProxy.update(1L, "yunzhi");
        userServiceProxy.delete(1L);
    }
}
打印結果:
  日誌:在添加用户之前
  增加用户: yunzhi
  日誌:在添加用户之後

  日誌:在更新用户之前
  編輯用户: yunzhi
  日誌:在更新用户之後

  日誌:在刪除用户之前
  刪除用户: 1
  日誌:在刪除用户之後

結論:

靜態代理的實現方式相對直觀,代碼也較為簡潔易懂。然而,這種模式的缺點也非常明顯:當需要代理的接口數量增多時,每增加一個接口就必須創建一個相應的代理類,這樣會導致大量的代理類代碼,造成系統的臃腫和維護上的困難。

動態代理的實現

與靜態代理不同,動態代理是在運行時通過代理框架(如 JDK 動態代理或 CGLIB)來生成代理對象,不需要提前編寫代理類。

JDK動態代理

JDK 動態代理,當目標類實現了接口時,並將接口的方法委託給目標類的實現。

JDK動態代理的實現步驟:

1.定義接口和實現類。
2.創建InvocationHandler實現,負責目標方法的攔截和增強
3.使用 Proxy 類創建代理對象。

創建一個 JdkLoggingInvocationHandler 類,統一實現日誌處理代理

public class JdkLoggingInvocationHandler implements InvocationHandler {
    private Object target;

    public JdkLoggingInvocationHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("日誌:調用 " + method.getName() + " 方法");
        // 調用目標對象的方法
        Object result = method.invoke(target, args);
        // 在方法調用後添加日誌
        System.out.println("日誌:執行 " + method.getName() + " 方法完成");
        return result;
    }
}

使用代理

    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceImpl();
        JdkLoggingInvocationHandler handler = new JdkLoggingInvocationHandler(userService);

        UserService proxy = (UserService) Proxy.newProxyInstance(
                UserService.class.getClassLoader(),
                new Class<?>[]{UserService.class},
                handler);

        proxy.save("yunzhi");
        proxy.update(1L, "yunzhi");
        proxy.delete(1L);
    }
打印結果:
  日誌:調用 save 方法
  增加用户: yunzhi
  日誌:執行 save 方法完成

  日誌:調用 update 方法
  編輯用户: yunzhi
  日誌:執行 update 方法完成

  日誌:調用 delete 方法
  刪除用户: 1
  日誌:執行 delete 方法完成

注意點

這裏我們要注意當前的代理對象不是原始對象了,通過 Proxy.newProxyInstance 創建的代理對象,實際上並不是 UserServiceImpl 或其他實現類的實例,它是通過 Proxy 類在運行時生成的,實現了目標接口,並通過 InvocationHandler 實現了方法的增強邏輯。

image.png

目標類沒有接口就不能使用JDK代理

這裏我們把 UserService 移除掉,不是實現接口的形式

public class UserServiceImpl {
}

使用代理

UserServiceImpl userService = new UserServiceImpl();
JdkLoggingInvocationHandler handler = new JdkLoggingInvocationHandler(userService);

UserServiceImpl proxy = (UserServiceImpl) Proxy.newProxyInstance(
        UserServiceImpl.class.getClassLoader(),
        new Class<?>[]{UserServiceImpl.class},
        handler);

image.png

這裏我們可以發現,如果目標類不是接口的話就沒辦法進行代理增強。

結論

在靜態代理中,每個目標類都需要一個單獨的代理類,代理類的代碼重複且難以維護。

在JDK動態代理中,你只需要編寫一個 InvocationHandler 來統一處理所有目標類的代理方法,增強邏輯可以複用,但是必須要求目標類是有接口實現。

CGLIB 動態代理

CGLIB 代理可以對任何沒有實現接口的類進行代理,因為它是通過繼承目標類並重寫方法來實現的,在實際的場景中,有一些業務不總是實現接口,為了增強這些沒有接口的類,所以 Spring 使用 CGLIB 的方式實現動態代理。

截取 Spring 項目中使用 AOP 的對象

image.png

CGLIB 動態代理的實現步驟:

1.定義目標類
CGLIB 代理是基於類的,而不是接口的,所以目標類不需要實現接口。

2.創建 MethodInterceptor 實現
MethodInterceptor 用於攔截目標方法,並進行增強邏輯。這個類相當於 JDK 動態代理中的 InvocationHandler。

3.使用 Enhancer 創建代理對象
Enhancer 是 CGLIB 的核心類,用於創建動態代理對象。它通過繼承目標類並重寫目標方法來實現代理。

創建一個 CglibLoggingMethodInterceptor 類,統一實現日誌處理代理

public class CglibLoggingMethodInterceptor implements MethodInterceptor {
    private Object target;

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

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("日誌:調用 " + method.getName() + " 方法");
        Object result = proxy.invokeSuper(obj, args);  // 調用父類的方法
        System.out.println("日誌:執行 " + method.getName() + " 方法完成");
        return result;
    }
}

使用代理

    public static void main(String[] args) {
        UserServiceImpl userServiceImpl = new UserServiceImpl();

        // 創建 CGLIB 代理的 Enhancer 對象
        Enhancer enhancer = new Enhancer();

        // 設置代理的目標類(即 UserServiceImpl 類)
        // CGLIB 通過繼承這個目標類來生成代理類
        enhancer.setSuperclass(UserServiceImpl.class);

        // 設置方法攔截器,代理會調用這個攔截器
        // 這裏使用了一個自定義的 CglibLoggingMethodInterceptor 來攔截方法並添加日誌增強
        enhancer.setCallback(new CglibLoggingMethodInterceptor(userServiceImpl));

        // 創建代理對象
        // 通過 Enhancer.create() 創建代理對象,此對象會繼承 UserServiceImpl 類並重寫其方法
        UserServiceImpl proxy = (UserServiceImpl) enhancer.create();

        proxy.save("yunzhi");
        proxy.update(1L, "yunzhi");
        proxy.delete(1L);
    }
打印結果:
  日誌:調用 save 方法
  增加用户: yunzhi
  日誌:執行 save 方法完成

  日誌:調用 update 方法
  編輯用户: yunzhi
  日誌:執行 update 方法完成

  日誌:調用 delete 方法
  刪除用户: 1
  日誌:執行 delete 方法完成

注意點

這裏我們要注意當前的代理對象不是原始對象了,通過 Enhancer 類來創建代理對象。這個代理對象是通過繼承目標類 UserServiceImpl 來創建的,代理對象並不是原始的 UserServiceImpl 對象,而是通過繼承生成的新類。

image.png

總結:

  • 靜態代理

    • 適用於簡單場景,代碼重複且不靈活。
  • JDK 動態代理

    • 代理對象實現了目標接口,實際的目標對象沒有改變。
    • 代理對象通過 Proxy 類動態生成,並通過 InvocationHandler 處理方法增強。
  • CGLIB 代理

    • 代理對象繼承自目標類並重寫目標方法,實際的目標類沒有改變。
    • 代理對象通過 Enhancer 動態生成,並通過 MethodInterceptor 處理方法增強。
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.