AspectJ面向註解的模型可以非常簡便地通過少量註解把任意類轉變成切面。這種新特性通常稱爲@AspectJ。
package com.springinaction.springidol;
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;
@Aspect
public class AudienceOfAspectJ
{
@Pointcut("execution(* com.springinaction.springidol.Performer.perform(..))")
public void performance()
{
}
@Before("performance()")
public void takeSeats()
{
System.out.println("The audience is taking their seats.");
}
@Before("performance()")
public void turnOffCellPhones()
{
System.out.println("The audience is turning off their cellphones.");
}
@AfterReturning("performance()")
public void applaud()
{
System.out.println("CLAP CLAP CLAP CLAP");
}
@AfterThrowing("performance()")
public void demandRefund()
{
System.out.println("Boo!We want our money back!");
}
}
@Pointcut註解用於定義一個可以在@AspectJ切面內可重用的切點。
切點的名稱來源於註解所應用的方法名稱。因此,該切點的名稱爲performance()。
因爲AudienceOfAspectJ類本身包含了所有它需要定義的切點和通知,所有我們不再需要在XML配置中聲明切點和通知。我們需要在Spring上下文中聲明一個自動代理Bean,該Bean知道如何把@AspectJ註解所標註的Bean轉變爲代理通知。
爲此,Spring自帶了名爲AnnotationAwareAspectJAutoProxyCreator的自動代理創建類。我們可以在Spring上下文中把AnnotationAwareAspectJAutoProxyCreator註冊爲一個Bean。
爲了簡化如此長的名字,Spring在aop命名空間中提供了一個自定義的配置元素:<aop:aspectj-autoproxy />
我們需要記住<aop:aspectj-autoproxy />
僅僅使用@AspectJ註解作爲指引來創建基於代理的切面,但本質上它仍然是一個Spring風格的切面。這意味着雖然我們使用@AspectJ的註解,但是我們仍然限於代理方法的調用。如果想利用AspectJ的所有能力,我們必須在運行時使用AspectJ並不依賴Spring來創建基於代理的切面。
<aop:aspect>
相對於@AspectJ的一個明顯優勢是我們不需要實現切面功能的源碼。通過@AspectJ,我們必須標註類和方法,它需要有源碼。而<aop:aspect>
可以引用任意一個Bean。
1.註解環繞通知
@Around("performance()")
public void watchPerformance(ProceedingJoinPoint joinpoint)
{
try
{
System.out.println("The audience is taking their seats.");
System.out.println("The audienct is turning off their cellphones.");
long start = System.currentTimeMillis();
joinpoint.proceed();
long end = System.currentTimeMillis();
System.out.println("CLAP CLAP CLAP CLAP");
System.out.println("The performance took " + (end - start) + " milliseconds.");
}
catch (Throwable e)
{
System.out.println("Boo!We want out money back!");
}
}
2.傳遞參數給標註的通知
package com.springinaction.springidol;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class MagicianOfAspectJ implements MindReader
{
private String thoughts;
@Pointcut("execution(* com.springinaction.springidol.Thinker.thinkOfSomething(String)) && args(thoughts)")
public void thinking(String thoughts)
{
}
@Before("thinking(thoughts)")
public void interceptThoughts(String thoughts)
{
System.out.println("Intercepting volunteer's thoughts");
this.thoughts = thoughts;
}
public String getThoughts()
{
return thoughts;
}
}
3.標註引入
等價於<aop:declare-parents>
的註解是@AspectJ的@DeclareParents。
package com.springinaction.springidol;
import org.aspectj.lang.annotation.DeclareParents;
public class ContestantIntroducer
{
@DeclareParents(value = "com.springinaction.springidol.Performer", defaultImpl = GraciousContestant.class)
public static Contestant contestant;
}
<bean class="com.springinaction.springidol.ContestantIntroducer" />
ContestantIntroducer是一個切面。但是不同於我們之前所創建的那些切面,該切面並沒有提供前置、後置或環繞通知。它爲Performer Bean引入了Contestant接口。
value屬性等同於<aop:declare-parents>
的types-matching屬性。它標識應該被引入指定接口的Bean的類型。
defaultImpl屬性等同於<aop:declare-parents>
的default-impl屬性。它標識該類提供了所引入接口的實現。
由@DeclareParents註解所標註的static屬性指定了將被引入的接口。
當發現使用@Aspect註解所標註的Bean時,<aop:aspectj-autoproxy />
將自動創建代理。依據被調用的方式是屬於被代理的Bean還是引入的接口,該代理把調用委託給被代理的Bean或引入的實現。
我們需要關注的一個注意事項是@DeclareParents沒有對應於<aop:declare-parents>
的delegate-ref屬性所對應的等價物。這是因爲@DeclareParents是一個@AspectJ註解。@AspectJ是一個不同於Spring的項目,因此它的註解並不瞭解Spring的Bean。這意味着如果我們想委託給Spring所配置的Bean,那@DeclareParents並不能滿足需求,我們只能藉助於<aop:declare-parents>
。
Spring AOP有助於把橫切關注點從應用的業務邏輯中分離出來。但是正如我們所看到的,Spring切面仍然只是基於代理的,而且限於通知方法的調用。