目标:
1.什麼是AOP, 什麼是AspectJ,
2. 什麼是Spring AOP
3. Spring AOP注解版實作原理
4. Spring AOP切面原了解析
一. 認識AOP
1.1 什麼是AOP
aop是面向切面程式設計,相比傳統oop,aop能夠在方法的前置,中置,後置中插入邏輯代碼,對于項目中大量邏輯重複的代碼,使用aop能很好的收口邏輯,将邏輯獨立于業務代碼之外,一處編寫,多處使用。
AOP是Object Oriented Programming(OOP)的補充.
OOP能夠很好地解決對象的資料和封裝的問題,卻不能很好的解決Aspect("方面")分離的問題。下面舉例具體說明。
比如,我們有一個Bank(銀行)類。Bank有兩個方法,save(存錢)和withdraw(取錢)。
類和方法的定義如下:
package com.lxl.www.aop;
public class Bank {
/**
* 存錢
*/
public Float save(Account account, float money) {
// 增加account賬戶的錢數,傳回賬戶裡目前的錢數
return null;
}
/**
* 取錢
*/
public Float withdraw(Account account, float money) {
// 減少account賬戶的錢數,傳回取出的錢數
return null;
}
};
這兩個方法涉及到使用者的賬戶資金等重要資訊,必須要非常小心,是以編寫完上面的商業邏輯之後,項目負責人又提出了新的要求--給Bank類的每個重要方法加上安全認證特性。
于是, 我們在兩個方法上增加安全代碼
改後的類和方法如下:
public class Bank {
/**
* 存錢
*/
public Float save(Account account, float money) {
// 驗證account是否為合法使用者
// 增加account賬戶的錢數,傳回賬戶裡目前的錢數
return null;
}
/**
* 取錢
*/
public Float withdraw(Account account, float money) {
// 驗證account是否為合法使用者
// 減少account賬戶的錢數,傳回取出的錢數
return null;
}
};
這兩個方法都需要操作資料庫,為了保持資料完整性,項目負責人又提出了新的要求--給Bank類的每個操作資料庫的方法加上事務控制。
于是,我們不得不分别在上面的兩個方法中加入安全認證的代碼。
類和方法的定義如下:
package com.lxl.www.aop;
public class Bank {
/**
* 存錢
*/
public Float save(Account account, float money) {
// 驗證account是否為合法使用者
// begin Transaction
// 增加account賬戶的錢數,傳回賬戶裡目前的錢數
// end Transaction
return null;
}
/**
* 取錢
*/
public Float withdraw(Account account, float money) {
// 驗證account是否為合法使用者
// begin Transaction
// 減少account賬戶的錢數,傳回取出的錢數
// end Transaction
return null;
}
};
我們看到,這些與商業邏輯無關的重複代碼遍布在整個程式中。實際的工程項目中涉及到的類和函數,遠遠不止兩個。如何解決這種問題?
AOP就是為了解決這種問題而出現的。在不修改代碼的情況下達到增強的效果
1.2 AOP的相關概念
- 切面(Aspect): 封裝通用業務邏輯的元件,即我們想要插入的代碼内容. 在spring AOP中, 切面可以使用通用類基于模式的方式, 或者在普通類中标注@Aspect注解來實作
- 連接配接點(Join point): 連接配接點是在應用執行過程中能夠插入切面的點。簡單了解, 可以了解為需要增強的方法.
- 通知(Advice): 用于指定具體産生作用的位置,是方法之前或之後等等
- 前置通知(before) - 在目标方法被調用之前調用通知功能
- 後置通知(after) - 在目标方法完成之後調用通知(不論程式是否出現異常),此時不會關心方法的輸出是什麼
- 傳回通知(after-returning) - 在目标方法成功執行之後調用通知
- 異常通知(after-throwing) - 在目标方法抛出異常後調用通知
- 環繞通知(around) - 通知包裹了被通知的方法,在被通知的方法調用之前和調用之後執行自定義的行為
- 目标對象(target): 目标對象是指要被增強的對象, 即包含主業務邏輯的類對象
- 切點(PointCut): 指定哪些Bean元件的哪些方法使用切面元件. 例如:當執行某個特定名稱的方法時.我們定義一個切點(execution com.lxl.www.aop.*.*(..)) . 切點表達式如何和連接配接點比對是AOP的核心. spring預設使用AspectJ切點語義.
- 織入(Weaving): 将通知切入連接配接點的過程叫做織入
- 引入(Introductions): 可以将其它接口或者實作動态引入到targetClass中
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnL1ADM2YDMwYDNx0CO0QzMwYTM1AjNwITMwIDMy0iNxkzN4ETMvwlMxAjMwIzLcZTM5cDOxEzLcd2bsJ2Lc12bj5ycn9Gbi52YuAjMwIzZtl2Lc9CX6MHc0RHaiojIsJye.png)
對照上圖, 來對應每一個區域,看看其具體含義
那麼在Spring中使用AOP就意味着你需要哪些東西呢?我們來舉個例子, 就實作上面銀行的例子.
- 首先有一個bank銀行類
package com.lxl.www.aop.bank; public interface Bank { /** * 存錢 */ Float save(Account account, float money) ; /** * 取錢 */ Float withdraw(Account account, float money); };
- 有一個銀行類的實作方法. 這裡面save, withdraw就是連接配接點. 最終會将各種通知插入到連接配接點中
package com.lxl.www.aop.bank; import org.springframework.stereotype.Service; /** * 工商銀行 * * * DATE 2020/12/6. * * @author lxl. */ @Service public class IcbcBank implements Bank{ @Override public Float save(Account account, float money) { // 主業務邏輯: 增加account賬戶的錢數,傳回賬戶裡目前的錢數 System.out.println(account.getName() + "賬戶存入" + money); return null; } @Override public Float withdraw(Account account, float money) { // 主業務邏輯: 減少account賬戶的錢數,傳回取出的錢數 System.out.println(account.getName() + "賬戶取出" + money); return null; } }
- 接下來, 要有一個切面, 切面是一個類. 切面類裡面定義了切點, 通知, 引用
那麼這裡的目标對象是誰呢? 就是我們的IcbcBank類. 這裡需要注意的是引入: 引入的概念是将一個接口動态的讓另一個類實作了. 這樣實作了接口的類, 就可以動态的擁有接口實作類的功能.package com.lxl.www.aop.bank; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.DeclareParents; import org.aspectj.lang.annotation.Pointcut; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** * 切面 */ @Aspect // 标記這是一個切面 @Order @Component // 将其放到ioc容器管理 public class BankLogAspect { /** * 引入 * * 這段話可以了解為, 為com.lxl.www.aop.bank.IcbcBank 引入了一個接口 EnhanceFunctionOfBank, * 同時, 引入了預設的實作類 IcbcEnhanceFunctionOfBank */ @DeclareParents(value = "com.lxl.www.aop.bank.IcbcBank", // 引入的目标類. 也就是需要引入動态實作的類 defaultImpl = IcbcEnhanceFunctionOfBank.class) // 引入的接口的預設實作 public static EnhanceFunctionOfBank enhanceFunctionOfBank; // 引入的接口 /** * 定義一個切點 */ @Pointcut("execution(* com.lxl.www.aop.bank.IcbcBank.*(..))") public void pointCut() {} /** * 定義一個前置通知 * @param joinPoint */ @Before(value = "pointCut()") public void beforeAdvice(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); System.out.println("執行目标方法"+methodName+"的前置通知"); } /** * 定義了一個後置通知 */ @After(value = "pointCut()") public void afterAdvice(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); System.out.println("執行目标方法"+methodName+"的後置通知"); } /** * 定義了一個傳回通知 */ @AfterReturning(value = "pointCut()", returning = "result") public void returningAdvice(JoinPoint joinPoint, Object result) { String methodName = joinPoint.getSignature().getName(); System.out.println("執行目标方法"+methodName+"的傳回通知"); } /** * 定義了一個異常通知 */ @AfterThrowing(value = "pointCut()") public void throwingAdvice(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); System.out.println("執行目标方法"+methodName+"的異常通知"); } }
- 銀行的額外功能. 也就是銀行除了可以存錢, 取錢. 還有一個額外的功能. 比如理财. 不是每個銀行都有的.
package com.lxl.www.aop.bank; /** * 增強的功能 */ public interface EnhanceFunctionOfBank { void Financialanagement(Account account); }
- 具體銀行額外功能的實作類
這個功能我們可以通過引入,動态增加到IcbcBank類中, 原本IcbcBank隻有存錢和取錢的功能. 這樣, 就可以增加理财功能了. 這就是引入.package com.lxl.www.aop.bank; import org.springframework.stereotype.Service; /** * Description */ @Service public class IcbcEnhanceFunctionOfBank implements EnhanceFunctionOfBank { /** * 理财功能 * @param account */ @Override public void Financialanagement(Account account) { System.out.println(account.getName() +"的賬戶 增加 理财功能"); } }
- 整體配置類
使用aop,需要引入AOP, 這裡使用的注解的方式引入的.package com.lxl.www.aop.bank; import org.springframework.beans.factory.annotation.Configurable; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.EnableAspectJAutoProxy; @Configurable // 使用注解的方式引入AOP @EnableAspectJAutoProxy @ComponentScan("com.lxl.www.aop.bank") public class BankMainConfig { }
- main入口方法
如上, 運作結果:package com.lxl.www.aop.bank; import com.lxl.www.aop.Calculate; import com.lxl.www.aop.MainConfig; import com.lxl.www.aop.ProgramCalculate; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class BankMainClass { public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(BankMainConfig.class); Account account = new Account("張三"); Bank bank = (Bank) ctx.getBean("icbcBank"); bank.save(account, 100); System.out.println(); EnhanceFunctionOfBank enhanceFunctionOfBank = (EnhanceFunctionOfBank) ctx.getBean("icbcBank"); enhanceFunctionOfBank.Financialanagement(account); } }
- 需要注意的地方: 是.gradle配置檔案. 通常, 我們在引入AspectJ的jar包的時候, 會引入到父類項目的build.gradle中. 如下所示 最後我們還需要引入到指定的項目中
5.1 Spring5源碼--Spring AOP源碼分析一
以上就是對整個AOP的了解. 接下來, 分析AOP的源碼.
詳見第二篇文章
as