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隻支援使用方法作為切點,如果需要對構造器/執行個體域應用切面隻能使用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.