AOP 思想是和 Java 中的代理模式差不多,就是在不改变原有代码的情况下,对某些方法的功能进行增强。
其中关键的概念有:连接点、切入点、切面、通知;
连接点:需要被增强功能的方法,以及通知所包围的原有代码段;
切入点:需要被增强的方法;
切面:描述切入点和通知的关系,具体地指描述某个切入点需要哪个通知;
通知:就是一些不变的代码段,需要给多个切入点添加的功能相同的代码;
1)切入点一定是连接点,连接点不一定是切入点。
2)想要使用注解的形式使用 AOP,必须在配置类中声明注解@EnableAspectJAutoProxy
在学习中使用到的并不是 Spring 原生的 AOP,而是使用第三方的实现–AspectJ
package cn.edu.njust.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* -- coding: UTF-8 -- *
*
* @author wangs
* @description:
* EnableAspectJAutoProxy: 开启注解格式的AOP功能
* @date 2023/11/15 10:07
*/
@Configuration
@ComponentScan("cn.edu.njust")
@EnableAspectJAutoProxy
public class SpringConfiguration {
}
即切入点方法和通知的代码谁先执行,谁后执行;
前置通知**@Before**
:在执行切入点方法前执行;
后置通知**@After**
:在执行切入点方法后执行;
环绕通知**@Around**
:在执行切入点方法前后执行,需要使用到 ProceedingJoinPoint 参数调用切入点方法,否则切入点方法不能执行;
返回后通知**@AfterReturning**
:正常执行切入点方法后执行;
抛出异常后通知**@AfterThrowing**
:一般用在程序 try-catch 块中捕捉到异常的时候执行;
切入点表达式声明在一个没有返回值类型,没有形参,没有方法实体的方法上,表达式会指定目标的访问权限(可省略)、返回值类型、权限类名(可按照一定规则简写)、抛出的异常类型(可省略);
package cn.edu.njust.advice;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* -- coding: UTF-8 -- *
*
* @author wangs
* @description: 定义通知
* 需要将通知交给IoC容器管理
* 需要将其声明为是一个切面
* @date 2023/11/15 10:17
*/
@Component
@Aspect
public class MyAdvice {
/*
* 定义切入点
* 切入点方法:无返回值、无参数、无方法实体
* */
// @Pointcut("execution(void cn.edu.njust.dao.impl.UserDaoImpl.update())")
@Pointcut("execution(void cn.edu.njust.dao.UserDao.update())")
public void pt(){}
/*
* 定义切面:即将切入点与通知关联
* method(): 这个方法本身是一个通知
*
* */
@Before("pt()")
public void method() {
System.out.println(System.currentTimeMillis());
}
}
说明:
1)@Component:定义的切面需要交给 IoC 容器管理;
2)@Aspect:使用注解的形式需要指明这是一个切面;
3) @Before(“pt()”):如上所示,method()就是这个案例中的“通知”,pt()是切入点,而实际的切入点在 pt()方法上使用@Pointcut("execution(void cn.edu.njust.dao.UserDao.update())")
指明,直接定位到具体的方法;
1)基本使用
// 方式1:描述到具体实现类具体方法名称
@Pointcut("execution(void cn.edu.njust.dao.impl.UserDaoImpl.update())")
// 方式2:描述到接口类接口方法
@Pointcut("execution(void cn.edu.njust.dao.UserDao.update())")
2)语法格式详细
标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数) 异常名)
2.1)动作关键词:execution,表示执行到具体的切入点;
2.2)访问修饰符:一般省略不写,自己通过测试发现,如果在这里将权限改小,是不能正常将切入点和通知关联的;
2.3)返回值:与切入点的返回值保持相同;
2.4)全类名+方法名:切入点的权限类名+具体方法名称;
2.5)参数:如果切入点有参数,则需将参数的类型列举出来,如 int、String 等等;
2.6)异常名:指明抛出的异常类型,一般省略不写;
3)切入点表达式的通配符
// * :单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现
// 匹配com.itheima包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法
execution(public * com.itheima.*.UserService.find*(*))
// ..:多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写
// 匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法
execution(public User com..UserService.findById(..))
// +:专用于匹配子类类型
// 这个使用率较低,描述子类的,咱们做JavaEE开发,继承机会就一次,使用都很慎重,所以很少用它。*Service+,表示所有以Service结尾的接口的子类。
execution(* *..*Service+.*(..))
1)环绕通知获取参数:调用 ProceedingJoinPoint 中的 getArgs() 方法即可获取一次参数的数组;
@Around("pt()")
public void aroundMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("---------- 环绕通知 ----------");
System.out.println("切入点前执行");
// 获取参数
proceedingJoinPoint.getArgs();
System.out.println("切入点后执行");
}
2)非环绕通知获取参数:在通知方法上使用参数 JoinPoint,调用该对象的 getArgs() 方法也可以获取对应的参数;
使用方式和环绕行类似,不在演示代码
能获取返回值的通知就两种,一种是环绕通知,另一种是返回后通知。
1)环绕通知:和其他正常方法一样,调用 ProceedingJoinPoint 中的 proceed() 方法,如果切入点方法有返回值,就会有返回值,直接正常返回该参数即可;
@Around("pt()")
public void aroundMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("---------- 环绕通知 ----------");
System.out.println("切入点前执行");
// 获取返回值
proceedingJoinPoint.proceed();
System.out.println("切入点后执行");
}
2)返回后通知:使用一个参数来接受,如下:
@AfterReturning(value = "pt()",returning = "ret")
public void afterReturning(Object ret) {
System.out.println("afterReturning advice ..."+ret);
}
只有抛出异常后通知有获取异常的能力,也是使用参数接收的,具体格式如下:
@AfterThrowing(value = "pt()",throwing = "t")
public void afterThrowing(Throwable t) {
System.out.println("afterThrowing advice ..."+t);
}
** 注解** | 说明 |
---|---|
@EnableAspectJAutoProxy | 声明在配置类上,开启注解 AOP 功能 |
@Aspect | 声明在切面类上,表示这是一个切面 |
@Pointcut | 声明一个切入点 |
@Before | 说明在通知上面,Before 表示这个通知在切入点执行前执行 |
@After | 通知在切入点原始方法执行完之后执行 |
@AfterReturning | 通知在切入点原始方法正常执行完毕后执行 |
@AfterThrowing | 通知在切入点原始方法抛出异常后执行 |
@Around | 通知在切入点原始方法执行前后执行,需要调用方法执行切入点方法 |
** 方法原型** | 说明 |
---|---|
Object proceed() throws Throwable; | 调用切入点的方法 |
Object[] getArgs() | 获取一个切入点方法参数,封装成一个数组形式 |