天天看點

Spring切面

AOP基本概念

  • 通知(Advice):定義了一個切面該幹什麼(what)以及什麼時候應用(when).spring的通知類型有五種:
    • before:在方法調用之前.
    • after:在方法調用之後,無論方法是否正常傳回.
    • after-returning:在方法正常傳回之後應用.
    • after-throwing:在方法出現異常時調用.
    • around:該通知将包住整個方法.
  • 連接配接點(join point):所有可能應用切面的點,包括方法調用,執行個體域改變等等.
  • 切點(pointcut):定義了切面的具體應用位置,哪些方法使用了通知之類的.(where)
  • 切面(aspect):是advice與pointcut的結合,它定義了對哪些方法(where)在哪些情況下(when)執行什麼操作(what).
  • 引入(INTRODUCTION):用于向已有類中加入新的執行個體域/方法,使用動态代理實作.
  • 織入(WEAVING):将切面/引入應用到目标對象,并建立代理對象的過程.織入的時機主要有以下3個:
    • 編譯期:在目标類編譯時将切面織入,需要使用特别的編譯器,AspectJ使用該方式.
    • 類加載時期:在類加載器加載位元組碼時,對位元組碼進行修改,實作織入.需要特殊的ClassLoader,AspectJ 5支援該織入方式.
    • 運作期:通過建立動态代理(JDK/CGLib),在運作期基于目标對象動态生成代理類,并最終建立代理執行個體,更多的是複合的意味.這是spring的織入模式.

Spring AOP支援

spring對于AOP的支援主要有以下三種方式:

1. 利用xml的aop命名空間,将一個純pojo轉換為切面.

2. 利用@AspectJ驅動的切面.

3. 直接注入AspectJ的切面.

tips:1,2兩種方式,Spring是通過動态代理來實作的.

jdk動态代理的應用如下:将Advice對象以及目标對象注入到InvocationHandler執行個體中,并在invoke方法中實作切面.(該InvocationHandler執行個體會傳遞給代理對象).

Spring切面

spring隻支援使用方法作為切點,如果需要對構造器/執行個體域應用切面隻能使用3的AspectJ.

Spring AOP

Spring使用AspectJ表達式定義切點,但是隻支援AspectJ的子集。

Spring支援的AspectJ符号如下表

AspectJ符号 含義
execution(expression) 用于真正篩選方法切點,其他符号基于該結果進行進一步限定,是以是必須的。
this(fullClassName) 用于選擇那些代理是基于fullClassName的bean(切點/代理對象)
bean(id) 限定隻對特定id的bean應用切面
@args(AnnotationTypes) 用于限定那些參數的類型使用AnnotationTypes進行注解的切點
args(name) 用于傳遞實際參數到通知
within(package.*) 限定切點的bean的類型在package.*的範圍内
@within(AT)/@target(AT) 限定切點所在類必須由@AT進行注解
@annotation(AT) 限定切點對應的方法必須有@AT注解
target(targetClass) 限定切點所在的類必須是targetClass

基于注解的Spring AOP

  • 必須在配置類中使用@EnableAspectJAutoProxy,允許自動使用切面代理。
  • 通過@Aspect聲明一個切面并通過@Component/@Bean建立該切面的執行個體,使其可以使用。
package high.perform;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


@Aspect
@Component
public class Audience {
    //這裡聲明了切點表達式,用于比對切點
    //*表示比對任意傳回類型
    //+表示比對Perform及其子類
    //..表示參數任意
    @Pointcut(value = "execution(* high.perform.Perform+.play(..)) && " +
            // args用于傳遞來自切點的參數,在這裡即為Perform.play的參數,
            // 需要與playX的參數同名傳遞(出于多參數考慮)
            "args(song) && " +
            // 限定為被@T注解的類作為切點所在的類
            "@target(TX) && " +
            // 選擇id為mp的bean,若id不存在則無論bean(id)/!bean(id)過濾結果都為false.
            "bean(mp) && " +
            // 用于選擇那些被@TX注解的切點(方法)
            "@annotation(TX)",
            // 當使用在非debug模式編譯,需要使用argNames屬性,該屬性必須與playX的參數名一緻
            argNames = "song")
    public void playX(String song){}

    //在這裡聲明一個前置通知,使用前面定義的切點
    @Before("playX(song)")
    public void beforePerform(String song){
        System.out.println(song+" is ready to play.");
        System.out.println("Please sit down.");
    }
}

interface Perform {
    void play(String x);
}
@Component("mp")
@TX
//這裡是一個切點
class MusicPerform implements Perform{
    @Override
    @TX
    public void play(@TX String x) {
        System.out.println("Sing "+x+" out.");
    }
}
@Target({ElementType.TYPE,ElementType.PARAMETER,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface TX{}
           
  • 環繞通知(Aroud)是最強大的通知,其對應的實作方法需要有一個ProceedingJoinPoint參數.
package high.perform;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


@Aspect
@Component
public class Audience {
    @Pointcut("execution(* *.play(..)) && args(song)")
    public void playX(String song){}

    @Around("playX(song)")
    //ProceedingJoinPoint不需要加入到args中
    public void aroundPerform(ProceedingJoinPoint pjp,String song){
        try{
            System.out.println("this is before advice.");
            Object[] params = {song};
            //目标方法調用
            pjp.proceed(params);
            System.out.println("this is after advice.");
        }catch (Throwable e){
            System.out.println("this is after-throwing.");
        }
    }

}

interface Perform {
    void play(String x);
}
@Component("mp")
@TX
//這裡是一個切點
class MusicPerform implements Perform{
    @Override
    @TX
    public void play(@TX String x) {
        System.out.println("Sing "+x+" out.");
    }
}
@Target({ElementType.TYPE,ElementType.PARAMETER,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface TX{}
           

利用切面實作引入

@Aspect
@Component()
public class IAIB{
    //這裡的value必須是一個全限定類名,否則不成功.
    //對所有B的子類進行代理,引入接口A的功能,具體實作使用AI.
    //這部分引入的功能基于動态代理很容易實作,隻要在建立代理時傳進去引入的接口即可.
    @DeclareParents(value = "high.perform.B+",defaultImpl = AI.class)
    public static A a;
}
           

注入AspectJ切面

wait for need.