天天看點

Spring面向切面程式設計AOP ->(個人學習記錄筆記)

@

目錄

  • Spring面向切面程式設計AOP
    • 1. 導包
    • 2. spring中基于XML的AOP配置
      • 2.1 配置步驟
      • 2.2 切入點表達式的寫法
      • 2.3 具體類型
      • 2.4 環繞類型
    • 3. spring中基于注解的AOP配置
      • 3.1 xml配置
      • 3.2 不使用xml配置

  • aspectjweaver

    解析切入表達式
<dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <!--  解析切入點表達式  -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>
    </dependencies>
           

  1. 把通知的

    Bean

    也交給

    spring

    來管理
  2. 使用

    aop:config

    标簽表明開始AOP的配置
  3. aop:aspect

    标簽表明配置切面
  1. aop:aspect

    标簽内部使用對應的标簽來配置通知的類型
  • 我們現在示例是讓

    printLog

    方法在切入點方法執行之前之前:是以是前置通知
    • aop:before

      :表示配置前置通知
      • method

        屬性:用于指定

        Logger

        類中哪個方法是前置通知
      • pointcut

        屬性:用于指定切入點表達式,該表達式的含義指的是對業務層中哪些方法增強

  • 關鍵字:

    execution(表達式)

  • 表達式:
    • 通路修飾符 傳回值 包名.包名.包名...類名.方法名(參數清單)

  • 标準的表達式寫法:
    • public void xyz.slienceme.service.impl.AccountServiceImpl.saveAccount()

  • 通路修飾符可以省略
    • void xyz.slienceme.service.impl.AccountServiceImpl.saveAccount()

  • 傳回值可以使用通配符,表示任意傳回值
    • xyz.slienceme.service.impl.AccountServiceImpl.saveAccount()

  • 包名可以使用通配符,表示任意包。但是有幾級包,就需要寫幾個

    *.

    • * *.*.*.*.AccountServiceImpl.saveAccount())

  • 包名可以使用..表示目前包及其子包
    • * *..AccountServiceImpl.saveAccount()

  • 類名和方法名都可以使用*來實作通配
    • * *..*.*()

  • 參數清單:
    • 可以直接寫資料類型:
      • 基本類型直接寫名稱

        int

      • 引用類型寫包名.類名的方式

        java.lang.String

    • 可以使用通配符表示任意類型,但是必須有參數
    • 可以使用..表示有無參數均可,有參數可以是任意類型

      全通配寫法:

    • * *..*.*(..)

  • 實際開發中切入點表達式的通常寫法:
    • 切到業務層實作類下的所有方法
      • * xyz.slienceme.service.impl.*.*(..)

<?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,把service對象配置進來-->
    <bean id="accountService" class="xyz.slienceme.service.impl.IAcountServiceImpl"/>
    <!--配置AOP-->
    <aop:config>
        <!--配置切面 -->
        <aop:aspect id="logAdvice" ref="logger">
            <!-- 配置通知的類型,并且建立通知方法和切入點方法的關聯-->
            <aop:before method="printLog" pointcut="execution(* xyz.slienceme.service.impl.*.*(..))"/>
        </aop:aspect>
    </aop:config>
    <!--  配置Logger類  -->
    <bean id="logger" class="xyz.slienceme.utils.Logger"/>
</beans>
           

<?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,把service對象配置進來-->
    <bean id="accountService" class="xyz.slienceme.service.impl.IAcountServiceImpl"/>

    <!--配置AOP-->
    <aop:config>

        <!-- 配置切入點表達式 id屬性用于指定表達式的唯一辨別。expression屬性用于指定表達式内容
          此标簽寫在aop:aspect标簽内部隻能目前切面使用。
          它還可以寫在aop:aspect外面,此時就變成了所有切面可用
      -->
        <aop:pointcut id="pt1" expression="execution(* xyz.slienceme.service.impl.*.*(..))"/>
        <!--配置切面 -->
        <aop:aspect id="logAdvice" ref="logger">
            <!-- 配置前置通知:在切入點方法執行之前執行 -->
            <aop:before method="beforePrintLog" pointcut-ref="pt1"/>

            <!-- 配置後置通知:在切入點方法正常執行之後值。它和異常通知永遠隻能執行一個 -->
            <aop:after-returning method="afterReturnPrintLog" pointcut-ref="pt1"/>

            <!-- 配置異常通知:在切入點方法執行産生異常之後執行。它和後置通知永遠隻能執行一個 -->
            <aop:after-throwing method="afterThrowingPrintiLog" pointcut-ref="pt1"/>

            <!-- 配置最終通知:無論切入點方法是否正常執行它都會在其後面執行 -->
            <aop:after method="afterPrintiLog" pointcut-ref="pt1"/>

        </aop:aspect>
    </aop:config>
    <!--  配置Logger類  -->
    <bean id="logger" class="xyz.slienceme.utils.Logger"/>
</beans>
           
package xyz.slienceme.utils;
import org.aspectj.lang.ProceedingJoinPoint;

/**
 * @Author slience_me
 * @Time : 2021/7/1  11:19
 * 用于記錄日志的工具類,它裡邊提供了公共的代碼
 */
public class Logger {

    /**
     * 前置通知
     */
    public void beforePrintLog(){
        System.out.println("前置通知Logger類中的beforePrintLog方法開始記錄日志了。。。");
    }
    /**
     * 後置通知
     */
    public void afterReturnPrintLog(){
        System.out.println("後置通知Logger類中的afterReturnPrintLog方法開始記錄日志了。。。");
    }
    /**
     * 異常通知
     */
    public void afterThrowingPrintiLog(){
        System.out.println("異常通知Logger類中的afterThrowingPrintiLog方法開始記錄日志了。。。");
    }
    /**
     * 最終通知
     */
    public void afterPrintiLog(){
        System.out.println("最終通知Logger類中的afterPrintiLog方法開始記錄日志了。。。");
    }
}
           

<?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,把service對象配置進來-->
    <bean id="accountService" class="xyz.slienceme.service.impl.IAcountServiceImpl"/>

    <!--配置AOP-->
    <aop:config>

        <!-- 配置切入點表達式 id屬性用于指定表達式的唯一辨別。expression屬性用于指定表達式内容
          此标簽寫在aop:aspect标簽内部隻能目前切面使用。
          它還可以寫在aop:aspect外面,此時就變成了所有切面可用
      -->
        <aop:pointcut id="pt1" expression="execution(* xyz.slienceme.service.impl.*.*(..))"/>
        <!--配置切面 -->
        <aop:aspect id="logAdvice" ref="logger">
            <!-- 配置環繞通知 詳細的注釋請看Logger類中-->
            <aop:around method="aroundPringLog" pointcut-ref="pt1"/>
        </aop:aspect>
    </aop:config>
    <!--  配置Logger類  -->
    <bean id="logger" class="xyz.slienceme.utils.Logger"/>
</beans>
           
package xyz.slienceme.utils;

import org.aspectj.lang.ProceedingJoinPoint;

/**
 * @Author slience_me
 * @Time : 2021/7/1  11:19
 * 用于記錄日志的工具類,它裡邊提供了公共的代碼
 */
public class Logger {
	 /**
     * 前置通知
     */
    public void beforePrintLog(){
        System.out.println("前置通知Logger類中的beforePrintLog方法開始記錄日志了。。。");
    }
    /**
     * 後置通知
     */
    public void afterReturnPrintLog(){
        System.out.println("後置通知Logger類中的afterReturnPrintLog方法開始記錄日志了。。。");
    }
    /**
     * 異常通知
     */
    public void afterThrowingPrintiLog(){
        System.out.println("異常通知Logger類中的afterThrowingPrintiLog方法開始記錄日志了。。。");
    }
    /**
     * 最終通知
     */
    public void afterPrintiLog(){
        System.out.println("最終通知Logger類中的afterPrintiLog方法開始記錄日志了。。。");
    }

    /**
     * 環繞通知
     * 問題:
     *      當我們配置了環繞通知之後,切入點方法沒有執行,而通知方法執行了。
     * 分析:
     *      通過對比動态代理中的環繞通知代碼,發現動态代理的環繞通知有明确的切入點方法調用,而我們的代碼中沒有。
     * 解決:
     *      Spring架構為我們提供了一個接口:ProceedingJoinPoint。該接口有一個方法proceed(),此方法就相當于明确調用切入點方法。
     *      該接口可以作為環繞通知的方法參數,在程式執行時,spring架構會為我們提供該接口的實作類供我們使用。
     *
     * spring中的環繞通知:
     *      它是spring架構為我們提供的一種可以在代碼中手動控制增強方法何時執行的方式。
     */
    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方法開始記錄日志了。。。最終");
        }
    }
}

           

<?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="xyz.slienceme"/>

    <!-- 配置spring開啟注解AOP的支援 -->
    <aop:aspectj-autoproxy/>
</beans>
           
  • 更建議使用環繞類型
package xyz.slienceme.utils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * @Author slience_me
 * @Time : 2021/7/1  11:19
 * 用于記錄日志的工具類,它裡邊提供了公共的代碼
 */
@Component("logger")
@Aspect//表示目前面是個切面類
public class Logger {

    @Pointcut("execution(* xyz.slienceme.service.impl.*.*(..))")
    private void pt1(){}

    /**
     * 前置通知
     */
    @Before("pt1()")
    public void beforePrintLog(){

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

        System.out.println("後置通知Logger類中的afterReturnPrintLog方法開始記錄日志了。。。");
    }
    /**
     * 異常通知
     */
    @AfterThrowing("pt1()")
    public void afterThrowingPrintiLog(){
        System.out.println("異常通知Logger類中的afterThrowingPrintiLog方法開始記錄日志了。。。");
    }
    /**
     * 最終通知
     */
    @After("pt1()")
    public void afterPrintiLog(){
        System.out.println("最終通知Logger類中的afterPrintiLog方法開始記錄日志了。。。");
    }
    
    @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方法開始記錄日志了。。。最終");
        }
    }
}

           

@Configuration
    @ComponentScan(basePackages = "xyz.slienceme")
    @EnableAspectJAutoProxy
    public class SpringConfiguration{
    }