面向切面编程(AOP)是面向对象编程(OOP)的补充和完善。
核心知识点主要包括以下几个方面:
-
连接点(Joinpoint):程序执行过程中的一些点,比如方法调用,异常处理等。在AOP中,所有方法都被视为连接点,无论是接口里的抽象方法还是实现类里的重写方法。
-
切入点(Pointcut):可能被抽取共性功能的方法称为切入点。切入点是连接点的子集。
-
通知(Advice):将抽取出来的共性功能称为通知。它可以在方法的调用之前、之后或者抛出异常时执行。
-
AOP代理:AOP实现的关键在于AOP框架自动创建的AOP代理,主要分为静态代理和动态代理。
-
切面(Apect):描述通知与切入点的对应关系(通知+切入点)
-
目标对象(Target):通知所作用的对象
通知类型:
-
环绕通知(@Around):环绕通知是最为强大的一种通知类型,可以在切入点方法的执行前后进行自定义操作。例如,可以进行权限校验、日志记录等功能。
-
前置通知(@Before):这种通知在切入点方法之前执行。它可以用于权限检查、日志记录等功能。
-
后置通知(@After):这种通知在切入点方法之后执行。常常用于资源清理、性能统计等功能。
-
返回通知(@AfterReturning):这种通知在切入点方法成功执行之后执行。常用于结果处理、缓存更新等场景。
-
异常通知(@AfterThrowing):在切入点方法抛出异常后执行。通常用于错误处理、事务回滚等操作。
注意事项:
- @Around环绕通知需要自己调用 ProceedingJoinPoint.proceed() 来让原始方法执行,其他通知不需要考虑目标方法执行
- @Around环绕通知方法的返回值,必须指定为Object,来接收原始方法的返回值。
@Slf4j
@Component
@Aspect
public class MyAspect1 {
@Pointcut("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
private void pt(){}
@Before("pt()")
public void before() {
log.info("before ...");
}
@Around("pt()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
log.info("around before...");
//调用目标对象的原始方法执行
Object result = proceedingJoinPoint.proceed();
log.info("around after...");
return result;
}
@After("pt()")
public void after() {
log.info("after ...");
}
@AfterReturning("pt()")
public void afterReturning() {
log.info("afterReturning ...");
}
@AfterThrowing("pt()")
public void afterThrowing() {
log.info("afterThrowing ...");
}
}
@PointCut
该注解的作用是将公共的切点表达式抽取出来,需要时引用到该切点表达式即可
原始代码:
@Slf4j
@Component
@Aspect
public class MyAspect1 {
@Pointcut("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
private void pt(){}
@Before("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
public void before() {
log.info("before ...");
}
@Around("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
log.info("around before...");
//调用目标对象的原始方法执行
Object result = proceedingJoinPoint.proceed();
log.info("around after...");
return result;
}
@After("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
public void after() {
log.info("after ...");
}
@AfterReturning("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
public void afterReturning() {
log.info("afterReturning ...");
}
@AfterThrowing("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
public void afterThrowing() {
log.info("afterThrowing ...");
}
}
引入@PointCut后:
@Slf4j
@Component
@Aspect
public class MyAspect1 {
@Pointcut("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
public void pt(){}
@Before("pt()")
public void before() {
log.info("before ...");
}
@Around("pt()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
log.info("around before...");
//调用目标对象的原始方法执行
Object result = proceedingJoinPoint.proceed();
log.info("around after...");
return result;
}
@After("pt()")
public void after() {
log.info("after ...");
}
@AfterReturning("pt()")
public void afterReturning() {
log.info("afterReturning ...");
}
@AfterThrowing("pt()")
public void afterThrowing() {
log.info("afterThrowing ...");
}
}
通知的执行顺序:
1.不同的切面类中,默认按照切面类的类名字母排序:
- 目标方法前的通知方法:字母排名靠前的先执行
- 目标方法后的通知方法:字母排名靠前的后执行
以上内容基本一致,只是改变输出序号:
2.用@Order(数字) 加在切面类上来控制顺序
- 目标方法前的通知方法:数字小的先执行
- 目标方法后的通知方法:数字小的后执行
加上这样的注解:
切入点表达式:
execution:主要跟据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,语法为:
execution(修饰符? 返回类型 包名.类名.?方法名(参数列表) throws 异常?)
注意:带?的可以省略;
* :代表独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数,也可以是通配包、类、方法名的一部分
.. : 多个连续的任意符号,可以通配任意层级的包,或者任意类型、任意个数的参数
@annotation(注解全类名)
在Java中,注解(Annotation)是一种用于为代码提供元数据的机制。它可以用于标记类、方法、字段等元素,以便在编译时或运行时进行额外的处理。
案例:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyLog {
}
@Component
@Slf4j
@Aspect
public class MyAspect7 {
// @Pointcut("execution(public void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))")
@Pointcut("@annotation(com.itheima.aop.MyLog)")
private void pt(){}
@Before("pt()")
public void before(){
log.info("MyAspect7 ... before ...");
}
}
运行结果: