
應用AOP
面向切面編程旨在執行橫切任務,可以是任何可能通過不同方法重複多次的代碼,不能完全結構化到一個單獨的模塊中。因此,AOP允許我們將它保留在主代碼之外並垂直聲明它。一個例子是在應用程序中使用安全策略。通常,安全貫穿應用程序的許多元素。更重要的是,應用程序的安全策略應該同樣適用於應用程序的所有現有部分和新部分。同時,正在使用的安全策略本身可以發展。這是使用AOP 的最佳場所。另外,另一個例子是記錄. 使用 AOP 方法進行日誌記錄而不是手動添加日誌記錄功能有幾個優點:-
日誌記錄的代碼很容易添加和刪除:您需要做的就是添加或刪除某些方面的一些配置。
-
所有用於日誌記錄的源代碼都保存在一個地方,因此您無需手動搜索所有使用它的地方。
-
可以在任何地方添加日誌記錄代碼,無論是在已經編寫的方法和類中還是在新功能中。這減少了編碼錯誤的數量。
此外,當從設計配置中刪除一個方面時,您可以確定所有跟踪代碼都已消失並且沒有遺漏任何內容。
- 方面是獨立的代碼,可以改進並一次又一次地使用。

AOP的基本原理
為了進一步探討這個主題,讓我們首先了解一下 AOP 的主要概念。 建議——從連接點調用的附加邏輯或代碼。建議可以在連接點之前、之後或代替連接點執行(下面將詳細介紹)。可能的建議類型:-
之前——這種類型的建議在目標方法(即連接點)執行之前啟動。將方面用作類時,我們使用@Before註釋將通知標記為之前出現。將方面用作.aj文件時,這將是before()方法。
- After — 在方法(連接點)執行完成後執行的建議,無論是在正常執行還是在拋出異常時。
將方面用作類時,我們可以使用@After註釋來指示這是之後的建議。
將方面用作.aj文件時,這是after()方法。
-
返回後——只有當目標方法正常完成且沒有錯誤時才執行此建議。
當方面表示為類時,我們可以使用@AfterReturning批註將通知標記為在成功完成後執行。
將方面用作.aj文件時,這將是after() 返回 (Object obj)方法。
-
拋出後——此建議適用於方法(即連接點)拋出異常的情況。我們可以使用此建議來處理某些類型的失敗執行(例如,回滾整個事務或具有所需跟踪級別的日誌)。
對於類方面,@AfterThrowing註釋用於指示在拋出異常後使用此通知。
將方面用作.aj文件時,這將是after() throwing (Exception e)方法。
-
周圍——也許是最重要的建議類型之一。它包含一個方法,即一個連接點,我們可以使用它來選擇是否執行給定的連接點方法。
您可以編寫在執行連接點方法之前和之後運行的建議代碼。
around advice負責調用連接點方法和返回值(如果該方法有返回值)。換句話說,在這個通知中,你可以簡單地模擬目標方法的運行而不調用它,並返回你想要的任何結果作為返回結果。
給定方面作為類,我們使用@Around註釋來創建包裝連接點的建議。當使用.aj文件形式的方面時,此方法將是around()方法。
-
編譯時編織——如果你有方面的源代碼和使用方面的代碼,那麼你可以直接使用 AspectJ 編譯器編譯源代碼和方面;
-
編譯後編織(二進制編織) ——如果您不能或不想使用源代碼轉換將方面編織到代碼中,您可以採用先前編譯的類或 jar 文件並將方面注入其中;
-
加載時編織——這只是二進制編織,延遲到類加載器加載類文件並為 JVM 定義類。
需要一個或多個編織類加載器來支持這一點。它們要么由運行時顯式提供,要么由“編織代理”激活。
Java 中的示例
接下來,為了更好地理解AOP,我們將查看“Hello World”式的小示例。首先,我會注意到我們的示例將使用編譯時織入。首先,我們需要在pom.xml文件中添加以下依賴項:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.5</version>
</dependency>
通常,特殊的ajc編譯器是我們使用方面的方式。IntelliJ IDEA默認不包含它,因此在選擇它作為應用程序編譯器時,您必須指定 5168 75 AspectJ分發的路徑。這是第一種方式。第二種,也就是我使用的,是在pom.xml文件中註冊以下插件:
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.7</version>
<configuration>
<complianceLevel>1.8</complianceLevel>
<source>1.8</source>
<target>1.8</target>
<showWeaveInfo>true</showWeaveInfo>
<<verbose>true<verbose>
<Xlint>ignore</Xlint>
<encoding>UTF-8</encoding>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
在此之後,最好從Maven重新導入並運行mvn clean compile。現在讓我們直接進入示例。
示例 1
讓我們創建一個主類。在其中,我們將有一個入口點和一個在控制台上打印傳遞名稱的方法:
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);
}
}
這裡沒有什麼複雜的。我們傳遞了一個名稱並將其顯示在控制台上。如果我們現在運行該程序,我們將在控制台上看到以下內容:
public aspect GreetingAspect {
pointcut greeting() : execution(* Main.printName(..));
before() : greeting() {
System.out.print("Hi, ");
}
}
這個文件有點像一個類。讓我們看看這裡發生了什麼: 切入點是一組連接點; greeting()是這個切入點的名稱; : execution指示在執行Main.printName(...)方法的所有 ( * )調用期間應用它。接下來是一個特定的建議 — before() — 在調用目標方法之前執行。: greeting()是這個通知響應的切點。好吧,下面我們看到了方法本身的主體,它是用我們理解的 Java 語言編寫的。當我們在這個方面存在的情況下運行main時,我們將得到這個控制台輸出:
@Aspect
public class GreetingAspect{
@Pointcut("execution(* Main.printName(String))")
public void greeting() {
}
@Before("greeting()")
public void beforeAdvice() {
System.out.print("Hi, ");
}
}
在.aj方面文件 之後,這裡的一切都變得更加明顯:
- @Aspect表示這個類是一個切面;
- @Pointcut("execution(* Main.printName(String))")是所有調用Main.printName時觸發的切點,輸入參數類型為String;
- @Before("greeting()")是在調用greeting()切點中指定的代碼之前應用的建議。
示例 2
假設我們有一些方法可以為客戶端執行一些操作,我們從main調用這個方法:
public class Main {
public static void main(String[] args) {
performSomeOperation("Tanner");
}
public static void performSomeOperation(String clientName) {
System.out.println("Performing some operations for Client " + clientName);
}
}
讓我們使用@Around註解來創建一個“偽事務”:
@Aspect
public class TransactionAspect{
@Pointcut("execution(* Main.performSomeOperation(String))")
public void executeOperation() {
}
@Around(value = "executeOperation()")
public void beforeAdvice(ProceedingJoinPoint joinPoint) {
System.out.println("Opening a transaction...");
try {
joinPoint.proceed();
System.out.println("Closing a transaction...");
}
catch (Throwable throwable) {
System.out.println("The operation failed. Rolling back the transaction...");
}
}
}
通過ProceedingJoinPoint對象的proceed方法,我們調用 wrapping 方法來確定它在通知中的位置。因此,上述方法中的代碼joinPoint.proceed(); 是Before,而下面的代碼是After。如果我們運行main,我們會在控制台中看到:
public static void performSomeOperation(String clientName) throws Exception {
System.out.println("Performing some operations for Client " + clientName);
throw new Exception();
}
然後我們得到這個控制台輸出:
例 3
在我們的下一個示例中,讓我們做一些類似登錄到控制台的事情。首先,看一下Main,我們在其中添加了一些偽業務邏輯:
public class Main {
private String value;
public static void main(String[] args) throws Exception {
Main main = new Main();
main.setValue("<some value>");
String valueForCheck = main.getValue();
main.checkValue(valueForCheck);
}
public void setValue(String value) {
this.value = value;
}
public String getValue() {
return this.value;
}
public void checkValue(String value) throws Exception {
if (value.length() > 10) {
throw new Exception();
}
}
}
在main中,我們使用setValue為value實例變量賦值。然後我們使用getValue獲取值,然後調用checkValue看是否超過10個字符。如果是這樣,那麼將拋出異常。現在讓我們看看我們將用來記錄方法工作的方面:
@Aspect
public class LogAspect {
@Pointcut("execution(* *(..))")
public void methodExecuting() {
}
@AfterReturning(value = "methodExecuting()", returning = "returningValue")
public void recordSuccessfulExecution(JoinPoint joinPoint, Object returningValue) {
if (returningValue != null) {
System.out.printf("Successful execution: method — %s method, class — %s class, return value — %s\n",
joinPoint.getSignature().getName(),
joinPoint.getSourceLocation().getWithinType().getName(),
returningValue);
}
else {
System.out.printf("Successful execution: method — %s, class — %s\n",
joinPoint.getSignature().getName(),
joinPoint.getSourceLocation().getWithinType().getName());
}
}
@AfterThrowing(value = "methodExecuting()", throwing = "exception")
public void recordFailedExecution(JoinPoint joinPoint, Exception exception) {
System.out.printf("Exception thrown: method — %s, class — %s, exception — %s\n",
joinPoint.getSignature().getName(),
joinPoint.getSourceLocation().getWithinType().getName(),
exception);
}
}
這裡發生了什麼? @Pointcut("execution(* *(..))")將加入所有方法的所有調用。 @AfterReturning(value = "methodExecuting()", returning = "returningValue")是將在成功執行目標方法後執行的通知。我們這裡有兩種情況:
- 當方法有返回值時——if (returningValue!= Null) {
- 當沒有返回值時——else {

GO TO FULL VERSION