1、切点
Spring使用AspectJ的切点表达式语言来定义切点,但是仅支持AspectJ切点指示器的一个子集:
编写切点例子:
execution(* concert.Performance.perform(..))
execution(* concert.Performance.perform(..)) && within(concert.*))
Spring还引入了bean()指示器,可以限定切点只匹配特定的bean,例子:
execution(* concert.Performance.perform()) and !bean('woodstock')
2、切面
切面=切点+通知,通知定义何时以及工作的内容。
Spring使用AspectJ注解来声明通知方法:
@Pointcut注解能够在一个@AspectJ切面定义内定义可重用的切点。
定义切面的例子:
@Aspect
public class Audience {
@Pointcut("execution(** concert.Performance.perform(..))")
public void performance() {}
@Before("performance()")
public void silenceCellPhones() {
System.out.println("Silencing cell phones");
}
@Before("performance()")
public void takeSeats() {
System.out.println("Taking seats");
}
@AfterReturning("performance()")
public void applause() {
System.out.println("CLAP CLAP CLAP!!!");
}
@AfterThrowing("performance()")
public void demandRefund() {
System.out.println("Demanding a refund");
}
}
启动自动代理功能(javaConfig):
@Configuration
@EnableAspectJAutoProxy
@ComponentScan
public class ConcertConfig {
@Bean
public Audience audience() {
return new Audience();
}
}
启动自动代理功能(XML,首先声明aop命名空间):
<aop:aspectj-autoproxy />
Spring的AspectJ自动代理仅仅使用@AspectJ作为创建切面的指导,切面依然是基于代理的。在本质上,它依然是Spring基于代理的切面,尽管使用的是@AspectJ注解,仍然限于代理方法的调用。如果想利用AspectJ的所有能力,必须在运行时使用AspectJ并且不依赖Spring来创建基于代理的切面。
3、环绕通知
例子:
@Aspect
public class Audience {
@Pointcut("excution(** concert.Performance.perform(..))")
public void performance() {}
@Around("performance()")
public void watchPerformance(ProceedingJoinPoint jp) {
try {
System.out.println("Silencing cell phones");
System.out.println("Taking seats");
jp.proceed();
System.out.println("CLAP CLAP CLAP!!!");
} catch (Throwable e) {
System.out.println("Demanding a refund");
}
}
}
对于proceed()方法,既可以多次调用实现重试逻辑,也可以不进行调用从而阻塞对被通知方法的访问。
4、通知中的参数
例子:
public class TrackCounter {
private Map<Integer, Integer> trackCounts = new HashMap<Integer, Integer>();
@Pointcut("excution(* soundsystem.CompactDisc.playTrack(int)) && args(trackNumber)")
public void trackPlayed(int trackNumber) {}
@Before("trackPlayed(trackNumber)")
public void countTrack(int trackNumber) {
int currentCount = getPlayCount(trackNumber);
trackCounts.put(trackNumber, currentCount + 1);
}
public int getPlayCount(int trackNumber) {
return trackCounts.containsKey(trackNumber)? trackCounts.get(trackNumber) : 0;
}
}
切点定义中的参数与切点方法中的参数名称相一致,通过注解传递到通知方法中。
5、通过注解引入新功能
利用被称为引入的AOP概念,切面可以为Spring bean添加新方法。
例子:
public interface Encoreable{
void performEncore();
}
@Aspect
public class EncoreableIntroducer{
@DeclareParents(value="concert.Performance+",defaultImpl=DefaultEncoreable.class)
public static Encoreable encoreable;
}
@DeclareParents注解由三部分组成:
- value属性指定了哪种类型的bean要引入该接口,例子中,标记符后面的加号表示是Performance的所有子类型。
- defaultImpl属性指定了为引入功能提供实现的类。
- @DeclareParents注解所标注的静态属性指明了要引入的接口