AOP 概念
AOP:Aspect Orient Programming
中文翻譯:面向切面編程
作爲面向對象編程的一種補充,採用非侵入式技術手段處理系統中分佈於各個模塊的橫切關注點,比如事務管理,安全審計,日誌、緩存等等。
AOP 優缺點
優點:原有代碼無需更改,不影響原有業務邏輯;
缺點:通過代理方式,會有一定的性能損失;
AOP 代理
AOP 的底層實現是通過代理和反射技術,攔截正常的方法調用,在正常方法調用完成前和完成後插入切面相關的代碼。具體實現有如下兩種:
- 靜態代理:
代表:AspectJ,直接通過修改字節碼實現,編譯期完成,但是需要額外的編譯器支持; - 動態代理:
代表:Spring AOP,運行時動態創建代理類;
Spring AOP 代理又分爲兩種,JDK Proxy 和 CGLIB,分別針對接口代理和類代理。
AOP 關鍵點:
- Aspect:切面
對應一個類,用於實現橫跨多個企業應用類的業務處理,比如事務,日誌等。
通俗的話來說,其他應用類實現業務的主要邏輯(縱向),切面關注次要方面(橫向插入日誌,事務處理,安全審計等動作)。 - Join Point:連接點
方法執行,異常處理,改變對象的值等特定情況; - Advice:動作
與連接點和切點相關的一系列動作
對應一個方法 - Pointcut:切點
與連接點相關的表達式,匹配則執行相關動作(Advice); - Target Object:目標對象
加入相關的動作處理(Advice)的目標對象; - AOP Proxy:代理
Spring AOP 通過 JDK Proxy 或者 CGLIB 創建代理; - Weaving:織入
關聯切面和目標對象的過程。可以在編譯期,加載期或運行期完成;
Spring AOP 在運行期完成這個過程,AspectJ在編譯期完成這個過程;
AOP 實現舉例:(函數執行時間打印)
基於常用的 Spring AOP 爲例,基於註解實現打印函數執行時間的切面功能。
增加依賴包
創建 Spring 項目,在 pom.xml 加入 Spring AOP 相關的類。
#pom.xml
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.14</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.14</version>
</dependency>
開啓切面能力
proxyTargetClass = true,強制使用 CGLIB 代理,如果不設置針對於接口方法織入,默認使用 JDK Proxy 代理。
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AspectJConfiguration {
}
定義註解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CallTime {
}
創建切面
@Aspect
@Component
public class CallTimeAspect {
}
增加切點
@Aspect
@Component
public class CallTimeAspect {
@Pointcut("@annotation(CallTime) && execution(public org.test.service.*(..))")
public void pointCutMethod();
}
"@annotation(CallTime) && execution(public org.test.service.*(..))"
這個表達式表示只攔截加有CallTime註解的,並且是public方法,並且是 org.test.service這個包下面的類的方法,只有匹配這個表達式的方法纔會被攔截,當然這個表達式可以更復雜,比如針對參數做匹配等等。
增加連接點和動作
Spring 只支持方法連接點,主要有3種方式對方法進行增強:
@Before 方法執行前;
@After 方法執行後;
@Around 方法執行過程中;
@Aspect
@Component
public class CallTimeAspect {
@Pointcut("@annotation(CallTime) && execution(public org.test.service.*(..))")
public void pointCutMethod();
@Around("pointCutMethod()")
public Object callTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object proceed = joinPoint.proceed();
long end = System.currentTimeMillis();
String className = proceed.getSignature().getDeclaringType().getSimpleName();
String functionName = proceed.getSignature().getName();
System.out.println(String.format("%s.%s takes %dms", className, functionName, end - start));
return proceed;
}
}
增加攔截代碼
# org.test.service
public class BusinessService {
@CallTime
void doBusiness() {
...
}
}
當doBusiness在外部被調用時,就會打印執行時間了:
BusinessService.doBusiness takes XXXms
潛在的問題
- 被final修飾的類無法被改寫,因此也就沒法使用 AOP 進行攔截;
- 在外部調用被攔截的方法中再調用該類其他的方法,無法使用 AOP 進行攔截;
class Test {
@CallTime
public void funcA() {
this.funcB();
}
@CallTime
public void funcB() {
}
在外部調用:
new Test().funcA()
只有 funcA 會被攔截,funcB 並不會。
- 私有方法不能被外部調用,因此也就沒法使用 AOP 進行攔截;