更新時間:2022-06-13 09:33:48 來源:動力節(jié)點 瀏覽1370次
在本文中,我們將使用 Spring 中的 AOP 支持來實現(xiàn)自定義 AOP 注釋。
首先,我們將給出 AOP 的高級概述,解釋它是什么以及它的優(yōu)點。在此之后,我們將逐步實現(xiàn)我們的注解,逐步建立對 AOP 概念的更深入理解。
結(jié)果將是對 AOP 的更好理解以及未來創(chuàng)建自定義 Spring 注釋的能力。
快速總結(jié)一下,AOP 代表面向方面的編程。從本質(zhì)上講,它是一種無需修改代碼即可向現(xiàn)有代碼添加行為的方法。
關(guān)于 AOP 的詳細介紹,有關(guān)于 AOP切入點和建議的文章。本文假設(shè)我們已經(jīng)具備基本知識。
我們將在本文中實現(xiàn)的 AOP 類型是注解驅(qū)動的。如果我們使用過 Spring @Transactional注解,我們可能已經(jīng)對此很熟悉了:
@Transactional
public void orderGoods(Order order) {
// A series of database calls to be performed in a transaction
}
這里的關(guān)鍵是非侵入性。通過使用注釋元數(shù)據(jù),我們的核心業(yè)務(wù)邏輯不會被我們的事務(wù)代碼污染。這使得推理、重構(gòu)和隔離測試變得更容易。
有時,開發(fā) Spring 應(yīng)用程序的人可以將其視為“ Spring Magic”,而無需詳細考慮它是如何工作的。實際上,發(fā)生的事情并不是特別復(fù)雜。但是,一旦我們完成了本文中的步驟,我們將能夠創(chuàng)建自己的自定義注解,以便理解和利用 AOP。
首先,讓我們添加我們的Maven依賴機制。
對于這個例子,我們將使用 Spring Boot,因為它的約定優(yōu)于配置的方法讓我們能夠盡快啟動并運行:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
請注意,我們已經(jīng)包含了 AOP 啟動器,它引入了我們開始實現(xiàn)方面所需的庫。
我們要創(chuàng)建的注釋將用于記錄方法執(zhí)行所需的時間。讓我們創(chuàng)建我們的注解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
}
盡管實現(xiàn)相對簡單,但值得注意的是這兩個元注釋的用途。
@Target注解告訴我們注解適用的地方。 這里我們使用的是ElementType.Method,這意味著它只適用于方法。如果我們嘗試在其他任何地方使用注解,那么我們的代碼將無法編譯。這種行為是有道理的,因為我們的注釋將用于記錄方法執(zhí)行時間。
@Retention只是說明注解在運行時是否對 JVM 可用。默認情況下它不是,所以 Spring AOP 將無法看到注解。這就是它被重新配置的原因。
現(xiàn)在我們有了注釋,讓我們創(chuàng)建我們的方面。這只是將封裝我們的橫切關(guān)注點的模塊,在我們的例子中是方法執(zhí)行時間日志記錄。它只是一個類,用@Aspect注釋:
@Aspect
@Component
public class ExampleAspect {
}
我們還包含了@Component 注釋,因為我們的類也需要是一個 Spring bean 才能被檢測到。本質(zhì)上,這是我們將實現(xiàn)我們希望自定義注解注入的邏輯的類。
現(xiàn)在,讓我們創(chuàng)建我們的切入點和建議。這將是一個存在于我們方面的帶注釋的方法:
@Around("@annotation(LogExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
return joinPoint.proceed();
}
從技術(shù)上講,這并沒有改變?nèi)魏螙|西的行為,但是仍然有很多事情需要分析。
首先,我們用@Around注釋了我們的方法。這是我們的建議,圍繞建議意味著我們在方法執(zhí)行之前和之后添加額外的代碼。還有其他類型的建議,例如之前和之后,但它們將超出本文的范圍。
接下來,我們的@Around注釋有一個切入點參數(shù)。我們的切入點只是說,“將此建議應(yīng)用于任何帶有@LogExecutionTime注釋的方法。” 還有很多其他類型的切入點,但如果作用域,它們將再次被排除在外。
logExecutionTime()方法本身就是我們的建議。有一個參數(shù)ProceedingJoinPoint。在我們的例子中,這將是一個使用@LogExecutionTime 注釋的執(zhí)行方法。
最后,當(dāng)我們的注解方法最終被調(diào)用時,會發(fā)生的是我們的通知將首先被調(diào)用。然后由我們的建議決定下一步該做什么。在我們的例子中,我們的建議除了調(diào)用proceed()之外什么都不做,它只是調(diào)用原始的帶注釋的方法。
現(xiàn)在我們已經(jīng)有了我們的骨架,我們需要做的就是在我們的建議中添加一些額外的邏輯。除了調(diào)用原始方法之外,這將是記錄執(zhí)行時間的內(nèi)容。讓我們將這個額外的行為添加到我們的建議中:
@Around("@annotation(LogExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object proceed = joinPoint.proceed();
long executionTime = System.currentTimeMillis() - start;
System.out.println(joinPoint.getSignature() + " executed in " + executionTime + "ms");
return proceed;
}
同樣,我們在這里沒有做任何特別復(fù)雜的事情。我們剛剛記錄了當(dāng)前時間,執(zhí)行了方法,然后將花費的時間打印到控制臺。我們還記錄了方法簽名,它是為使用連接點實例而提供的。如果我們愿意,我們還可以訪問其他信息,例如方法參數(shù)。
現(xiàn)在,讓我們嘗試用@LogExecutionTime 注釋一個方法,然后執(zhí)行它來看看會發(fā)生什么。請注意,這必須是 Spring Bean 才能正常工作:
@LogExecutionTime
public void serve() throws InterruptedException {
Thread.sleep(2000);
}
執(zhí)行后,我們應(yīng)該會看到控制臺記錄了以下內(nèi)容:
void org.baeldung.Service.serve() executed in 2030ms
以上就是關(guān)于“實現(xiàn)自定義Spring AOP注解”介紹,大家如果對此比較感興趣,想了解更多相關(guān)知識,不妨來關(guān)注一下動力節(jié)點的Spring教程,里面的課程內(nèi)容由淺到深,細致全面,很適合沒有基礎(chǔ)的小伙伴學(xué)習(xí),希望對大家能夠有所幫助哦。
初級 202925
初級 203221
初級 202629
初級 203743