[Spring實戰系列](18)注解切面
使用注解來創建切面是AspectJ 5所引入的關鍵特性。在AspectJ 5之前,編寫AspectJ切面需要學習一種Java語言的擴展,但是AspectJ面向注解的模型可以非常簡單的通過少量注解把任意類轉變為切面。
回顧一下Audience類,沒有任何地方讓它成為一個切面,我們不得不使用XML聲明通知和切點。
我們通過@AspectJ注解,我們再看看Audience類,不需要任何額外的類或Bean聲明就能將它轉換為一個切面。
package com.sjf.bean;
/**
* 歌手實體類
* @author sjf0115
*
*/
public class Singer {
public void perform() {
System.out.println("正在上演個人演唱會... ");
}
}
package com.sjf.bean;
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;
/**
* 觀眾實體類(注解為切面)
* @author sjf0115
*
*/
@Aspect
public class Audience {
// 定義切點
@Pointcut("execution(* com.sjf.bean.Singer.perform(..))")
public void SingerPerform(){
// 空方法
}
// 表演之前
@Before("SingerPerform()")
public void takeSeats(){
System.out.println("the audience is taking their seats...");
}
// 表演成功之後
@AfterReturning("SingerPerform()")
public void applaud(){
System.out.println("very good, clap clap clap...");
}
// 表演失敗之後
@AfterThrowing("SingerPerform()")
public void demandRefund(){
System.out.println("very bad, We want our money back...");
}
}
新的Audience類現在已經使用@AspectJ注解進行標注。該注解標示了Audience不僅僅是一個POJO,還是一個切面。
@Pointcut注解用於定義一個可以在@AspectJ切面內可重用的切點。@Pointcut注解的值是一個AspectJ切點表達式(這裡標示切點必須匹配Singer的perform()方法)。切點的名稱來源於注解所應用的
方法名稱。因為,該切點的名稱為SingerPerform()。SingerPerform()方法的實際內容並不重要,它只是一個標示,供@Pointcut注解依附。
Audience的每一個方法都是用通知注解來標注。takeSeats()方法使用@Before注解來標示它們是前置通知方法。applaud()方法使用@AfterReturning注解來標示它是後置通知方法。demandRefund()方法使用@AfterThrowing注解標示它在拋出異常時該方法被會調用。SingerPerform()切點的名稱作為參數的值賦予給所有的通知注解,來標示每一個通知方法應該應用在哪。
注意:
除了注解和無操作的SingerPerform()方法,Audience類在實現上並沒有任何改變,Audience類仍然是一個簡單的Java對象,能夠像以前一樣使用(在Spring中使用Bean進行配置)。
因為Audience類本身包含了所有它所需要定義的切點和通知,所以我們不在需要在XML配置中聲明切點和通知。為了讓Spring將Audience應用為一個切面,我們需要在Spring上下文中聲明一個自動代理Bean,該Bean知道如何把@AspectJ注解所標注的Bean轉變為代理通知。
為此,Spring自帶了名為AnnotationAwareAspectJProxyCreator的自動代理創建類。我們可以在Spring上下文中把AnnotationAwareAspectJProxyCreator注冊為一個Bean,但是這個類文字太長,不宜使用。因此,我們使用Spring的
aop空間提供的一個自定義配置元素()來代替前者,這個更易使用。
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
配置元素將在Spring上下文中創建一個AnnotationAwareAspectJProxyCreator類,它會自動代理一些Bean,這些Bean的方法需要與使用@AspectJ注解的Bean中所定義的切點相匹配。
運行結果:
theaudienceistakingtheirseats...
正在上演個人演唱會...
verygood,clapclapclap...
注意:
元素和@AspectJ注解都是把一個POJO轉變為一個切面的有效方式。但是相對@AspectJ的一個明顯優勢是:不需要實現切面功能的代碼(本例中是Audience類代碼)。通過@AspectJ,我們必須標注類和方法,它需要有源碼
1. 注解環繞通知
像Spring基於XML的AOP一樣,@AspectJ注解的使用不僅僅限於定義前置和後置通知類型。我們還可以創建環繞通知。
@Around("SingerPerform()")
public void PerformTime(ProceedingJoinPoint joinPoint){
// 演出之前
System.out.println("the audience is taking their seats...");
try {
long start = System.currentTimeMillis();
// 執行演出操作
joinPoint.proceed();
long end = System.currentTimeMillis();
// 演出成功
System.out.println("very good, clap clap clap...");
System.out.println("該演出共需要 "+(end - start) + " milliseconds");
} catch (Throwable e) {
// 演出失敗
System.out.println("very bad, We want our money back...");
e.printStackTrace();
}
}
在這裡,@Around注解標示了PerformTime()方法將被作為環繞通知應用與SingerPerform()切點。和之前使用XML方式唯一的區別就是用@Around注解所標注的。簡單的使用@Around注解來標注方法並不足以調用proceed()方法,因此,被環繞通知的方法必須接受一個ProceedingJoinPoint對象作為方法入參,並在對象上調用proceed()方法。
2. 傳遞參數給所標注的通知
之前我們曾經使用Spring基於XML的切面聲明為通知傳遞參數,而是@AspectJ注解為通知傳遞參數,與之相比並沒有太大的區別。
package com.sjf.bean;
/**
* 歌手實體類
* @author sjf0115
*
*/
public class Singer {
public void perform(String song) {
System.out.println("正在上演個人演唱會... " + song);
}
}
package com.sjf.bean;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
/**
* 主辦方實體類
* @author sjf0115
*
*/
@Aspect
public class Organizers {
// 定義切點
@Pointcut("execution(* com.sjf.bean.Singer.perform(String)) and args(song)")
public void SingerPerform(){
//
}
// 表演之前
@Before("SingerPerform() and args(song)")
public void BeforeSong(String song){
System.out.println("演唱會馬上就開始了,演唱歌曲為 " + song);
}
}
元素變為@Pointcut注解,元素變為@Before注解。
不知道下面配置出現報錯如何解決?求解.....
// 定義切點
@Pointcut("execution(* com.sjf.bean.Singer.perform(String)) and args(song)")
public void SingerPerform(String song){
//
}
// 表演之前
@Before("SingerPerform(song)")
public void BeforeSong(String song){
System.out.println("演唱會馬上就開始了,演唱歌曲為 " + song);
}
來源於:《Spring實戰》