AOP 將橫切關注點分成具有建議和切入點的面向。
根據平台的不同,編織可以在編譯、載入或運行時發生。
典型案例:日誌記錄、安全性、事務、異常和快取。
使用精確的切入點來避免遠距離動作並提高清晰度。
如果你曾經在整個專案中遇到過重複功能的問題, 程序設計 面向方面對您來說聽起來就像天籟之音。 POA/AOP 建議將這些橫切關注點分離 (例如日誌記錄、安全性或事務)分解為稱為方面的獨立模組,以便業務程式碼乾淨且專注於真正重要的事情。
除了理論之外,這種方法並不是孤立的:它嵌入在我們每天使用的工具和框架中。 從 Java 生態系中的 AspectJ 和 Spring AOP,到 .NET 和 AspectC++ 中的 PostSharp,穿過書店 蟒蛇 與 AspectLib 一樣,方面編織發生在編譯、載入或運行時,並允許您添加行為而不觸及原始程式碼。
什麼是面向方面程式設計(AOP/POA)?
POA 是一種透過分離呼叫來提高模組化的範例 跨領域關注點。這些是出現在許多地方但彼此之間沒有任何直接關係的邏輯片段(例如,日誌記錄或權限控制)。 而不是重複它們或用噪音填充方法,我們將它們封裝在方面中並在適當的地方“連接”它們。
想像一個繪圖應用程式:每個修改畫布的操作都必須刷新螢幕。 在數百個方法中呼叫 update() 這太折磨了。使用 AOP,你聲明任何以“set”或“draw”開頭的方法之後,都會將刷新作為一個方面執行, 不會弄髒功能代碼.
與 OOP 相比,AOP 並不能取代它;而是對它的補充。 物件和類別仍然是焦點,但我們將交叉元素提取到獨立的模組中,以改善職責分離。在大型專案和分散式團隊中,這種模組化可以簡化開發流程。
問題?確實存在。最主要的問題是 遠端操作:一個方面可能會影響應用程式中的許多點,從而很難追蹤哪個程式碼執行了什麼。 你必須明智地使用它 並具有良好的可觀察性策略。
AOP 的關鍵概念
即使您不是每天都使用它,掌握 AOP 術語也能為您理解框架底層的功能打開大門。 這些都是基本支柱 以及它們在主要環境中的外觀。
出現
一個方面是 模組化單元 它將跨切行為(例如審計、安全或交易)組合在一起。 封裝內容(建議)和位置(切入點) 修改目標程式碼的行為而不觸及其實現。
連接點
連接點是 執行流程中明確定義的點 其中可以掛鉤一個面向(方法呼叫、異常處理、欄位存取等)。 這些點的集合由切入點過濾 我們定義的。
忠告
建議是 在特定連接點執行的程式碼。可以是之前、之後、返回之後、投擲之後,也可以是接合點周圍。 周圍環境可以取代或包圍原來的性能。,這對於交易或政策來說極其強大。
切入點
切入點是 選擇連接點的表達式. 定義類別、方法名稱、簽章、套件等的條件。 僅當切入點「匹配」時才應用建議.
簡介(類型間聲明)
介紹允許 新增成員(欄位/方法)或合約 對於現有類型,無需修改其原始程式碼即可擴充其 API。 有助於均勻分佈能力 在多個班級中。
// AspectJ: añadir campo y método a una clase existente
public aspect ExtendedFunctionalityAspect {
private String MyClass.newField = "Campo introducido";
public String MyClass.getNewField() { return this.newField; }
}
// Spring AOP: exposición mediante interfaz e implementación en el aspecto
public interface ExtendedFunctionality { String getNewField(); }
@Aspect
public class ExtendedFunctionalityAspect implements ExtendedFunctionality {
@Override public String getNewField() { return "Campo introducido"; }
}
// PostSharp (.NET): introducción como atributo de aspecto
[Serializable]
public class ExtendedFunctionalityAttribute : InstanceLevelAspect {
[IntroduceMember]
public string NewField { get; set; } = "Campo introducido";
}
// AspectC++: nuevo método inyectado por el aspecto
aspect ExtendedFunctionalityAspect {
void MyClass::introducedMethod() {
std::cout << "Método introducido" << std::endl;
}
};
所有這些方法都說明了 非侵入式擴充類型。這樣,您可以擴展常見功能(例如,追蹤或計算屬性),而無需觸及原始類別。
如何提高 Windows 中的網際網路速度實踐中的連結點
這就是在不同工具中定義和使用連接點的方式。 注意每個環境如何公開執行點資訊。 對建議作出反應。
// AspectJ: capturar la ejecución de todos los métodos de MyClass
public aspect LoggingAspect {
before(): execution(* MyClass.*(..)) {
System.out.println("Antes de llamar al método...");
}
after(): execution(* MyClass.*(..)) {
System.out.println("Después de llamar al método...");
}
}
// Spring AOP: pointcut + JoinPoint para detalles de la invocación
@Aspect
public class LoggingAspect {
@Pointcut("execution(* com.example.MyClass.*(..))")
private void loggableMethods() {}
@Before("loggableMethods()")
public void beforeLog(JoinPoint joinPoint) {
System.out.println("Antes de: " + joinPoint.getSignature().getName());
}
@After("loggableMethods()")
public void afterLog(JoinPoint joinPoint) {
System.out.println("Después de: " + joinPoint.getSignature().getName());
}
}
// PostSharp: advices en los límites del método (entrada/salida)
[Serializable]
public class LoggingAspect : OnMethodBoundaryAspect {
public override void OnEntry(MethodExecutionArgs args) {
Console.WriteLine($"Antes de: {args.Method.Name}");
}
public override void OnExit(MethodExecutionArgs args) {
Console.WriteLine($"Después de: {args.Method.Name}");
}
}
// AspectC++: before/after sobre un método concreto
aspect LoggingAspect {
advice execution(".* MyClass::loggableMethod(..)") : before() {
std::cout << "Antes de loggableMethod..." << std::endl;
}
advice execution(".* MyClass::loggableMethod(..)") : after() {
std::cout << "Después de loggableMethod..." << std::endl;
}
};
# Python + AspectLib: aspecto como generador alrededor de la llamada
import aspectlib
@aspectlib.Aspect
def logging_aspect(*args, **kwargs):
print("Antes de llamar al método...")
yield
print("Después de llamar al método...")
with logging_aspect:
my_instance.loggable_method()
本質是一樣的:識別執行點並應用額外的行為,而無需觸及目標方法。
建議:類型和範例
建議是行為的核心。 常見類型:before、after、after returning、after throwing 和 around。後者可以決定是否執行原始方法或模擬其結果, 非常適合事務或緩存.
// AspectJ: before/after en todos los métodos de MyClass
public aspect LoggingAspect {
before(): execution(* MyClass.*(..)) { System.out.println("Antes..."); }
after(): execution(* MyClass.*(..)) { System.out.println("Después..."); }
}
// Spring AOP: before/after anotados
@Aspect
public class LoggingAspect {
@Pointcut("execution(* com.example.MyClass.*(..))")
private void loggableMethods() {}
@Before("loggableMethods()")
public void beforeLog(JoinPoint j) { System.out.println("Antes de: " + j.getSignature().getName()); }
@After("loggableMethods()")
public void afterLog(JoinPoint j) { System.out.println("Después de: " + j.getSignature().getName()); }
}
// PostSharp: entrada/salida como advices implícitos
[Serializable]
public class LoggingAspect : OnMethodBoundaryAspect {
public override void OnEntry(MethodExecutionArgs a) { Console.WriteLine($"Antes: {a.Method.Name}"); }
public override void OnExit(MethodExecutionArgs a) { Console.WriteLine($"Después: {a.Method.Name}"); }
}
// AspectC++: before/after con expresiones de ejecución
aspect LoggingAspect {
advice execution(".* MyClass::loggableMethod(..)") : before() { /* ... */ }
advice execution(".* MyClass::loggableMethod(..)") : after() { /* ... */ }
};
# Python + AspectLib: advice alrededor
import aspectlib
@aspectlib.Aspect
def around_log(*args, **kwargs):
print("Preparando...")
yield
print("Finalizado")
在所有情況下, 該委員會概括了橫向邏輯 並且僅在定義的截止點處啟動。
切入點:選擇行動點數
切入點表達式確定 適用某方面的情況它們依賴執行模式、簽名和套件,並且可以組合。 讓我們看看典型的變體:
// AspectJ: pointcut nombrado reutilizable
public aspect LoggingAspect {
pointcut loggableMethods(): execution(* MyClass.*(..));
before(): loggableMethods() { System.out.println("Antes..."); }
after(): loggableMethods() { System.out.println("Después..."); }
}
// Spring AOP: método anotado como pointcut
@Aspect
public class LoggingAspect {
@Pointcut("execution(* com.example.MyClass.*(..))")
private void loggableMethods() {}
@Before("loggableMethods()")
public void beforeLog(JoinPoint jp) { /* ... */ }
}
// AspectC++: punto de corte con expresión de ejecución
aspect LoggingAspect {
pointcut loggableMethods(): execution(".* MyClass::loggableMethod(..)");
advice loggableMethods() : before() { /* ... */ }
advice loggableMethods() : after() { /* ... */ }
};
# Python + AspectLib: ejemplo conceptual de pointcut
import aspectlib
@aspectlib.Pointcut("call(* MyClass.loggable_method(..))")
def loggable_methods():
pass
切入點允許 精確控制撞擊面 從一個方面來說,這是避免意外的關鍵。
如何在筆記型電腦上設定顯示器編織:當方面被整合時
面料是 將方面合併到程式碼庫的過程. 它可以發生在編譯、後編譯(二進位)、載入或執行時。 每個平台都以自己的方式實現它。:
// AspectJ: tejido en compilación con ajc
// MyClass.java
public class MyClass { public void myMethod() { System.out.println("En el método principal..."); } }
// LoggingAspect.aj
public aspect LoggingAspect {
pointcut loggableMethods(): execution(* MyClass.*(..));
before(): loggableMethods() { System.out.println("Antes..."); }
after(): loggableMethods() { System.out.println("Después..."); }
}
// Compilación con tejido
ajc MyClass.java LoggingAspect.aj
在 Spring AOP 中,編織通常在 使用代理的運行時。容器包裹豆子並在適當的時候應用建議, 無需修改字節碼.
// Spring AOP: aspectos aplicados vía proxies dinámicos
@Aspect
public class LoggingAspect {
@Before("execution(* com.example.MyClass.*(..))")
public void beforeLog(JoinPoint jp) { /* ... */ }
@After("execution(* com.example.MyClass.*(..))")
public void afterLog(JoinPoint jp) { /* ... */ }
}
在 PostSharp (.NET) 中,編織已完成 正在編譯:編譯器將方面程式碼注入到產生的程式集中。在 AspectC++ 中也會發生同樣的事情: 面料在建造過程中得到解決,使最終的二進位檔案具有整合行為。
Spring 中的 AOP
在 Spring 中啟用 AOP 簡單而明確。 如果您使用 XML,只需啟用 AspectJ 的自動代理。 並具有正確的依賴關係;從那裡,註釋的方面被應用到候選 bean。
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
除了日誌記錄之外,AOP 還用於 安全、事務、快取、異常處理 以及其他重複性任務。 Spring 也整合了以下機制: 「隱式」 AOP 該框架的幾個特點。
Java 中使用 AspectJ 的實作範例
想看到潛力,沒有什麼比「你好,世界」的表情更好了。 以下程式碼片段說明了之前、周圍和記錄成功/錯誤您可以使用 ajc 進行建置或設定 Maven/Gradle 來為您完成此操作。
範例 1:列印前自動問候語
// Clase principal
public class Main {
public static void main(String[] args) {
printName("Tanner");
printName("Victor");
printName("Sasha");
}
public static void printName(String name) { System.out.println(name); }
}
// Aspecto (.aj) que añade el saludo antes del método
public aspect GreetingAspect {
pointcut greeting() : execution(* Main.printName(..));
before() : greeting() { System.out.print("Hola, "); }
}
輸出將顯示 每次呼叫前註入問候語,不改變原有方法。
範例 2:使用 @Around 的“偽交易”
public class Main {
public static void main(String[] args) { performSomeOperation("Tanner"); }
public static void performSomeOperation(String clientName) {
System.out.println("Ejecutando operaciones para el cliente " + clientName);
}
}
@Aspect
public class TransactionAspect {
@Pointcut("execution(* Main.performSomeOperation(String))")
public void executeOperation() {}
@Around("executeOperation()")
public Object wrap(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("Abriendo transacción...");
try {
Object out = pjp.proceed();
System.out.println("Cerrando transacción...");
return out;
} catch (Throwable t) {
System.out.println("Fallo en la operación. Haciendo rollback...");
throw t;
}
}
}
周圍的建議 決定是否以及何時執行目標方法,並允許您將其與基礎設施邏輯(交易、指標等)包裝在一起。
範例 3:成功和錯誤日誌
public class Main {
private String value;
public static void main(String[] args) throws Exception {
Main m = new Main();
m.setValue("
String v = m.getValue();
m.checkValue(v);
}
public void setValue(String v) { this.value = v; }
public String getValue() { return this.value; }
public void checkValue(String v) throws Exception { if (v.length() > 10) throw new Exception(); }
}
@Aspect
public class LogAspect {
@Pointcut("execution(* *(..))")
public void methodExecuting() {}
@AfterReturning(value = "methodExecuting()", returning = "ret")
public void ok(JoinPoint jp, Object ret) {
if (ret != null) {
System.out.printf("OK: %s en %s, retorno=%s\n",
jp.getSignature().getName(), jp.getSourceLocation().getWithinType().getName(), ret);
} else {
System.out.printf("OK: %s en %s\n",
jp.getSignature().getName(), jp.getSourceLocation().getWithinType().getName());
}
}
@AfterThrowing(value = "methodExecuting()", throwing = "ex")
public void ko(JoinPoint jp, Exception ex) {
System.out.printf("ERROR: %s en %s, ex=%s\n",
jp.getSignature().getName(), jp.getSourceLocation().getWithinType().getName(), ex);
}
}
在這裡,我們保留核心清潔並委託給方面。 集中記錄成功與失敗,對於可觀察性非常有用。
修復無效文件中的異常錯誤優點和缺點(優點和缺點)
與任何範例一樣,AOP 有其優點和缺點。 選擇合適的應用位置 標誌著清晰與混亂之間的差異。
優點: 模組化橫切關注點,清理領域程式碼,加速大型團隊和項目,與其他範式結合, 促進重複使用.
更多好處: 可以輕鬆地將一致的策略(安全性、快取、指標)應用於新舊方, 減少遺漏錯誤 並允許您透過配置啟動/停用機制。
缺點: 可以產生遠端操作的反模式, 很難跟上潮流 如果它被濫用,那麼決定它在哪裡表現最佳並不總是那麼簡單。
常見用途和良好做法
典型用例: 日誌記錄、稽核、安全、事務管理、異常處理、快取策略以及指標/遙測。保持切入點清晰且有文件記錄。 避免過於籠統的表達 他們編織了“半個世界”,並準備測試來驗證他們的編織行為。
在 Spring 中,僅啟動必要的內容並控制方面的範圍。 在 AspectJ 和 AspectC++ 中檢查 fabric 對編譯的影響,並啟用警告(Xlint)以避免忽略意外的配對。在 .NET 中,PostSharp 可以很好地整合到建置管道中; 記錄屬性 應用。
啟動和工具
對於 AspectJ,您可以使用編譯器 AJC 或將 fabric 與 Maven/Gradle 整合。通常使用以下方式配置插件 complianceLevel、source/target、showWeaveInfo、verbose、Xlint 和編碼,並新增運行時依賴項(例如,aspectjrt 1.9.5)。在 IDE 中,請確保將編譯器路徑設定為 正確建立 或讓建置插件完成工作。
Spring AOP 與代理商一起工作: 您啟用 並註釋你的方面。在 .NET 中,PostSharp 注入編譯;在 C++ 中,AspectC++ 在編譯時織入。有 AspectLib 的 Python 適用 裝飾器/生成器 調用,對於原型或實用程式很有用。
有用的術語澄清
有時,會出現一些可能產生誤導的鄰近概念,例如 查詢語言 人工智能 審問 數據庫 或知識系統。它不是AOP,也不與AOP競爭; 它只是屬於另一層 (查詢/推理),而 AOP 解決程式執行中橫切關注點的模組化。
如果您已經做到了這一步,那麼您已經擁有了完整的地圖: 什麼是 AOP,它的組成部分(面向、連結點、建議、切入點)、如何在不同時間進行編織、成員的介紹,以及 Java、Spring、.NET、C++ 和 Python 中的實際範例。借助這些工具,您可以明智地決定何時最好隔離那些重複性任務,並 恢復業務代碼的清晰度.
艾薩克對字節世界和一般技術充滿熱情的作家。我喜歡透過寫作分享我的知識,這就是我在這個部落格中要做的,向您展示有關小工具、軟體、硬體、技術趨勢等的所有最有趣的事情。我的目標是幫助您以簡單有趣的方式暢遊數位世界。