Spring的Aop

一、AOP的相關術語

Joinpoint(鏈接點): 目標類中的全部方法都是鏈接點
Pointcut(切入點): 目標類類中會被加強的方法都是切入點
Advice(通知): 所謂通知是指攔截到Pointcut以後所要作的事情就是通知.通知分爲前置通知,後置通知,異常通知,最終通知,環繞通知
Aspect(切面): 通知與切入點的結合(所謂的切面就是用來講明通知與切入點的關係,即:通知在切入點執行的何時執行)
Introduction(引介): 引介是一種特殊的通知在不修改類代碼的前提下, Introduction能夠在運行期爲類動態地添加一些方法或Field
Target(目標對象): 目標類對象(要加強的類)
Proxy(代理對象): 一個目標類被AOP織入加強後,就產生一個結果代理類
Weaving(織入): 是把加強功能應用到目標的過程,即:把advice應用到target的過程

二、Spring的AOP的基本配置步驟

需求:在不修改已有代碼的前提下,在項目現有全部類的方法先後打印日誌java

接口spring

public interface AccountService {
    /**
     * 模擬保存帳戶
     */
    void saveAccount();

    /**
     * 模擬更新帳戶
     */
    void updateAccount(int i);

    /**
     * 模擬刪除帳戶
     */
    int deleteAccount();
}

實現類express

public class AccountServiceImpl implements AccountService {

    public void saveAccount() {
        System.out.println("執行了保存");
    }

    public void updateAccount(int i) {
        System.out.println("執行了更新");
    }

    public int deleteAccount() {
        System.out.println("執行了刪除");
        return 0;
    }
}

通知類app

/**
 * 日誌打印類
 */
public class Logger {
    /**
     * 用於打印日誌,計劃讓其在切入點方法執行前執行(切入點方法就是業務層方法)
     */
    public void printLog() {
        System.out.println("Logger的printLog()執行了");
    }
}

配置文件框架

<?xml version="1.0" encoding="UTF-8"?>
<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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 配置spring的IOC,將業務層的AccountServiceImpl配置到容器中 -->
    <bean id="accountServiceImpl" class="com.itcast.service.impl.AccountServiceImpl"></bean>

    <!--
        spring中基於xml的AOP配置:
            1.將通知Bean配置到spring的IOC容器中
            2.使用<aop:config>標籤代表開始AOP的配置
            3.使用<aop:aspect>標籤代表開始配置切面
                    屬性:
                        id:切面的惟一標識符
                        ref:用於指向通知bean的id
            4.在<aop:aspect>標籤的內部使用對應的標籤來配置通知的類型
                    前置通知:<aop:before>
                        method屬性:用於指定Logger類中的哪一個方法是前置通知
                        pointcut屬性:用於指定切入點表達式,該表達式的含義是指對業務層的哪些方法加強
                            切入點表達式的語法形式:
                                關鍵字:execution(表達式)
                                表達式:
                                    訪問權限 返回值 包名.包名...類名.方法名(參數列表)
                                        標準的表達式寫法:
                                            public void com.itcast.service.impl.AccountServiceImpl.saveAccount()
                                        訪問修飾符能夠省略:
                                            void com.itcast.service.impl.AccountServiceImpl.saveAccount()
                                        返回值可使用通配符,表示任意返回值
                                            * com.itcast.service.impl.AccountServiceImpl.saveAccount()
                                        包名可使用通配符,表示任意包,可是有幾級包,就須要寫幾個*.
                                            * *.*.*.*.AccountServiceImpl.saveAccount()
                                        包名可使用..表示當前包及其子包
                                            * *..AccountServiceImpl.saveAccount()
                                        包名和方法名也可使用*來實現通配
                                            * *..*.*()
                                        參數列表
                                            能夠直接寫數據類型
                                                基本類型直接寫名稱:* *..*.*(int)
                                                引用類型寫包名.類名:* *..*.*(java.lang.String)
                                            可使用通配符表示任意類型,但必須是有參數:* *..*.*(*)
                                            可使用..表示有無參數都可:* *..*.*(..)
                                        全通配寫法:
                                            * *..*.*(..)
                                        實際開發中實際切入點表達式的一般寫法:切到業務層下的全部實現類
                                            * com.itcast.service.impl.*.*(..)
                    後置通知:<aop:after>
                    異常通知:<aop:after-throwing>
                    最終通知:<app:after>
                    環繞通知:<aop:around>
                    咱們如今示例是讓printLog()方法在切入點方法執行以前先執行,屬於前置通知

    -->
    <bean id="logger" class="com.itcast.utils.Logger"></bean>

    <aop:config>
        <aop:aspect id="logAdvice" ref="logger">
            <!-- 配置通知的類型,而且創建通知方法和切入點方法的關聯關係 -->
            <aop:before method="printLog" pointcut="execution(* com.itcast.service.impl.*.*(..))"></aop:before>
        </aop:aspect>
    </aop:config>
    
</beans>

測試類ide

public class TestAOP {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        AccountService accountService = applicationContext.getBean("accountServiceImpl",AccountService.class);
        accountService.saveAccount();
        accountService.updateAccount(1);
        accountService.deleteAccount();
    }
}

測試結果工具

Logger的printLog()執行了
執行了保存
Logger的printLog()執行了
執行了更新
Logger的printLog()執行了
執行了刪除

三、四種經常使用的通知類型

XML中的AOP配置測試

<?xml version="1.0" encoding="UTF-8"?>
<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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 配置spring的IOC,將業務層的AccountServiceImpl配置到容器中 -->
    <bean id="accountServiceImpl" class="com.itcast.service.impl.AccountServiceImpl"></bean>

    <bean id="printLog" class="com.itcast.utils.Logger"></bean>

    <aop:config>
        <!--
            配置切入點表達式,id屬性用於指定表達式的惟一標識符,expression屬性用於指定表達式內容,此標籤寫在
            <aop:aspect>標籤內部,只能當前切面內使用。它能夠配置<aop:aspect>標籤爲,這樣就能夠全部的切面均可以複用
        -->
        <aop:pointcut id="pt1" expression="execution(* com.itcast.service.impl.*.*(..))"/>

        <aop:aspect id="logAdvice" ref="printLog">
            
            <!------------------------------- 使用<aop:pointcut>標籤簡化配置前 ------------------------------->
            <!--  
            <!-- 前置通知 -->
            <aop:before method="beforePrintLog" pointcut="execution(* com.itcast.service.impl.*.*(..))"></aop:before>

            <!-- 後置通知 -->
            <aop:after-returning method="afterReturningPrintLog" pointcut="execution(* com.itcast.service.impl.*.*(..))"></aop:after-returning>

            <!-- 異常通知 -->
            <aop:after-throwing method="afterThrowingPrintLog" pointcut="execution(* com.itcast.service.impl.*.*(..))"></aop:after-throwing>

            <!-- 最終通知 -->
            <aop:after method="afterPrintLog" pointcut="execution(* com.itcast.service.impl.*.*(..))"></aop:after>
            -->

            <!------------------------------- 使用<aop:pointcut>標籤簡化配置後 ------------------------------->

            <!-- 前置通知 -->
            <aop:before method="beforePrintLog" pointcut-ref="pt1"></aop:before>

            <!-- 後置通知 -->
            <aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>

            <!-- 異常通知 -->
            <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>

            <!-- 最終通知 -->
            <aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>
        </aop:aspect>
        
    </aop:config>

</beans>

測試類代理

public class TestAOP {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        AccountService accountService = applicationContext.getBean("accountServiceImpl",AccountService.class);
        accountService.saveAccount();
    }
}

測試結果日誌

前置通知:Logger的printLog()執行了
執行了保存
後置通知:Logger的printLog()執行了
最終通知:Logger的printLog()執行了

四、配置環繞通知

通知類

/**
 * 用於記錄日誌的工具類
 */
public class Logger {
    /**
     * 前置通知
     */
    public void beforePrintLog() {
        System.out.println("前置通知:Logger的printLog()執行了");
    }

    /**
     * 後置通知
     */
    public void afterReturningPrintLog() {
        System.out.println("後置通知:Logger的printLog()執行了");
    }

    /**
     * 異常通知
     */
    public void afterThrowingPrintLog() {
        System.out.println("異常通知:Logger的printLog()執行了");
    }

    /**
     * 最終通知
     */
    public void afterPrintLog() {
        System.out.println("最終通知:Logger的printLog()執行了");
    }

    /**
     * 環繞通知
     * 問題:
     *      當咱們配置了環繞通知以後,切入點方法沒有執行,而通知方法執行了
     * 分析:
     *      經過對比動態代理中的環繞通知代碼,發現動態代理的環繞通知有明確的切入點方法調用
     * 解決:
     *      Spring框架爲咱們提供了一個接口,Pro
   */
    public Object aroundPrintLog(ProceedingJoinPoint joinPoint) {
        // 調用proceed()方法即調用切入點方法
        Object retValue = null;
        try {
            System.out.println("前置通知:Logger的aroundPrintLog()執行了");
            // 獲取切入點方法執行時所需的參數
            Object args[] = joinPoint.getArgs();
            retValue = joinPoint.proceed(args);
            System.out.println("後置通知:Logger的aroundPrintLog()執行了");
            return retValue;
        }catch (Throwable throwable) {
            System.out.println("異常通知:Logger的aroundPrintLog()執行了");
            throw new RuntimeException(throwable);
        }finally {
            System.out.println("最終通知:Logger的aroundPrintLog()執行了");
        }
    }
}

XML中的AOP配置

<?xml version="1.0" encoding="UTF-8"?>
<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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 配置spring的IOC,將業務層的AccountServiceImpl配置到容器中 -->
    <bean id="accountServiceImpl" class="com.itcast.service.impl.AccountServiceImpl"></bean>

    <bean id="printLog" class="com.itcast.utils.Logger"></bean>

    <aop:config>
        <!--
            配置切入點表達式,id屬性用於指定表達式的惟一標識符,expression屬性用於指定表達式內容,此標籤寫在
            <aop:aspect>標籤內部,只能當前切面內使用。它能夠配置<aop:aspect>標籤爲,這樣就能夠全部的切面均可以複用
        -->
        <aop:pointcut id="pt1" expression="execution(* com.itcast.service.impl.*.*(..))"/>

        <aop:aspect id="logAdvice" ref="printLog">
            <!-- 配置環繞通知,詳細註釋請查詢Logger類 -->
            <aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
        </aop:aspect>
    </aop:config>

</beans>

測試類

public class TestAOP {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        AccountService accountService = applicationContext.getBean("accountServiceImpl",AccountService.class);
        accountService.saveAccount();
    }
}

測試結果

前置通知:Logger的aroundPrintLog()執行了
執行了保存
後置通知:Logger的aroundPrintLog()執行了
最終通知:Logger的aroundPrintLog()執行了

五、基於註解的AOP配置

接口

/**
 * 帳戶的業務層接口
 */
public interface AccountService {

    /**
     * 模擬保存帳戶
     */
   void saveAccount();

    /**
     * 模擬更新帳戶
     * @param i
     */
   void updateAccount(int i);

    /**
     * 刪除帳戶
     * @return
     */
   int  deleteAccount();
}

實現類

/**
 * 帳戶的業務層實現類
 */
@Service("accountService")
public class AccountServiceImpl implements IAccountService{

    @Override
    public void saveAccount() {
        System.out.println("執行了保存");
    }

    @Override
    public void updateAccount(int i) {
        System.out.println("執行了更新");

    }

    @Override
    public int deleteAccount() {
        System.out.println("執行了刪除");
        return 0;
    }
}

XML配置文件

<?xml version="1.0" encoding="UTF-8"?>
<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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 配置spring建立容器時要掃描的包-->
    <context:component-scan base-package="com.itheima"></context:component-scan>

    <!-- 配置spring開啓註解AOP的支持 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

通知類

在這裏插入代碼片/**
 * 用於記錄日誌的工具類,它裏面提供了公共的代碼
 */
@Component("logger")
//表示當前類是一個切面類
@Aspect
public class Logger {

    @Pointcut("execution(* com.itheima.service.impl.*.*(..))")
    private void pt1(){}

    /**
     * 前置通知
     */
    @Before("pt1()")
    public  void beforePrintLog(){
        System.out.println("前置通知Logger類中的beforePrintLog方法開始記錄日誌了。。。");
    }

    /**
     * 後置通知
     */
    @AfterReturning("pt1()")
    public  void afterReturningPrintLog(){
        System.out.println("後置通知Logger類中的afterReturningPrintLog方法開始記錄日誌了。。。");
    }
    /**
     * 異常通知
     */
    @AfterThrowing("pt1()")
    public  void afterThrowingPrintLog(){
        System.out.println("異常通知Logger類中的afterThrowingPrintLog方法開始記錄日誌了。。。");
    }

    /**
     * 最終通知
     */
    @After("pt1()")
    public  void afterPrintLog(){
        System.out.println("最終通知Logger類中的afterPrintLog方法開始記錄日誌了。。。");
    }
}

測試類

/**
 * 測試AOP的配置
 */
public class AOPTest {
    public static void main(String[] args) {
        //1.獲取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2.獲取對象
        IAccountService as = (IAccountService)ac.getBean("accountService");
        //3.執行方法
        as.saveAccount();
    }
}

測試結果

前置通知Logger類中的beforePrintLog方法開始記錄日誌了。。。
執行了保存
最終通知Logger類中的afterPrintLog方法開始記錄日誌了。。。
後置通知Logger類中的afterReturningPrintLog方法開始記錄日誌了。。。

觀察返回結果就會發現通知的輸出順序有問題,這個時spring在使用註解環繞通知時存在的問題

基於註解的AOP,使用環繞通知不會存在順序問題

/**
 * 用於記錄日誌的工具類,它裏面提供了公共的代碼
 */
@Component("logger")
//表示當前類是一個切面類
@Aspect
public class Logger {

    @Pointcut("execution(* com.itheima.service.impl.*.*(..))")
    private void pt1(){}
    
    /**
     * 環繞通知
     * 問題:
     *      當咱們配置了環繞通知以後,切入點方法沒有執行,而通知方法執行了。
     * 分析:
     *      經過對比動態代理中的環繞通知代碼,發現動態代理的環繞通知有明確的切入點方法調用,而咱們的代碼中沒有。
     * 解決:
     *      Spring框架爲咱們提供了一個接口:ProceedingJoinPoint。該接口有一個方法proceed(),此方法就至關於明確調用切入點方法。
     *      該接口能夠做爲環繞通知的方法參數,在程序執行時,spring框架會爲咱們提供該接口的實現類供咱們使用。
     *
     * spring中的環繞通知:
     *      它是spring框架爲咱們提供的一種能夠在代碼中手動控制加強方法什麼時候執行的方式。
     */
    @Around("pt1()")
    public Object aroundPringLog(ProceedingJoinPoint pjp){
        Object rtValue = null;
        try{
            Object[] args = pjp.getArgs();//獲得方法執行所需的參數

            System.out.println("Logger類中的aroundPringLog方法開始記錄日誌了。。。前置");

            rtValue = pjp.proceed(args);//明確調用業務層方法(切入點方法)

            System.out.println("Logger類中的aroundPringLog方法開始記錄日誌了。。。後置");

            return rtValue;
        }catch (Throwable t){
            System.out.println("Logger類中的aroundPringLog方法開始記錄日誌了。。。異常");
            throw new RuntimeException(t);
        }finally {
            System.out.println("Logger類中的aroundPringLog方法開始記錄日誌了。。。最終");
        }
    }
}

測試類

/**
 * 測試AOP的配置
 */
public class AOPTest {
    public static void main(String[] args) {
        //1.獲取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2.獲取對象
        IAccountService as = (IAccountService)ac.getBean("accountService");
        //3.執行方法
        as.saveAccount();
    }
}

測試結果

Logger類中的aroundPringLog方法開始記錄日誌了。。。前置
執行了保存
Logger類中的aroundPringLog方法開始記錄日誌了。。。後置
Logger類中的aroundPringLog方法開始記錄日誌了。。。最終
相關文章
相關標籤/搜索