概述#
當 SOLID 驅動的 AOP(第十章)因為 Legacy Code 或團隊因素而難以採用時,可以考慮工具導向的 AOP。本章介紹兩種方法:Dynamic Interception(動態攔截) 與 Compile-time Weaving(編譯期織入),並分析各自的利弊。
作者明確表示偏好順序為:SOLID 驅動 AOP > Dynamic Interception > Compile-time Weaving。Compile-time Weaving 在本書中被視為反模式。
Dynamic Interception#
運作原理#
Dynamic Interception 透過函式庫在執行時期動態產生 Decorator,免去手動撰寫每個 Decorator 類別的工作。最知名的實作是 Castle Dynamic Proxy。

Figure 11.1: Dynamic Interception 函式庫在執行時期動態產生 Decorator 類別
IInterceptor 介面#
Castle Dynamic Proxy 的核心是 IInterceptor 介面:
public interface IInterceptor
{
void Intercept(IInvocation invocation);
}所有攔截邏輯都實作在 Intercept 方法中,透過 IInvocation 物件存取被呼叫的方法資訊與參數。
Circuit Breaker 範例#
public class CircuitBreakerInterceptor : IInterceptor
{
private readonly CircuitBreaker breaker;
public CircuitBreakerInterceptor(CircuitBreaker breaker)
{
this.breaker = breaker;
}
public void Intercept(IInvocation invocation)
{
if (breaker.IsOpen)
throw new CircuitBreakerOpenException();
try
{
invocation.Proceed(); // 呼叫原始方法
breaker.RecordSuccess();
}
catch (Exception ex)
{
breaker.RecordFailure();
throw;
}
}
}在 Composition Root 中使用#
var generator = new ProxyGenerator();
var interceptor = new CircuitBreakerInterceptor(breaker);
IProductRepository repository =
generator.CreateInterfaceProxyWithTarget<IProductRepository>(
new SqlProductRepository(connectionString),
interceptor);CreateInterfaceProxyWithTarget 會在執行時期產生一個實作 IProductRepository 的 Proxy 類別,自動將所有方法呼叫導向 Interceptor。

Figure 11.2: 客戶端呼叫被攔截的 Abstraction 時的方法呼叫流程
優點#
- 減少 Boilerplate:不需要為每個介面手動撰寫 Decorator
- 容易加入既有系統:不需要重構介面設計
- 一個 Interceptor 套用多個介面:與泛型 Decorator 的效果類似
缺點#
- 依賴特定工具:與 Castle Dynamic Proxy 或其他函式庫耦合
- 執行期錯誤:型別錯誤在編譯時無法發現,延遲到執行時期才爆發
- 脆弱的慣例:依賴字串比對方法名稱等慣例,容易因重構而破壞
- 除錯困難:動態產生的 Proxy 讓 Call Stack 更難閱讀
- 效能開銷:Reflection 與動態型別產生有額外成本
Dynamic Interception 是「次佳選擇 (Next best pick)」——當 SOLID 驅動的 AOP 對團隊來說變革太大時,Dynamic Interception 提供了一個務實的折衷方案。
Compile-time Weaving#
運作原理#
Compile-time Weaving 在編譯後修改 IL (Intermediate Language),將橫切關注點直接織入已編譯的程式碼中。最知名的工具是 PostSharp。

Figure 11.3: Compile-time Weaving 流程
使用方式#
透過 Attribute 標記需要織入行為的方法:
[Transaction]
public void AdjustInventory(int productId, int quantity)
{
// 業務邏輯
// PostSharp 會在編譯後自動加入 Transaction 的開始與提交
}
Figure 11.4: Compile-time Weaving 視覺化
為什麼作者認為這是反模式#
作者對 Compile-time Weaving 的批評非常明確:
1. 與 DI 理念背道而馳
DI 的核心是在執行時期透過 Composition Root 組裝行為,而 Compile-time Weaving 在編譯時期就將行為烘焙進程式碼中,完全無法在部署時改變。
2. 違反 Open/Closed Principle
要改變一個方法的橫切行為,必須修改原始碼中的 Attribute 標記,而非透過擴充來改變。
3. 違反 Dependency Inversion Principle
Aspect 類別(如 [Transaction] 的實作)無法使用 Constructor Injection,因為它們的實例由框架在編譯時期織入,不經過 Composition Root。
4. 測試困難
- 無法輕易用 Test Double 替換 Aspect 的行為
- Aspect 的邏輯與業務邏輯緊密耦合在同一個編譯單元中
- 整合測試變得更複雜
5. SOLID 驅動的方法在各方面都更優秀
| 比較維度 | SOLID 驅動 | Compile-time Weaving |
|---|---|---|
| 編譯期型別安全 | 有 | 有限 |
| 可測試性 | 高 | 低 |
| 靈活度 | 執行期可調整 | 編譯時固定 |
| 工具依賴 | 無 | 高 |
| Constructor Injection | 完整支援 | 不支援 |
| 除錯體驗 | 正常 | 困難 |
Compile-time Weaving 表面上降低了程式碼量,但代價是失去了 DI 帶來的所有彈性——鬆耦合、可測試性、執行期組裝能力。作者認為這個取捨不值得。
三種 AOP 方法的選擇指引#
AOP 方法選擇決策流程
能否採用泛型 Abstraction 重構介面?
- 是 → 使用 SOLID 驅動的 AOP(最佳選擇)
- 否 → 進入下一步
系統是否為 Legacy Code 且重構成本過高?
- 是 → 使用 Dynamic Interception(務實的折衷)
- 否 → 考慮漸進式重構,逐步走向 SOLID 驅動
是否有極端的限制使得 Dynamic Interception 也無法使用?
- 極少數情況下才考慮 Compile-time Weaving
- 即便如此,也應視為過渡方案而非最終架構
本章關鍵術語
- Dynamic Interception:透過函式庫在執行時期動態產生 Proxy/Decorator
- Compile-time Weaving:在編譯後修改 IL,將橫切邏輯織入程式碼
- Castle Dynamic Proxy:.NET 生態系中最常用的 Dynamic Interception 函式庫
- PostSharp:.NET 生態系中最知名的 Compile-time Weaving 工具
- IInterceptor:Castle Dynamic Proxy 中定義攔截邏輯的介面
- IInvocation:代表被攔截的方法呼叫,包含方法資訊與參數