Aspect定义
在 Spring 中使用 Aspect 需要使用 @Component 直接将其标记为一个 Bean
并且使用 @Aspec 注解将其标记为一个切面
然后在该类中定义上面我们说的切点,通知等
Pointcut
定义
@Pointcut("execution(public void WeixinService.share(String))")
public void shareCut() {
}
切点定义在方法上,并使用 @Pointcut 注解,注解中的值便是切点的表达式
切点的名称就是方法的名称,这里是 shareCut(),注意这里有括号
若要将具体的通知 Advice 关联的某个切点上,在 Advice 的注解上写上切点的名称就可以了,如下
@AfterReturning("shareCut()") public void log(JoinPoint joinPoint) { System.out.println(joinPoint.getSignature() + " executed"); }
execution 表达式
- 任意公共方法的执行:execution(public * *(…))
- 任何一个以“set”开始的方法的执行:execution(* set*(…))
AccountService接口的任意方法的执行:execution(* com.xyz.service.AccountService.*(…)) - 定义在service包里的任意方法的执行:execution(* com.xyz.service..(…))
- 定义在service包或者子包里的任意方法的执行:execution(* com.xyz.service….(…))
- 在service包里的任意连接点(在Spring AOP中只是方法执行) :within(com.xyz.service.*)
- 在service包或者子包里的任意连接点(在Spring AOP中只是方法执行) :within(com.xyz.service…*)
指示器
切点的表达式以 指示器 开始, 指示器 就是一种关键字,用来告诉 Spring AOP 如何匹配连接点,Spring AOP 提供了以下几种指示器
execution
within
this 和 target
args
@target
@annotation
- execution
匹配方法执行连接点,即匹配哪个方法执行
@Pointcut("execution(public String aaric.springaopdemo.UserDao.findById(Long))")
上面这个切点会匹配在 UserDao 类中 findById 方法的调用,并且需要该方法是 public 的,返回值类型为 String,只有一个 Long 的参数。 切点的表达式同时还支持宽字符匹配,如
@Pointcut("execution(* aaric.springaopdemo.UserDao.*(..))")
上面的表达式中,第一个宽字符 * 匹配 任何返回类型,第二个宽字符 * 匹配 任何方法名,最后的参数 (…) 表达式匹配 任意数量任意类型 的参数,也就是说该切点会匹配类中所有方法的调用。
- within
如果要匹配一个类中所有方法的调用,便可以使用 within 指示器
@Pointcut(“within(aaric.springaopdemo.UserDao)”)
这样便可以匹配该类中所有方法的调用了。同时,我们还可以匹配某个包下面的所有类的所有方法调用,如下面的例子
@Pointcut(“within(aaric.springaopdemo…*)”)
- this 和 target
如果目标对象实现了任何接口,Spring AOP 会创建基于CGLIB 的动态代理,这时候需要使用 target 指示器
如果目标对象没有实现任何接口,Spring AOP 会创建基于JDK的动态代理,这时候需要使用 this 指示器
@Pointcut(“target(aaric.springaopdemo.A)”) A 实现了某个接口 @Pointcut(“target(aaric.springaopdemo.B)”) B 没有实现任何一个接口
- args
该指示器用来匹配具体的方法参数
@Pointcut(“execution(* …find(Long))”)
这个切点会匹配任何以 find 开头并且只有一个 Long 类型的参数的方法。 如果我们想匹配一个以 Long 类型开始的参数,后面的参数类型不做限制,我们可以使用如下的表达式
@Pointcut(“execution(* …find(Long,…))”)
- @target
该指示器不要和 target 指示器混淆,该指示器用于匹配连接点所在的类是否拥有指定类型的注解,如
@Pointcut("@target(org.springframework.stereotype.Repository)")
- @annotation
该指示器用于匹配连接点的方法是否有某个注解
@Pointcut("@annotation(org.springframework.scheduling.annotation.Async)")
- 组合切点表达式
切点表达式可以通过 &&、 || 和 !等操作符来组合,如
@Pointcut("@target(org.springframework.stereotype.Repository)")
public void repositoryMethods() {}
@Pointcut(“execution(* …create(Long,…))”)
public void firstLongParamMethods() {}
@Pointcut(“repositoryMethods() && firstLongParamMethods()”)
public void entityCreationMethods() {}
Advice
定义
Advice 通知,即在连接点处要执行的代码,分为以下几种类型:
Around
Before
After
开启Advice
如果要在 Spring 中使用 Spring AOP 需要开启 Advice,使用 @EnableAspectJAutoProxy 注解就可以了,代码如下
@SpringBootApplication
@EnableAspectJAutoProxy
public class SpringAopDemoApplication {
}
Before Advice
Before Advice 用来执行在方法调用之前的操作,如果 Before Advice 在执行的过程中抛出异常的话,那么连接点的方法就不会被执行。
@Aspect
@Component
public class PrintAspect {
@Pointcut("@target(org.springframework.stereotype.Repository)")
public void repositoryMethods() {};
@Before("repositoryMethods()")
public void logMethodCall(JoinPoint jp) {
String name = jp.getSignature().getName();
System.out.println("Before " + name);
}
}
logMethodCall Advice 会在任何 Repository 方法执行之前调用
After Advice
顾名思义,该 Advice 会在方法被调用之后被执行,但是有三种注解可以使用:
@AfterReturing:该 Advice 会在方法正常返回以后执行
@AfterThrowing: 该 Advice 会在方法抛出异常以后执行
@After: 该 Advice 无论如何,在方法执行以后都会执行
Around Advice
这个是最有用的 Advice, 它可以控制方法在执行前后的行为。 它可以选择是否继续执行连接点的方法,或者中断该方法的执行,而返回自己的返回值。
自定义AOP Annitation
- 创建Annotation
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CalculateExecuteTime {
} - 创建切面
@Aspect
@Component
public class CalculateExecuteTimeAspect {
}
-
创建切点和通知
@Aspect
@Component
public class CalculateExecuteTimeAspect {
@Around("@annotation(CalculateExecuteTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object proceed = joinPoint.proceed();
long executionTime = System.currentTimeMillis() - start;
System.out.println(joinPoint.getSignature() + " executed in " + executionTime + “ms”);
return proceed;
}
} -
在方法上加上自定义注解
@Service
public class WeixinService {@CalculateExecuteTime
public void share(@NotNull String articleUrl) {
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception ignored) {}
}
}