摘要:我們會寫切面來攔截對這些業務類和類的調用。切面定義何時攔截一個方法以及做什么和在一起成為切面連接點當代碼開始執行,并且切點的條件滿足時,通知被調用。
前言
這篇文章會幫助你使用Spring Boot Starter AOP實現AOP。我們會使用AspectJ實現四個不同的通知(advice),并且新建一個自定義的注解來追蹤方法的執行時間。
你將會了解什么是交叉分割關注點(cross-cutting concern)?
在應用中你如何實現交叉分割關注點?
- 如果你想要將對web應用所有的訪問請求記入日志,你能想到什么方法? - 如果你想追蹤每個請求的性能,你能想到什么方法?
AOP中的切面(Aspects)和切點(Pointcut)是什么?
有哪些不同類型的AOP通知(advice)?
如何使用Spring Boot實現AOP?
如何使用Spring AOP和AspectJ實現切面?
有哪些AOP最佳實踐?
項目代碼結構下圖是我們即將創建的項目結構的截圖:
一些細節:
SpringBootTutorialBasicsApplication.java: 由Spring Initializer初始化生成的Spring Boot應用類。這個類是應用啟動類。
pom.xml: 創建項目所需的全部依賴。我們將會使用Spring Boot Starter AOP依賴。
Business1.java, Business2.java, Dao1.java, Dao2.java: 業務類依賴于Dao類。我們會寫切面來攔截對這些業務類和DAO類的調用。
AfterAopAspect.java: 實現一些After通知。
UserAccessAspect.java: 實現一個Before通知,用來做訪問權限檢查
BusinessAopSpringBootTest.java:對業務方法進行單元測試
Maven3.0+:編譯工具
Eclipse: 開發工具
JDK1.8+
源碼Github地址介紹AOP
應用通常劃分為多個層進行開發,一個經典的JAVA應用有:
網絡層:用REST或是應用的形式將服務暴露給外部使用
業務層:業務邏輯
數據層:數據持久化邏輯
雖然各個層的職責不同,但是每個層之間也有一些共通的地方
日志
安全
這些共通的切面成為交叉分割關注點(cross-cutting-concerns)
實現交叉分割關注點的一個方法是在每一個層分貝進行實現。但是,這樣會使得代碼難以維護。
面向切面編程為實現交叉分割關注點提供了一個解決方案:
將交叉分割切入點實現為一個切面
定義切點,說明這些切面在何時調用
這樣確保了交叉分割關注點定義在一個內聚的代碼組件中,并且能夠在需要的時候使用。
初始化Spring Boot AOP項目使用Spring Initializer新建一個Spring AOP項目非常的方法。
Spring Initializer是創建Spring Boot項目的超級棒的工具。
備注:
啟動Spring Initializer并且選擇一下內容
選擇com.in28minutes.springboot.tutorial.basics.example為Group
選擇 spring-boot-tutorial-basics為Artifact
選擇AOP依賴
點擊Generate Project
將項目導入Eclipse
Spring Boot AOP starterSpring Boot AOP Starter的關鍵依賴有:
Spring AOP提供的基本的AOP功能
AspectJ提供的完整的AOP框架
配置AOPorg.springframework spring-aop 5.0.1.RELEASE compile org.aspectj aspectjweaver 1.8.12 compile
讓我們添加一些業務邏輯類 - Business1和Business2。這些業務邏輯類依賴于一組數據類 - Data1和Data2。
@Service public class Business1 { private Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired private Dao1 dao1; public String calculateSomething() { String value = dao1.retrieveSomething(); logger.info("In Business - {}", value); return value; } }
@Service public class Business2 { @Autowired private Dao2 dao2; public String calculateSomething() { //Business Logic return dao2.retrieveSomething(); } }
@Repository public class Dao1 { public String retrieveSomething() { return "Dao1"; } }
@Repository public class Dao2 { public String retrieveSomething() { return "Dao2"; } }
備注:
@Autowired private Dao1 dao1: DAO作為依賴注入業務類中
public String calculateSomething(): 每個業務類包含一個簡單的calculate方法
一個簡單的AOP單元測試讓我們寫一個簡單的單元測試來調用剛剛創建的業務類:
@RunWith(SpringRunner.class) @SpringBootTest public class BusinessAopSpringBootTest { private Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired private Business1 business1; @Autowired private Business2 business2; @Test public void invokeAOPStuff() { logger.info(business1.calculateSomething()); logger.info(business2.calculateSomething()); } }
備注:
@RunWith(SpringRunner.class) @SpringBootTest public class BusinessAopSpringBootTest:: 我們將在單元測試用啟動一個完整的Spring Boot應用
@Autowired private Business1 business1和@Autowiredprivate Business2 business2: 將業務類注入啟動測試的Spring上下文中
@Test public void invokeAOPStuff(){...}: 調用業務層的方法
這時,我們沒有實現任何的AOP邏輯,因此,測試的輸出應該就是從DAO類和業務類中返回的簡單的信息:
c.i.s.t.b.e.a.BusinessAopSpringBootTest : In Business - Dao1 c.i.s.t.b.e.a.BusinessAopSpringBootTest : Dao1實現@Before通知
通常來講,當我們使用AOP來實現安全時,我們會想要攔截對方法的調用并進行檢查。這可以直接通過@Before通知實現。
下面給出了一種實現:
@Aspect @Configuration public class UserAccessAspect { private Logger logger = LoggerFactory.getLogger(this.getClass()); //What kind of method calls I would intercept //execution(* PACKAGE.*.*(..)) //Weaving & Weaver @Before("execution(* com.in28minutes.springboot.tutorial.basics.example.aop.data.*.*(..))") public void before(JoinPoint joinPoint) { //Advice logger.info(" Check for user access "); logger.info(" Allowed execution for {}", joinPoint); } }
備注:
@Aspect: 說明這是一個切面
@Configuration: 說明這是一個對切面的Spring Bean配置
@Before: 我們想要在方法執行前執行切面
("execution(* com.in28minutes.springboot.tutorial.basics.example.aop.data.*.*(..))": 定義了切點。我們想要攔截com.in28minutes.springboot.tutorial.basics.example.aop.data包中的所有方法。
當我們運行單元測試時,你會看見,在執行DAO方法之前,會執行用戶權限檢查:
Check for user access Allowed execution for execution(String com.in28minutes.springboot.tutorial.basics.example.aop.data.Dao1.retrieveSomething()) c.i.s.t.b.e.a.BusinessAopSpringBootTest : In Business - Dao1 c.i.s.t.b.e.a.BusinessAopSpringBootTest : Dao1 Check for user access Allowed execution for execution(String com.in28minutes.springboot.tutorial.basics.example.aop.data.Dao2.retrieveSomething()) c.i.s.t.b.e.a.BusinessAopSpringBootTest : Dao2理解AOP術語: Pointcut, Advice, Aspect,Join Point
讓我們花點時間來了解一下AOP術語:
切點(Pointcut):該表達式用來定義何時方法應當被攔截。在上例中,切點為`execution(* com.in28minutes.springboot.tutorial.basics.
example.aop.data..(..))`。
通知(Advice):你想要做什么?一個通知是你在攔截方法時想要調用的邏輯。在上例中,通知為before(JoinPoint joinPoint)方法中的代碼。
切面(Aspect):定義何時攔截一個方法(Pointcut)以及做什么(Advice)和在一起成為切面
連接點(Join Point):當代碼開始執行,并且切點的條件滿足時,通知被調用。連接點是一個通知運行的特定實例。
織如(Weaver):實現AOP的框架 - AspectJ或Spring AOP
使用@After, @AfterReturning和@AfterThrowing通知讓我們現在來看看AOP提供的別的攔截選項:
@After: 在兩種場景下執行 - 當一個方法成功執行或是拋出異常
@AfterReturning: 只有在方法成功執行后運行
@AfterThrowing: 只有在方法拋出異常后運行
讓我們創建一個包含這些元素的簡單的切面:
@Aspect @Configuration public class AfterAopAspect { private Logger logger = LoggerFactory.getLogger(this.getClass()); @AfterReturning(value = "execution(* com.in28minutes.springboot.tutorial.basics.example.aop.business.*.*(..))", returning = "result") public void afterReturning(JoinPoint joinPoint, Object result) { logger.info("{} returned with value {}", joinPoint, result); } @After(value = "execution(* com.in28minutes.springboot.tutorial.basics.example.aop.business.*.*(..))") public void after(JoinPoint joinPoint) { logger.info("after execution of {}", joinPoint); } }
執行后運行結果如下所示:
Check for user access Allowed execution for execution(String com.in28minutes.springboot.tutorial.basics.example.aop.data.Dao1.retrieveSomething()) In Business - Dao1 after execution of execution(String com.in28minutes.springboot.tutorial.basics.example.aop.business.Business1.calculateSomething()) execution(String com.in28minutes.springboot.tutorial.basics.example.aop.business.Business1.calculateSomething()) returned with value Dao1 c.i.s.t.b.e.a.BusinessAopSpringBootTest : Dao1 Check for user access Allowed execution for execution(String com.in28minutes.springboot.tutorial.basics.example.aop.data.Dao2.retrieveSomething()) after execution of execution(String com.in28minutes.springboot.tutorial.basics.example.aop.business.Business2.calculateSomething()) execution(String com.in28minutes.springboot.tutorial.basics.example.aop.business.Business2.calculateSomething()) returned with value Dao2 c.i.s.t.b.e.a.BusinessAopSpringBootTest : Dao2
可以看到,就在將值返回給調用的業務邏輯之前,after通知被執行了。
其它AOP功能:@Around和注解能夠使用AOP實現的功能之一是通過自定義注釋來解析方法調用。
下面的例子展示了一個簡單的TrackTiem注釋:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface TrackTime {
我們可以添加一個切面來定義當添加TrackTime注解以后執行的邏輯。MethodExecutionCalculationAspect實現了一個簡單的時間追蹤功能。
@Aspect @Configuration public class MethodExecutionCalculationAspect { private Logger logger = LoggerFactory.getLogger(this.getClass()); @Around("@annotation(com.in28minutes.springboot.tutorial.basics.example.aop.TrackTime)") public void around(ProceedingJoinPoint joinPoint) throws Throwable { long startTime = System.currentTimeMillis(); joinPoint.proceed(); long timeTaken = System.currentTimeMillis() - startTime; logger.info("Time Taken by {} is {}", joinPoint, timeTaken); } }
備注:
@Around: 是一個環繞型通知。它攔截方法調用后使用joinPoint.proceed()來執行方法
@annotation(com.in28minutes.springboot.tutorial.basics.example.aop.TrackTime): 基于注解進行攔截的切點 - @annotation緊跟著完整的注解的名稱
定義了注解和通知之后,我們可以將注解運用到想要跟蹤的方法上,如下所示:
@Service public class Business1 { @TrackTime public String calculateSomething(){AOP最賤實踐
AOP最佳實踐之一是將所有的切點定義在一個類中。這樣有利于在一個地方維護所有的切點。
public class CommonJoinPointConfig { @Pointcut("execution(* com.in28minutes.spring.aop.springaop.data.*.*(..))") public void dataLayerExecution() {} @Pointcut("execution(* com.in28minutes.spring.aop.springaop.business.*.*(..))") public void businessLayerExecution() {} }
在定義其它切面的切入點時,可以這樣調用上面的定義:
@Around("com.in28minutes.spring.aop.springaop.aspect.CommonJoinPointConfig.businessLayerExecution()")
完整的代碼請前往GITHUB瀏覽
想要了解更多開發技術,面試教程以及互聯網公司內推,歡迎關注我的微信公眾號!將會不定期的發放福利哦~
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/76317.html
摘要:初次使用的人往往會困惑,不知道該使用哪種方法。目前來說,團隊推薦使用基于的方法來提供更高的靈活性。配置,從而在應用啟動時執行腳本來初始化數據庫。目前為止我們沒有任何消息需要配置,所以只在文件夾中創建一個空的文件。將配置為,它包含的上下文。 前言 spring是一個用于創建web和企業應用的一個很流行的框架。和別的只關注于一點的框架不同,Spring框架通過投資并組合項目提供了大量的功能...
摘要:前言這周我準備介紹一個有趣的但是很少使用的方法按照合約編程,又稱為合約編程,是一種軟件設計的方法。這些規則被稱為合約,可以比擬為商業合同中的條件和義務。通過將檢查和異常拋出指令包裝到方法中,人們可以很容易地實現合約式編程。 前言 這周我準備介紹一個有趣的但是很少使用的方法 按照合約編程,又稱為合約編程,是一種軟件設計的方法。它規定了軟件設計師應該為軟件組件定義正式,精確和可驗證的接口規...
摘要:本文簡介類概覽類構造器總結類構造方法類使用舉例類概覽是一個實現了接口,并且鍵為型的哈希表。中的條目不再被正常使用時,會被自動刪除。它的鍵值均支持。和絕大多數的集合類一樣,這個類不是同步的。 本文簡介 WeakHashMap類概覽 WeakHashMap類構造器總結 WeakHashMap類構造方法 WeakHasjMap類使用舉例 1. WeakHashMap類概覽 Wea...
摘要:什么是為執行字節碼提供一個運行環境。它的實現主要包含三個部分,描述實現規格的文檔,具體實現和滿足要求的計算機程序以及實例具體執行字節碼。該類先被轉化為一組字節碼并放入文件中。字節碼校驗器通過字節碼校驗器檢查格式并找出非法代碼。 什么是Java Development Kit (JDK)? JDK通常用來開發Java應用和插件。基本上可以認為是一個軟件開發環境。JDK包含Java Run...
摘要:否則它就會用新的值替代當前值。在這種情況下,鎖可能會優于原子變量,但在實際的爭用級別中,原子變量的性能優于鎖。在中引入了另外一個構件。 題目要求 在我們深入了解CAS(Compare And Swap)策略以及它是如何在AtomicInteger這樣的原子構造器中使用的,首先來看一下這段代碼: public class MyApp { private volatile int ...
閱讀 1679·2021-11-12 10:35
閱讀 1621·2021-08-03 14:02
閱讀 2691·2019-08-30 15:55
閱讀 2034·2019-08-30 15:54
閱讀 770·2019-08-30 14:01
閱讀 2433·2019-08-29 17:07
閱讀 2260·2019-08-26 18:37
閱讀 3039·2019-08-26 16:51