一、Spring AOP
1.1 AOP概述
- 定義
AOP(Aspect Oriented Programming)面向切面編程,通過預編譯和運行期動態代理的方式,實現了程序各層級業務邏輯的隔離,降低了程序的耦合性,提高了程序開發的效率
- 作用
在程序運行期間,通過動態代理的方式不改變源碼實現對方法的增強
- 優勢
- 降低了代碼的耦合性
- 提高了開發效率
- 方便維護
1.2 相關術語
- 連接點 Joinpoint
被攔截到的點;spring中指方法,因爲spring只支持方法的攔截
- 切入點 Pointcut
進行了增強的連接點
- 通知 Advice
在切入點進行的所有增強的操作
通知的類型:前置通知,後置通知,異常通知,最終通知,環繞通知
- 切面 Aspect
切入點和通知的結合
- 目標對象 Target
被代理的對象
- 代理對象 Proxy
增強後的對象
- 織入 Weaving
把切面應用到目標對象產生增強後的代理對象的過程
注意:spring採用動態代理織入,而AspectJ採用編譯器織入和類裝載期織入
- 引入
二、XML配置實現
- xml配置步驟
- 使用aop:config標籤開始spring aop的配置
- 使用aop:aspect標籤開始配置切面
- id: 切面ID
- ref: 切面處理Bean
- 使用aop:aspect等子標籤配置通知類型
- method: 通知處理的具體方法
- pointcut: 切入點表達式,用於描述哪些連接點可以作爲切入點
- 切入點表達式
- 關鍵字:execution
- 表達式:訪問修飾符 返回值 類限定名.方法名(參數列表)
- 標準寫法:public void com.lizza.service.UserService.update(int)
- 訪問修飾符可以省略:void com.lizza.service.UserService.update(int)
- 返回值可以使用通配符:* com.lizza.service.UserService.update(int)
- 包名可以使用通配符,有幾級包寫幾個*:* ..*.UserService.update(int)
- 包名使用…表示當前包及其子包:* com.lizza…update(int)
- 類名和方法名都可以使用通配符:* com.lizza…*()
- 參數列表:* com.lizza…*(…)
- 基本類型:直接寫名稱,如int,char,double
- 應用類型:全限定名.類名,如:java.lang.String
- *:表示必須有參數
- …:表示有無參數均可
- 全通配形式:* ….*(…)
- beans.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userService" class="com.lizza.service.UserService"></bean>
<bean id="log" class="com.lizza.util.Log"></bean>
<!--
Spring中配置AOP的步驟
1. 使用<aop:config>標籤開始spring aop的配置
2. 使用<aop:aspect>標籤開始配置切面
id: 切面ID
ref: 切面處理Bean
3. 使用<aop:aspect>等子標籤配置通知類型
method: 通知處理的具體方法
pointcut: 切入點表達式,用於描述哪些連接點可以作爲切入點
4. 切入點表達式
關鍵字:execution
表達式:訪問修飾符 返回值 類限定名.方法名(參數列表)
標準寫法:public void com.lizza.service.UserService.update(int)
訪問修飾符可以省略:void com.lizza.service.UserService.update(int)
返回值可以使用通配符:* com.lizza.service.UserService.update(int)
包名可以使用通配符,有幾級包寫幾個*:* *.*.*.UserService.update(int)
包名使用..表示當前包及其子包:* com.lizza..update(int)
類名和方法名都可以使用通配符:* com.lizza..*()
參數列表:* com.lizza..*(..)
基本類型:直接寫名稱,如int,char,double
應用類型:全限定名.類名,如:java.lang.String
*:表示必須有參數
..:表示有無參數均可
全通配形式:* *..*.*(..)
-->
<aop:config>
<aop:aspect id="logAdvice" ref="log">
<aop:before method="log" pointcut="execution(* com.lizza..*())"></aop:before>
</aop:aspect>
</aop:config>
</beans>
三、註解配置實現
3.1 關鍵註解
@Aspect
:指定切面類@Component
:將切面類作爲Spring容器的bean交由Spring容器管理@Pointcut
:指定切入點@Before
:前置通知@AfterReturning
:後置通知@AfterThrowing
:異常通知@After
:最終通知@Around
:環繞通知@EnableAspectJAutoProxy
:開啓aop
3.2 使用步驟
- 使用
@EnableAspectJAutoProxy
註解開啓AOP支持 - 編寫切面,使用
@Component
註解和@Aspect
註解來指定切面類 - 使用
@Pointcut
註解指定切入點,並且編寫execution(* com.lizza..*())
表達式來指定要切入的包 - 使用
@Before
,@AfterReturning
,@AfterThrowing
,@After
,@Around
註解來指定前置通知,後置通知,異常通知,最終通知,環繞通知
3.3 示例代碼
- 配置切面
@Aspect
@Component
public class Log {
@Pointcut("execution(* com.lizza..*())")
public void pointCut() {}
/**
* 前置通知:切入點執行之前執行
*/
@Before("pointCut()")
public void beforeLog() {
System.out.println("前置通知:記錄日誌...");
}
/**
* 後置通知:切入點執行之後執行
*/
@AfterReturning("pointCut()")
public void afterLog() {
System.out.println("後置通知:記錄日誌...");
}
/**
* 異常通知:切入點執行發生異常時執行
*/
@AfterThrowing("pointCut()")
public void exceptionLog() {
System.out.println("異常通知:記錄日誌...");
}
/**
* 最終通知:無論切入點執行發生異常與否,都會執行
*/
@After("pointCut()")
public void finalLog() {
System.out.println("最終通知:記錄日誌...");
}
/**
* 環繞通知
*/
@Around("pointCut()")
public Object aroundLog(ProceedingJoinPoint point) {
Object result;
try {
System.out.println("環繞通知-前置:記錄日誌...");
Object[] args = point.getArgs(); // 獲取方法執行的參數
result = point.proceed(args); // 執行切入點方法
System.out.println("環繞通知-後置:記錄日誌...");
return result;
} catch (Throwable t) {
System.out.println("環繞通知-異常:記錄日誌...");
throw new RuntimeException(t);
} finally {
System.out.println("環繞通知-最終:記錄日誌...");
}
}
}
- 開啓aop
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.lizza")
public class SpringConfig {
}
3.4 注意問題
- spring aop註解配置時,最終通知會在異常通知或後置通知之前執行,故實際應用時建議使用環繞通知
- spring aop環繞通知使用
ProceedingJoinPoint
來獲取切入點的方法,參數