一、介绍
1.1、大概流程:
创建切面类 -> 设置通知类型 -> 设置切入点
1.2、各种通知
- 前置通知:使用
@Before注解标识,在被代理的目标方法 前 执行 - 返回通知:使用
@AfterReturning注解标识,在被代理的目标方法 成功结束 后执行(寿终正寝) - 异常通知:使用
@AfterThrowing注解标识,在被代理的目标方法 异常结束 后执行(死于非命) - 后置通知:使用
@After注解标识,在被代理的目标方法 最终结束 后执行(盖棺定论) - 环绕通知:使用
@Around注解标识,使用try...catch...finally结构围绕 整个 被代理的目标方法,包括上面四种通知对应的所有位置
各种通知的执行顺序:
Spring版本5.3.x以前:
- 前置通知
- 目标操作
- 后置通知
- 返回通知或异常通知
Spring版本5.3.x以后:
- 前置通知
- 目标操作
- 返回通知或异常通知
- 后置通知
1.2.3、环绕通知
环绕通知是所有通知类型中功能最为强大的, 能够全面地控制连接点. 甚至可以控制是否执行连接点
☆ 对于环绕通知来说, 连接点的参数类型必须是 ProceedingJoinPoint . 它是 JoinPoint 的子接口, 允许控制何时执行, 是否执行连接点.
☆ 在环绕通知中 需要明确调用 ProceedingJoinPoint 的 proceed() 方法 来执行被代理的方法. 如果忘记这样做就会导致通知被执行了, 但目标方法没有被执行.
☆ 注意: 环绕通知的方法需要返回目标方法执行之后的结果, 即调用 joinPoint.proceed(); 的返回值, 否则会出现空指针异常
1.4、切入点表达式语法
切入点使用示例:
/**
* 设置前置通知
* @Before(value = "切入点表达式,用来配置切入点")
* 切入点表达式:execution(访问修饰符 增强方法返回类型 增强方法所在类全路径.方法名称(参数列表))
*/
// 表示:访问修饰符为public 方法返回类型为int CalculatorImpl类下的所有方法 任意参数--------给这些方法设置切入点
@Before("execution(public int com.atguigu.aop.annotation.CalculatorImpl.*(..))")
// 表示:任意的访问修饰符+任意的返回类型 annotation包中的所有类中的方法 任意参数--------给这些方法设置切入点
@Before("execution(* com.atguigu.aop.annotation.*.*(..))")
1.4.1、重用切入点表达式
①声明
@Pointcut("execution(* com.atguigu.aop.annotation.*.*(..))")
public void pointCut(){}
②在同一个切面中使用
@Before("pointCut()")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}
③在不同切面中使用
@Before("com.atguigu.aop.CommonPointCut.pointCut()")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}
1.4.2、切面优先级
相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序。
使用@Order注解可以控制切面的优先级:
- @Order(较小的数):优先级高
- @Order(较大的数):优先级低
1.4.3、@Pointcut常用的两种方式
- ① execution - 使用execution(方法表达式)匹配方法执行。
- ② @annotation -
@Aspect
public class AspectTest12 {
// 当被调用的目标方法上有@Ann12注解的时,会被beforeAdvice处理
@Pointcut("@annotation(com.javacode2018.aop.demo9.test12.Ann12)")
public void pc() {
}
@Before("pc()")
public void beforeAdvice(JoinPoint joinPoint) {
System.out.println(joinPoint);
}
}
二、示例
1、添加依赖
<dependencies>
<!--spring context依赖-->
<!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.2</version>
</dependency>
<!--spring aop依赖(☆)-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>6.0.2</version>
</dependency>
<!--spring aspects依赖(☆)-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>6.0.2</version>
</dependency>
</dependencies>
2、创建切面类
// @Aspect表示这个类是一个切面类
@Aspect
// @Component注解保证这个切面类能够放入IOC容器
@Component
@Order(3) //指定切面的优先级. 值越小,优先级越高. 标注@Order的切面比不标注@Order切面的优先级高
public class LogAspect {
@Pointcut("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))")
public void pointcut() {
}
/**
* 设置前置通知
* @Before(value = "切入点表达式,用来配置切入点")
* 切入点表达式:execution(访问修饰符 增强方法返回类型 增强方法所在类全路径.方法名称(参数列表))
*
* 通知的方法中可以通过 JoinPoint 获取到切入点中的相关参数
*/
@Before("execution(public int com.atguigu.aop.annotation.CalculatorImpl.*(..))")
public void beforeMethod(JoinPoint joinPoint){
// 获取增强方法的名称
String methodName = joinPoint.getSignature().getName();
// 获取增强方法的参数列表
String args = Arrays.toString(joinPoint.getArgs());
System.out.println("Logger-->前置通知,方法名称:"+methodName+", 参数列表:" + args);
// 输出-->>:Logger-->前置通知,方法名称:add, 参数列表:[2,3]
}
@After("pointcut()")
public void afterMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
System.out.println("Logger-->后置通知,方法名:"+methodName);
}
@AfterReturning(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", returning = "result")
public void afterReturningMethod(JoinPoint joinPoint, Object result){
String methodName = joinPoint.getSignature().getName();
System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result);
}
@AfterThrowing(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", throwing = "ex")
public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){
String methodName = joinPoint.getSignature().getName();
System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex);
}
@Around("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))")
public Object aroundMethod(ProceedingJoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
Object result = null;
try {
System.out.println("环绕通知-->目标对象方法执行之前");
// 调用目标方法
result = joinPoint.proceed();
System.out.println("环绕通知-->目标对象方法返回值之后");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("环绕通知-->目标对象方法出现异常时");
} finally {
System.out.println("环绕通知-->目标对象方法执行完毕");
}
return result;
}
}