面向切面的Spring
- 面向切面程式設計的基本原理
- 通過POJO建立切面
- 使用@AspectJ註解
- 為AspectJ切面注入依賴
定義AOP術語
-
通知(Advice)
- 前置通知(Before):在目標方法被呼叫之前呼叫通知功能
- 後置通知(After):在目標方法完成之後呼叫通知,此時不會關心方法的輸出是什麼
- 返回通知(After-returning):在目標方法成功執行之後呼叫通知
- 異常通知(After-throwing):在目標方法丟擲異常後呼叫通知
- 環繞通知(Around):通知包裹了被通知的方法,在被通知的方法呼叫之前和呼叫之後執行自定義的行為
- 連線點(Join point),是在應用執行過程中能夠插入切面的一個點
- 切點(Poincut),利用正規表示式定義所匹配的類和方法名稱來指定切點
- 切面(Aspect),通知和切點的結合,它是什麼,在何時何處完成其功能
- 引入(Introduction):允許向現有的類新增新方法或屬性,不影響現有的類
-
織入(Weaving):
- 把切面應用到目標物件並建立新的代理物件的過程
- 在指定的連線點被織入到目標物件中
-
在目標物件的生命週期裡有多個點可以進行織入:
- 編譯期:切面在目標類編譯時被織入,如AspectJ
- 類載入期:切面在目標類載入到JVM時被織入,如AspectJ 5的載入時織入(load-time weaving,LTW)
- 執行期:切面在應用執行的某個時刻被織入,如Spring AOP
Spring對AOP的支援
- 基於代理的經典Spring AOP
- 純POJO切面
- @AspectJ註解驅動的切面
- 注入式AspectJ切面(適用於Spring各版本)
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;
/**
* 是一個切面,沒有提供前置、後置或環繞通知,而是使用了@DeclareParents註解
* @DeclareParents註解詳解:
* value屬性指定了哪種型別的bean要引入的介面,"+"表示Performance的所有子型別
* defaultImpl屬性指定了引入功能提供實現的類
* @DeclareParents註解所標註的靜態屬性指明瞭要引入了介面
*
* 在Spring應用中把EncoreableIntroducer宣告為一個bean,當Spring發現@Aspect註解時會建立一個代理,然後將呼叫委託給被代理的bean或被引入的實現
*
*/
@Aspect
public class EncoreableIntroducer {
@DeclareParents(value="com.leaf.u_spring.chapter04.Performance+",
defaultImpl=DefaultEncoreable.class)
public static Encoreable encoreable;
}
通過切點來選擇連線點
-
AspectJ的切點表示式語言
- AspectJ指示器和描述
- arg(),限制連線點匹配引數為指定型別的執行方法
- @args(),限制連線點匹配引數由指定註解標註的執行方法
- execution(),用於匹配是連線點的執行方法
- this(),限制連線點匹配AOP代理的bean引用為指定型別的類
- target(),限制連線點匹配目標物件為指定型別的類
- @target(),限制連線點匹配特定的執行物件,這些物件對應的類要具有指定型別的註解
- within(),限制連線點匹配指定的型別
- @within(),限制連線點匹配指定註解所標註的型別(當使用Sping AOP時,方法定義在由指定的註解所標註的類裡)
- @annotation,限定匹配帶有指定註解的連線點
- 編寫切點 Performance
import java.util.Map;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import com.google.common.collect.Maps;
/**
* 使用引數化的通知來記錄磁軌播放的次數
*/
@Aspect
public class TrackCounter {
private Map<Integer, Integer> trackCounters = Maps.newHashMap();
/**
* 通知playTrack()方法
*
*
* playTrack(int)指定int型別引數
* && args(trackNumber)指定引數
*
*
* @param trackNumber
*/
@Pointcut("execution(* com.leaf.u_spring.chapter02.CompactDisc.playTrack(int)) && args(trackNumber)")
public void trackPlayed(int trackNumber){}
/**
* 播放前為磁軌計數
* @param trackNumber
*/
@Before("trackPlayed(trackNumber)")
public void countTrack(int trackNumber){
int currentCount = getPlayCount(trackNumber);
trackCounters.put(trackNumber, currentCount + 1);
}
public int getPlayCount(int trackNumber) {
return trackCounters.containsKey(trackNumber)?trackCounters.get(trackNumber):0;
}
}
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import com.google.common.collect.Lists;
import com.leaf.u_spring.chapter02.CompactDisc;
import com.leaf.u_spring.chapter03.BlankDisc;
/**
* 將BlankDisc和TrackCounter定義為bean,並啟用AspectJ自動代理
*
*/
@Configuration
@EnableAspectJAutoProxy
public class TrackCounterConfig {
@Bean
public CompactDisc sgtPeppers(){
BlankDisc cd = new BlankDisc();
cd.setTitle("陽光總在風雨後");
cd.setArtist("許美靜");
List<String> tracks = Lists.newArrayList();
tracks.add("陽光總在風雨後");
tracks.add("成都");
tracks.add("一生所愛");
tracks.add("我的中國心");
tracks.add("Alone Yet Not Alone");
cd.setTracks(tracks);
return cd;
}
@Bean
public TrackCounter trackCounter(){
return new TrackCounter();
}
}
使用註解建立切面
-
Spring使用AspectJ註解來宣告通知方法
- @After 通知方法會在目標方法返回或丟擲異常後呼叫
- @AfterReturning 通知方法會在目標方法返回後呼叫
- @AfterThrowing 通知方法會在目標方法丟擲異常後呼叫
- @Around 通知方法會將目標方法封裝起來
- @Before 通知方法會在目標方法呼叫之前執行
- 環繞通知是最為強大的通知型別
- Java不是動態語言,編譯之後很難再新增新的功能,但是引入AOP,切面可以為Spring bean新增新方法,例EncoreableIntroducer
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
/**
* 演出效果
*
* @Aspect註解表明Audience類不僅僅是個POJO,還是一個切面
* Audience類中的方法都使用註解來定義切面的具體行為
*/
@Aspect
public class Audience {
/**
* 表演之前
*/
@Before("execution(** com.leaf.u_spring.chapter04.Performance.perform(..))")
public void silenceCellPhones(){
System.out.println("silence Cell Phones");
}
/**
* 表演之前
*/
@Before("execution(** com.leaf.u_spring.chapter04.Performance.perform(..))")
public void takeSeates(){
System.out.println("taking seates");
}
/**
* 表演之後
*/
@AfterReturning("execution(** com.leaf.u_spring.chapter04.Performance.perform(..))")
public void applause(){
System.out.println("CLAP CLAP CLAP!!!");
}
/**
* 表演失敗之後
*/
@AfterThrowing("execution(** com.leaf.u_spring.chapter04.Performance.perform(..))")
public void demandRefund(){
System.out.println("Demanding a refund");
}
}
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
/**
* 演出效果
* 相同的切點表示式重複了4遍
* @Ponitcut註解能夠在一個@AspectJ切面內定義可重用的切點
*
*/
@Aspect
public class Audience2 {
/**
* 定義命名的切點
*/
@Pointcut("execution(** com.leaf.u_spring.chapter04.Performance.perform(..))")
public void performance(){}
/**
* 表演之前
*/
@Before("performance())")
public void silenceCellPhones(){
System.out.println("silence Cell Phones");
}
/**
* 表演之前
*/
@Before("performance())")
public void takeSeates(){
System.out.println("taking seates");
}
/**
* 表演之後
*/
@AfterReturning("performance())")
public void applause(){
System.out.println("CLAP CLAP CLAP!!!");
}
/**
* 表演失敗之後
*/
@AfterThrowing("performance())")
public void demandRefund(){
System.out.println("Demanding a refund");
}
}
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
/**
* 演出效果
* 建立環繞通知
*
*/
@Aspect
public class Audience3 {
/**
* 定義命名的切點
*/
@Pointcut("execution(** com.leaf.u_spring.chapter04.Performance.perform(..))")
public void performance(){}
/**
* 環繞通知方法
*/
@Around("performance())")
public void watchPerformance(ProceedingJoinPoint jp){
try {
System.out.println("silence Cell Phones");
System.out.println("taking seates");
//不呼叫proceed方法,通知會阻塞對被通知方法的呼叫
jp.proceed();
System.out.println("CLAP CLAP CLAP!!!");
} catch (Throwable e) {
System.out.println("Demanding a refund");
}
}
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy //啟用AspectJ自動代理 XMl--> <aop:aspectj-autoproxy/>
@ComponentScan
public class ConcertConfig {
/**
* 宣告Audience bean
* @return
*/
@Bean
public Audience audience(){
return new Audience();
}
}
在XML中宣告註解
- 原則:基於註解的配置優於基於Java的配置,基於Java的配置要優於基於XML的配置
- 需要宣告切面,又不能為通知類新增註解時,須轉向XML的配置
-
Spring的AOP配置能夠以非侵入式的方式宣告切面:
- <aop:advisor>:定義AOP通知器
- <aop:after>:定義AOP後置通知(不管被通知的方法是否執行成功)
- <aop:after-returning>:定義AOP返回通知
- <aop:after-throwing>:定義AOP異常通知
- <aop:around>:定義AOP環繞通知
- <aop:aspect>:定義一個切面
- <aop:aspectj-autoproxy>:啟用@AspectJ註解驅動的切面
- <aop:before>:定義一個AOP前置通知
- <aop:config>:頂層的AOP配置元素,大多數的<aop:*>元素必須包含在<aop:config>元素內
- <aop:declare-parents>:以透明的方式為被通知的物件引入額外的介面
- <aop:pointcut>:定義一個切點
引用:《Spring In Action 4》第4章