Spring提供了4种类型的AOP支持:
- 基于代理的经典Spring AOP;
- 纯POJO切面;
- @AspectJ注解驱动的切面;
- 注入式AspectJ切面(适用于Spring各版本)
因为Spring基于动态代理,所以Spring只支持方法连接点。这与一些其他的AOP框架是不同的,例如AspectJ和JBoss,除了方法切点,它们还提供了字段和构造器接入点。
一、定义切点
在Spring AOP中,要使用AspectJ的切点表达式语言来定义切点。以下是AspectJ切点指示器:
- arg(),限制连接点匹配参数为指定类型的执行方法
- @args(),限制连接点匹配参数由指定注解标注的执行方法
- execution(),用于匹配是连接点的执行方法
- target,限制连接点匹配目标对象为指定类型的类
- @target(),限制连接点匹配特定的执行对象,这些对象对应的类要具有指定类型的注解
- within(),限制连接点匹配指定的类型
- @within(),限制连接点匹配指定注解所标注的类型(当使用Spring AOP时,方法定义在由指定的注解所标注的类里)
- @annotation,限定匹配带有指定注解的连接点
只有execution指示器是实际执行匹配的,而其他的指示器都是用来限制匹配的。
方法表达式以“*”号开始,表明了我们不关心方法返回值的类型。然后,我们指定了全限定类名和方法名。对于方法参数列表,我们使用两个点号(..)表明切点要选择任意的perform()方法,无论该方法的入参是什么。
1.用法:
execution( * concert.Performance.perform(..))
2.包含条件时怎么表示呢?
execution( * concert.Performance.perform(..)) && within(concert.*) //表示同时满足
因为“&”在XML中有特殊含义,所以在Spring的XML配置里面描述切点时,我们可以使用and来代替“&&”。同样,or 和not可以分别用来代替“||”和“!”。
3.Spring还引入了一个新的bean()指示器,它允许我们在切点表达式中使用bean的ID来标识bean。bean()使用bean ID或bean名称作为参数来限制切点只匹配特定的bean。:
execution( * concert.Performance.perform(..)) and bean("user") //表示同时满足
我们还可以使用非操作为除了特定ID以外的其他bean应用通知:
execution( * concert.Performance.perform(..)) and !bean("user") //表示同时满足
在此场景下,切面的通知会被编织到所有ID不为woodstock的bean中。
二、使用注解创建切面
AspectJ提供了五个注解来定义通知(要实现的切面):
- @After :通知方法会在目标方法返回或抛出异常后调用。
- @AfterReturning : 通知方法会在目标方法返回后调用。
- @AfterThrowing :通知方法会在目标方法抛出异常后调用。
- @Around : 通知方法会将目标方法封装起来。
- @Before : 通知方法会在目标方法调用之前执行。
import org.aspectj.lang.annotation.After;
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
public class Audience {
@Before("execution(* com.df.test.service.impl.CDPlayer.play(..))")
public void beforeAction(){
System.out.println("beforeAction");
}
@After("execution(* com.df.test.service.impl.CDPlayer.play(..))")
public void afterAction(){
System.out.println("afterAction");
}
@AfterReturning("execution(* com.df.test.service.impl.CDPlayer.play(..))")
public void afterReturningAction(){
System.out.println("afterReturningAction");
}
@AfterThrowing("execution(* com.df.test.service.impl.CDPlayer.play(..))")
public void afterThrowingAction(){
System.out.println("afterThrowingAction");
}
}
我们完全可以这样做:@Pointcut注解能够在一个@AspectJ切面内定义可重用的切点,从而减少代码的重复量。
package com.df.test.service.impl;
import org.aspectj.lang.annotation.After;
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 Audience {
@Pointcut("execution(* com.df.test.service.impl.CDPlayer.play(..))")
public void Action(){
}
@Before("Action()")
public void beforeAction(){
System.out.println("beforeAction");
}
@After("Action()")
public void afterAction(){
System.out.println("afterAction");
}
@AfterReturning("Action()")
public void afterReturningAction(){
System.out.println("afterReturningAction");
}
@AfterThrowing("Action()")
public void afterThrowingAction(){
System.out.println("afterThrowingAction");
}
}
光有切面和切点是不够了,连接点(一个很基础的类CDPlayer,包含play方法)这里就不展示了。还要讲切面注入到Spring容器中去,并使用@EnableAspectJAutoProxy 注解,表示启用自动代理功能,xml用<aop:aspectj-autoproxy>来启用自动代理功能。
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
@ComponentScan(basePackageClasses = {SgtPeppers.class})
@EnableAspectJAutoProxy // 启用自动代理功能
public class CDPlayerConfig {
@Bean
public Audience getAd(){
return new Audience();
}
}
当然,我们也可以使用@Around环绕通知,就可以代替以上所有通知能干的事情:
...
import org.aspectj.lang.ProceedingJoinPoint;
@Aspect
public class Audience {
@Pointcut("execution(* com.df.test.service.impl.CDPlayer.play(..))")
public void Action(){
}
@Around("Action()")
public void aroundAction(ProceedingJoinPoint jp) { //必须要这个参数
try {
System.out.println("beforeAction");
jp.proceed(); //执行连接点的方法,也就是play()方法
System.out.println("afterAction");
} catch (Throwable e) {
// TODO Auto-generated catch block
System.out.println("afterThrowingAction");
e.printStackTrace();
}
}
}
有时候我们要拦截参数,做一些其他操作,可以这样完成:
package com.df.test.service.impl;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class Audience {
@Pointcut("execution(* com.df.test.service.impl.CDPlayer.play(..))")
public void Action(){
}
//可能在切点被执行之前,需要对传入的参数进行处理
@Before("Action() && args(prams3)")
public void beforeAction(String prams3){ //args(prams3)参数名称必须与形参保持一致
System.out.println("beforeAction" + prams3);
System.out.println("beforeAction");
}
@After("Action()")
public void afterAction(){
System.out.println("afterAction");
}
//对结果进行处理。ps:这里本人实现时,result一直是null,暂时没弄清楚原因,知道的同学请不吝赐教
@AfterReturning(pointcut = "Action()" ,returning = "result")
public void afterReturningAction(String result){ //returning = "result",名称要与形参保持一致
System.out.println("afterReturningAction" + result);
}
//要是跑异常,可以拦截起来做处理
@AfterThrowing(pointcut = "Action()" ,throwing = "e")
public void afterThrowingAction(Exception e){
System.out.println("afterThrowingAction" +e.getMessage());
}
}
SpringAOP的基本使用,到这里已经完成了。后面将会继续探讨通过AOP思想所引入的新功能以及AspectJ的使用。