Spring AOP 用於同一類別中的方法調用
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 或自我注入來實現所需的功能。
與往常一樣,程式碼可在 GitHub 上取得。