知識庫 / Spring RSS 訂閱

Spring AOP 在同一類中方法調用

Spring
HongKong
4
10:46 AM · Dec 06 ,2025

1. 概述

當使用 Spring AOP 時,存在許多複雜之處。一個常見的問題是,在同一類中處理方法調用會繞過 AOP 功能。

在本教程中,我們將學習一些 Spring AOP 的工作原理,以及可以應用哪些解決方法。

2. 代理與 Spring AOP

為了開始,我們將快速回顧一下代理對象是什麼以及它們在 Spring 框架中的使用。

代理對象可以被認為是包裹對象,它可以為目標對象的調用添加額外的功能。

在 Spring 中,當一個 Bean 需要額外的功能時,會創建一個代理對象,並將該代理對象注入到其他 Bean 中。例如,如果一個方法帶有 Transactional 註解或任何緩存註解,則使用該代理對象來為目標對象的調用添加所需的額外功能。

3. AOP:內部方法調用與外部方法調用

正如前面所述,Spring 創建的代理添加了所需的 AOP 功能。為了説明內部方法調用和外部方法調用之間的差異,我們來看一個緩存示例:

@Component
@CacheConfig(cacheNames = "addOne")
public class AddComponent {

    private int counter = 0;

    @Cacheable
    public int addOne(int n) {
        counter++;
        return n + 1;
    }

    @CacheEvict
    public void resetCache() {
        counter = 0;
    }
}

當 Spring 創建 AddComponent Bean 時,還會創建一個代理對象,用於為緩存添加 AOP 功能。當另一個對象需要 AddComponent Bean 時,Spring 會提供該代理對象用於注入。

進行測試,我們調用 addOne() 方法,從一個單獨的組件中多次使用相同的輸入,並驗證計數器只增加一次。

@SpringBootTest(classes = Application.class)
class AddComponentUnitTest {

    @Resource
    private AddComponent addComponent;

    @Test
    void whenExternalCall_thenCacheHit() {
        addComponent.resetCache();

        addComponent.addOne(0);
        addComponent.addOne(0);

        assertThat(addComponent.getCounter()).isEqualTo(1);
    }
}

現在,我們為 AddComponent 添加另一個方法,該方法內部調用 addOne()

public int addOneAndDouble(int n) {
    return this.addOne(n) + this.addOne(n);
}

當這種新方法調用 addOne() 時,調用不會經過代理,計數器會被遞增兩次:

@Test
void whenInternalCall_thenCacheNotHit() {
    addComponent.resetCache();

    addComponent.addOneAndDouble(0);

    assertThat(addComponent.getCounter()).isEqualTo(2);
}

4. 規避方案

雖然同一類中方法調用不會通過預期的 AOP 功能,但仍有幾種規避方案。

其中一種最佳方法是重構。 在我們的 AddComponent 示例中,與其直接將 addOneAndDouble() 添加到類中,不如創建一個新的類包含該方法。 新類可以注入 AddComponent,或者更準確地説,AddComponent 的代理將注入到該類中:

@Component
public class AddOneAndDoubleComponent {

    @Resource
    private AddComponent addComponent;

    public int addOneAndDouble(int n) {
        return addComponent.addOne(n) + addComponent.addOne(n);
    }
}

正如我們之前的測試結果所表明的,這隻會增加計數器一次。

如果無法進行重構,則可以嘗試直接將代理注入到類中。這需要謹慎處理,因為這會創建一個直接的循環依賴關係,Spring 默認不再允許。但是,仍然有許多解決方案可以允許在 Spring 中存在循環依賴關係,我們將會使用 @Lazy 註解來標記自依賴關係:

@Component
@CacheConfig(cacheNames = "selfInjectionAddOne")
public class SelfInjection {

    @Lazy
    @Resource
    private SelfInjection selfInjection;

    private int counter = 0;

    @Cacheable
    public int addOne(int n) {
        counter++;
        return n + 1;
    }

    public int addOneAndDouble(int n) {
        return selfInjection.addOne(n) + selfInjection.addOne(n);
    }

    @CacheEvict(allEntries = true)
    public void resetCache() {
        counter = 0;
    }
}

通過注入代理後,addOneAndDouble() 將會使用緩存功能,計數器只會遞增一次:

@Test
void whenCallingFromExternalClass_thenAopProxyIsUsed() {
    selfInjection.resetCache();

    selfInjection.addOneAndDouble(0);

    assertThat(selfInjection.getCounter()).isEqualTo(1);
}

Spring AOP 使用動態代理創建代理實例,這是一種動態織入的例子。另一方面,AspectJ 使用了其他幾種類型的織入方式,從而避免了重構和自注入的需求。

5. 結論

在本文中,我們探討了在同一類中處理 Spring AOP 調用的一些方法。重構通常是首選解決方案,但如果無法實現,可以使用 AspectJ 或自注入來實施所需的功能。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.