八、Spring AOP(兩種配置方式實現)

一、Spring AOP

1.1 AOP概述

  1. 定義

AOP(Aspect Oriented Programming)面向切面編程,通過預編譯和運行期動態代理的方式,實現了程序各層級業務邏輯的隔離,降低了程序的耦合性,提高了程序開發的效率

  1. 作用

在程序運行期間,通過動態代理的方式不改變源碼實現對方法的增強

  1. 優勢
  1. 降低了代碼的耦合性
  2. 提高了開發效率
  3. 方便維護

1.2 相關術語

  1. 連接點 Joinpoint

被攔截到的點;spring中指方法,因爲spring只支持方法的攔截

  1. 切入點 Pointcut

進行了增強的連接點

  1. 通知 Advice

在切入點進行的所有增強的操作
通知的類型:前置通知,後置通知,異常通知,最終通知,環繞通知
在這裏插入圖片描述

  1. 切面 Aspect

切入點和通知的結合

  1. 目標對象 Target

被代理的對象

  1. 代理對象 Proxy

增強後的對象

  1. 織入 Weaving

把切面應用到目標對象產生增強後的代理對象的過程
注意:spring採用動態代理織入,而AspectJ採用編譯器織入和類裝載期織入

  1. 引入

二、XML配置實現

  1. xml配置步驟
  1. 使用aop:config標籤開始spring aop的配置
  2. 使用aop:aspect標籤開始配置切面
  • id: 切面ID
  • ref: 切面處理Bean
  1. 使用aop:aspect等子標籤配置通知類型
  • method: 通知處理的具體方法
  • pointcut: 切入點表達式,用於描述哪些連接點可以作爲切入點
  1. 切入點表達式
  • 關鍵字: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 使用步驟

  1. 使用@EnableAspectJAutoProxy註解開啓AOP支持
  2. 編寫切面,使用@Component註解和@Aspect註解來指定切面類
  3. 使用@Pointcut註解指定切入點,並且編寫execution(* com.lizza..*())表達式來指定要切入的包
  4. 使用@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來獲取切入點的方法,參數

源碼地址:https://github.com/KJGManGlory/spring-framework

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章