1. 程式人生 > >【Spring】【AOP】【面向切面程式設計】【AOP的註解】【獲取引數和修改引數】

【Spring】【AOP】【面向切面程式設計】【AOP的註解】【獲取引數和修改引數】

Spring的AOP功能就是面向切面程式設計.我們從Spring容器取出的值,就是已經被重新包裝過代理物件

概念

  • 通知: 要切入的內容
  • 切點: 要切入的地方
  • 切面織入: 將切面織入類的方法中,切面=通知+切點

通知的類

在該類中宣告各自通知,每個通知+切點,都能組成一個切面

public class MyAdvice {

    //前置通知
    public void before() {
        System.out.println("前置通知");
    }

    //後置通知
    public void afterReturning() {
        System.out
.println("後置通知"); } //最終通知 public void after() { System.out.println("最終通知"); } //異常通知 public void afterThrowing() { System.out.println("異常通知"); } }

AOP的配置

  1. 注入通知類的bean
  2. 設定aop–宣告切點
  3. 設定aop–織入切面
    <!-- 注入通知類的bean -->
    <bean id="myAdvice" class="dao.impl.MyAdvice"
/>
<aop:config> <!-- 設定1個切點by execution表示式 --> <aop:pointcut expression="execution(* *..*.*myDowork*(..))" id="pc"/> <!-- 織入4個切面 --> <aop:aspect ref="myAdvice"> <aop:after method="after" pointcut-ref="pc"/> <aop:after-returning
method="afterReturning" pointcut-ref="pc"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pc"/> <aop:before method="before" pointcut-ref="pc"/> </aop:aspect> </aop:config>

1. execution表示式

expression="execution(表示式)"
作用:定位到符合表示式的方法,將其宣告為切入點;
定位的條件有五個:

  • 方法的形參型別
  • 方法的返回值型別
  • 方法所在的包
  • 方法所在的類
  • 方法的名稱

這裡寫圖片描述

例:execution(* *..*.*myDowork*(..)):
表示任意包,類,形參型別,返回值型別.方法名含有myDowork的方法.
例:execution(java.lang.String a.b.A.*(..)):
表示a.b包下的A類,返回值為String的所有方法,

注意:由於我們需要匯入很多jar,所以會有很多方法名重複,儘量使用execution表示式時候,加入包名.

2. 通知型別

  • <aop:before>:前置通知,方法執行前執行
  • <aop:after-returning>:後置通知, 方法執行完執行,(出現異常不執行)
  • <aop:after>:最終通知,方法執行完執行,(出現異常依然執行)
  • <aop:after-throwing>異常通知,方法出現異常後執行
  • <aop:around> 環繞通知,可以一次性完成所有通知,可以修改形參,一般配合註解使用

AOP的註解

不用在Spring的配置檔案中配置,直接在通知的類中用註解方式告訴Spring織入切面方式.
註解所在的包org.aspectj.lang.annotation.

  • 宣告使用註解<aop:aspectj-autoproxy/>
  • 通知的類:@Aspect
  • 切入點的方法(方法名=id);@Pointcut(“execution表示式”)
  • 織入切面:在通知類的方法上
    • @通知型別(“切點id()”)或@通知型別(“execution表示式”的String)
    • 通知型別中的屬性
      • value/pointcut=切點
      • throwing=”ex”,異常通知的Throwable 物件為ex
      • -
<!-- 注入通知類的bean -->
    <bean id="myAdvice" class="dao.impl.MyAdvice"/>

普通的切面織入

@Aspect
public class MyAdvice {

    //宣告切點方式1  //static+final
    public static final String EXP="execution(* *..*.*myDowork*(..))";
    //宣告切點方式2
    @Pointcut("execution(* *..*.*myDowork*(..))")
    public void pc() {}


    @Before("pc()")
    public void before() {
        System.out.println("前置通知");
    }

    @AfterReturning(EXP)
    public void afterReturning() {
        System.out.println("後置通知");
    }

    @After("pc()")
    public void after() {
        System.out.println("最終通知");
    }

    @AfterThrowing("pc()")
    public void afterThrowing() {
        System.out.println("異常通知");
    }
}

異常的切面織入

@Aspect
public class MyAdvice {
    //宣告切點 
    @Pointcut("execution(* *..*.*myDowork*(..))")
    public void pc() {}

    //throwing="ex"表示宣告異常的物件
    @AfterThrowing(pointcut="pc()",throwing="ex")
    public void afterThrowing(Throwable ex) {
        System.out.println("異常通知");
        System.out.println(ex.getMessage());
    }
}

環繞的切面織入

@Aspect
public class MyAdvice {
     //宣告切點
    public static final String EXP="execution(* *..*.*myDowork*(..))";

    @Around(EXP)
    public Object around(ProceedingJoinPoint pjp) {
        try {
            System.out.println("前置通知");
            Object ret = pjp.proceed();    //執行目標物件的方法
            System.out.println("後置通知");
            return ret;                    //返回值
        }catch(Throwable ex) {
            System.out.println("異常通知");
            System.out.println(ex.getMessage());
        }finally {
            System.out.println("最終通知");
        }
        return null;
    }}

引數的傳遞
作用:通過判斷引數值,來選擇不同的處理機制
1. 表示式中需要加入引數
2. 利用argNames屬性,宣告引數
3. 對於符合execution表示式,但不符合引數型別的方法,不會被織入切面

@Aspect
public class MyAdvice {

    //宣告切點方式1  static+final
    public static final String EXP="execution(* *..*.*myDowork*(..))&& args(str,i)";

    @Before(value=EXP,argNames="str,i")
    public void before2(String str,Integer i) {
        System.out.println("123");
        System.out.println(str+i);
    }



    //宣告切點方式2
    @Pointcut(value="execution(* *..*.*myDowork*(..)) && args(str,i)",argNames="str,i")
    public void pc(String str,Integer i) {}

    @Before(value="pc(str,i)",argNames="str,i")
    public void before(String str,Integer i) {
        System.out.println(str+i);
    }
}

引數的修改
只有在環繞通知中可以修改引數.
1.獲得引數的陣列:Object[] args = pjp.getArgs();
2.修改引數的陣列元素:args[i]=XXX;
3.執行目標物件方法時候,傳入新陣列;Object ret = pjp.proceed(args);

@Aspect
public class MyAdvice {

    public static final String EXP = "execution(* *..*.*myDowork*(..))";

    @Around(EXP)
    public Object around(ProceedingJoinPoint pjp) {
            try {
                Object[] args = pjp.getArgs();
                args[0]="hahah";
                Object ret = pjp.proceed(args);
                return ret;
            } catch (Throwable e) {
                e.printStackTrace();
            } 
            return null;
    }
}

注意點:環繞對所有符合表示式的方法,都會進行織入切面
即沒有形參的方法,且符合表示式,也會被織入切面 而加入形參pjp.proceed(args);會報錯.

  • 要嚴格寫好execution表示式
  • 判斷陣列物件長度和大小來進行選擇處理方式
  • pjp.getSignature().getDeclaringTypeName() 目標物件的許可權類名
  • pjp.getSignature().getName() 目標物件呼叫的方法名
  • pjp.getSignature().getDeclaringType() 目標物件的位元組碼物件