面向切面编程,面向特定方法编程,以方法为对象,在不修改原方法的基础上,对方法进行操作扩展等,底层是通过动态代理实现的
使用开发步骤:
1、创建一个类,加上@Aspect声明为一个AOP切面类,再加上@Component交给IOC容器来管理
2、定义方法
3、方法上加上通知类型注解,注解内使用切入点表达式,指定要被代理的方法路径以及返回值类型 ,重点掌握 @Around环绕通知
execution切入点表达式:
execution(访问修饰符? 返回值类型 包名.类名? .方法名(方法参数的类型) throws 异常?)
,如:
@Around("execution(* com.cxl.service.*.*(..))")
带?表示可以省略的部分, *为通配符,单个独立的任意符号 ..为多个连续的任意符号,此处表示方法的形参类型和个数是任意的,也可以表示任意层级 根据业务需要可以使用&& || !来组合复杂的切入点表达式
@annotation切入点表达式: 用于 匹配标识有特定注解的方法
@annotation(注解全类名)
@Around("@annotation(com.itheima.anno.Log)")
//匹配标识有Log注解的方法,com.itheima.anno.Log为注解的全类名
**连接点:JoinPoint**,可以被AOP控制的方法(暗含方法执行时的相关信息)
**通知:Advice**,指那些重复的逻辑,也就是共性功能(最终体现为一个方法,如下方统计方法的耗时的方法)
**切入点:PointCut**,匹配连接点的条件,通知仅会在切入点方法执行时被应用
**切面:Aspect**,描述通知与切入点的对应关系(通知+切入点)
**目标对象:Target**,通知所应用的对象
注意事项:
1、@Around环绕通知需要自己调用ProceedingJoinPoint.proceed()来让原始方法执行,其他通知不需要考虑目标方法执行
2、@Around环绕通知方法的返回值,必须指定Object,来接收原始方法的返回值
3、在Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等。
对于@Around通知,获取连接点信息只能使用ProceedingJoinPoint
对于其他四种通知,只能使用JoinPoint,它是ProceedingJoinPoint的父类型
具体代码示例如下:
@Slf4j
@Component
@Aspect
public class TimeAspect {
@Around环绕通知,切入点表达式
@Around("execution(* com.cxl.service.*.*(..))")
public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();//调用原方法
long end = System.currentTimeMillis();
log.info(joinPoint.getSignature()+"方法执行耗时:{}毫秒",end-start);
return result;
}
@After("execution(* com.cxl.service.DeptServiceImpl.*(..))")
public void after(JoinPoint point){
//1、获取目标对象的类名
String className = point.getTarget().getClass().getName();
//2、获取目标方法的方法名
String methodName = point.getSignature().getName();
//3、获取目标方法运行时传入的参数
Object[] = point.getArgs();
//4、放行 目标方法运行
//5、获取目标方法运行的返回值
Object result = point.proceed();
log.info("after...");
}
}
当切入点表达式都相同时,每个方法都写一遍太繁琐,可以使用
@PointCut注解
1、声明一个无意义方法,在此方法上加上
@PointCut注解,注解内填上重复的切入点表达式
2、其他需要使用该切入点表达式的方法在通知类型注解内填上该无意义方法的方法名()即可,其他切面类也可以用,与权限修饰符相对应,public,private
代码示例:
@Slf4j
@Component
@Aspect
public class TimeAspect {
@Pointcut("execution(* com.cxl.service.DeptServiceImpl.*(..))")
public void poinCut(){
}
@Around("poinCut()")
public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
long end = System.currentTimeMillis();
log.info(joinPoint.getSignature()+"方法执行耗时:{}毫秒",end-start);
return result;
}
@AfterReturning("poinCut()")
public void afterReturning(){
log.info("afterReturning...");
}
@AfterThrowing("poinCut()")
public void afterThrowing(){
log.info("afterThrowing...");
}
@Before("poinCut()")
public void before(){
log.info("before...");
}
@After("poinCut()")
public void after(){
log.info("after...");
}
}
通知执行顺序
当有多个切面的切入点都匹配到了目标方法,目标方法运行时,多个通知方法都会被执行
执行顺序是按照切面类类名的字母排序来执行的
目标方法前的通知方法:字母排名靠前的先执行
目标方法后的通知方法:字母排名靠后的先执行
可以用 @Order注解加在切面类上来控制执行顺序
@Order(数字)
目标方法前的通知方法:数字小的先执行
目标方法后的通知方法:数字大的先执行