Spring AOP簡介
1. 定義
AOP是Aspect Oriented Programming,即面向切面程式設計。
那什麼是AOP?
我們先回顧一下OOP:Object Oriented Programming,OOP作為物件導向程式設計的模式,獲得了巨大的成功,OOP的主要功能是資料封裝、繼承和多型。
而AOP是一種新的程式設計方式,它和OOP不同,OOP把系統看作多個物件的互動,AOP把系統分解為不同的關注點,或者稱之為切面(Aspect)。
AOP在程式開發中主要用來解決一些系統層面上的問題,比如日誌,事務,許可權等待,Struts2的攔截器設計就是基於AOP的思想,是個比較經典的例子。
2.AOP原理
AOP需要解決的問題是,如何把切面織入到核心邏輯中,如何對呼叫方法進行攔截,並在攔截前後進行安全檢查、日誌、事務等處理,就相當於完成了所有業務功能。
在Java平臺上,對於AOP的織入,有3種方式:
- 編譯期:在編譯時,由編譯器把切面呼叫編譯進位元組碼,這種方式需要定義新的關鍵字並擴充套件編譯器,AspectJ就擴充套件了Java編譯器,使用關鍵字aspect來實現織入;
- 類載入器:在目標類被裝載到JVM時,通過一個特殊的類載入器,對目標類的位元組碼重新“增強”;
- 執行期:目標物件和切面都是普通Java類,通過JVM的動態代理功能或者第三方庫實現執行期動態織入。
最簡單的方式是第三種,Spring的AOP實現就是基於JVM的動態代理。由於JVM的動態代理要求必須實現介面,如果一個普通類沒有業務介面,就需要通過CGLIB或者Javassist這些第三方庫實現。
AOP技術看上去比較神祕,但實際上,它本質就是一個動態代理,讓我們把一些常用功能如許可權檢查、日誌、事務等,從每個業務方法中剝離出來。
需要特別指出的是,AOP對於解決特定問題,例如事務管理非常有用,這是因為分散在各處的事務程式碼幾乎是完全相同的,並且它們需要的引數(JDBC的Connection)也是固定的。另一些特定問題,如日誌,就不那麼容易實現,因為日誌雖然簡單,但列印日誌的時候,經常需要捕獲區域性變數,如果使用AOP實現日誌,我們只能輸出固定格式的日誌,因此,使用AOP時,必須適合特定的場景。
3. 語法
3.1 基本概念
- Aspect:切面,即一個橫跨多個核心邏輯的功能,或者稱之為系統關注點;
- Joinpoint:連線點,即定義在應用程式流程的何處插入切面的執行;
- Pointcut:切入點,即一組連線點的集合;
- Advice:增強,指特定連線點上執行的動作;
- Introduction:引介,指為一個已有的Java物件動態地增加新的介面;
- Weaving:織入,指將切面整合到程式的執行流程中;
- Interceptor:攔截器,是一種實現增強的方式;
- Target Object:目標物件,即真正執行業務的核心邏輯物件;
- AOP Proxy:AOP代理,是客戶端持有的增強後的物件引用,Spring中的AOP代理可以使JDK動態代理,也可以是CGLIB代理,前者基於介面,後者基於子類
3.2 通知方法
前置通知(@Before)
- 在目標方法被呼叫之前做增強處理,@Before只需要指定切入點表示式即可
後置通知(@After)
- 在目標方法完成之後做增強,無論目標方法時候成功完成。@After可以指定一個切入點表示式
返回通知 (@AfterReturning)
- 在目標方法正常完成後做增強,@AfterReturning除了指定切入點表示式後,還可以指定一個返回值形參名returning,代表目標方法的返回值
異常通知 (@AfterThrowing)
- 主要用來處理程式中未處理的異常,@AfterThrowing除了指定切入點表示式後,還可以指定一個throwing的返回值形參名,可以通過該形參名來訪問目標方法中所丟擲的異常物件
環繞通知 (@Around)
- 環繞通知,在目標方法完成前後做增強處理,環繞通知是最重要的通知型別,像事務,日誌等都是環繞通知,注意程式設計中核心是一個ProceedingJoinPoint
3.3 啟用方式
xml配置方式,在applicationContext.xml中配置下面一句:
<aop:aspectj-autoproxy />
註解方式,在啟動類上加上
@EnableAspectJAutoProxy
@EnableAspectJAutoProxy @SpringBootApplication public class Application { public static void main(String[] args) { //do something } }
4.例項
4.1 定義目標類
public class MathCalculator {
public int div(int x, int y) {
System.out.println(x / y);
return x / y;
}
}
4.2 定義切面類,並指定通知方法
@Aspect
public class LogAspects {
@Pointcut("execution(int com.test.tornesol.util.spring.spring_aop.MathCalculator.div(int,int))")
public void pointCut() { }
@Before("com.test.tornesol.util.spring.spring_aop.LogAspects.pointCut()")
public void logStart(JoinPoint joinPoint) {
System.out.println(joinPoint.getSignature().getName() + " 除法執行,引數是:" + Arrays.asList(joinPoint.getArgs()));
}
@After("com.test.tornesol.util.spring.spring_aop.LogAspects.pointCut()")
public void logEnd() {
System.out.println("除法結束");
}
@AfterReturning(value = "com.test.tornesol.util.spring.spring_aop.LogAspects.pointCut())", returning = "result")
public void logReturn2(JoinPoint joinPoint, Object result) {
System.out.println(joinPoint.getSignature().getName() + "除法返回" + result);
}
@AfterThrowing(value = "com.test.tornesol.util.spring.spring_aop.LogAspects.pointCut()", throwing = "exception")
public void logException(Exception exception) {
System.out.println("除法異常");
}
}
Notes: 注意給切面類新增@Aspect註解
4.3 新增Configuration類,注入目標類和切面類,並開啟AOP代理模式
@Configuration
@EnableAspectJAutoProxy//開啟基於註解的AOP模式
public class MainConfig {
@Bean
public MathCalculator mathCalculator() {
return new MathCalculator();
}
@Bean
public LogAspects logAspects() {
return new LogAspects();
}
}
4.4 測試 輸出
public class AopDemo {
static public void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
context.getBean(MathCalculator.class).div(4, 2);
}
}
div 除法執行,引數是:[4, 2]
2
除法結束
div除法返回2
5. 參考文件
- 使用AOP - 廖雪峰的官方網站 (liaoxuefeng.com),廖雪峰的文件寫的還不錯,可以用來快速瞭解上手AOP。
- Aspect Oriented Programming With Spring,Spring的官方文件最權威詳細