一,AOP入门
1、AOP概述
AOP,Aspect Oriented Programming面向切面编程思想,是对OOP(面向对象)进行的补充
OOP是纵向继承技术,可以将程序中一段连续的代码进行封装
AOP是横向抽取技术,可以将程序中四个位置(核心概念之前、返回值之后、catch、finally)的非核心业务代码封装到一个类中(切面),在将切面中的功能套到(织入)目标对象具体的方法上
AOP在不改变源码的基础上实现功能的增强
AOP的重要应用是日志和事务等
2、AOP的专业术语
代理对象:代理目标对象的对象
目标对象:被代理的对象
横切关注点:指从核心代码中抽取的非核心业务代码,例: 添加日志功能
切面:封装非核心业务代码(横切关注点)的类
通知:切面中封装横切关注点的方法为通知方法,例: 把日志功能封装的方法
切入点:切面中通知作用于目标对象中方法的位置(通知之后)
切入点表达式:定位切入点的一种表达式
切入点是一个用于筛选连接点的表达式或者规则。它的主要作用是从众多的连接点中选择出符合特定条件的那些连接点。它就像一个过滤器,决定了 AOP 的横切逻辑应该应用到哪些具体的位置。只有符合这个表达式的连接点才会被性能监控这个 AOP 功能所增强
并且同一个连接点可能会被多个不同的切入点选中,也可能一个都不被选中。
连接点:在目标对象中抽取横切关注点的位置(方法)(通知之前)
连接点是在程序执行过程中能够插入切面(Aspect)的点。在 Spring AOP 中,这些点通常是方法的调用。
在准备使用AOP技术对哪个方法添加额外功能的方法就是一个连接点,这些点在 Spring AOP 中主要是指方法的调用、方法的执行、异常的抛出等位置。通常指的方法名
在使用AOP技术之前要添加额外功能的方法叫做连接点,在创建了切面类后,准备把切面类插入到哪里执行的位置也就是哪个方法(经过切入点表达式进行过滤后要使用的方法)叫做切入点。
可以说就是同一个方法,只是因为时间上的不同,分别给了不同的命名。
连接点(Join Point)和切入点(Pointcut)是不同的概念。
定义角度
连接点
在 Spring AOP 中,连接点主要是基于方法调用的。在底层,Spring AOP 使用动态代理或者字节码增强技术来拦截这些方法调用,从而实现 AOP 功能。从开发者的角度看,只要知道在方法调用的这个时刻,AOP 机制就可以介入。
连接点是在程序执行流程中,能够被切面(Aspect)插入额外逻辑的特定位置。从本质上讲,它是程序运行过程中的一个点,这些点在 Spring AOP 中主要是指方法的调用、方法的执行、异常的抛出等位置。例如,在一个 Java 的 Web 应用中,每次调用一个 Servlet 的doGet或doPost方法时,这个方法调用的位置就是一个连接点。连接点是一个比较宽泛的概念,它代表了所有可能被 AOP 增强的位置。
切入点
切入点表达式通常使用 AspectJ 的语法
切入点是一个用于筛选连接点的表达式或者规则。它的主要作用是从众多的连接点中选择出符合特定条件的那些连接点。例如,我们可以定义一个切入点表达式,用来选择某个特定包下所有以 "add" 开头的方法调用作为连接点,那么这个表达式所确定的这些特定的方法调用位置就是切入点所筛选出来的连接点。
数量角度
连接点
连接点的数量通常是由程序的结构和执行流程决定的。一个复杂的应用程序可能有大量的连接点。例如,一个包含多个业务层、持久层和控制层的大型企业级应用,其中每个层都有许多方法,这些方法的调用位置都是连接点,数量可能会有成百上千个。
切入点
切入点的数量是由开发者定义的。可以根据业务需求和功能模块等因素来定义不同的切入点。例如,可能为日志记录功能定义一个切入点,为事务管理功能定义另一个切入点。并且同一个连接点可能会被多个不同的切入点选中,也可能一个都不被选中。
功能角度
连接点
连接点只是提供了一个可以插入切面逻辑的机会,它本身并没有对 AOP 增强的范围进行任何限制。例如,在一个方法调用这个连接点上,如果没有切入点的筛选,我们不知道是否要对这个方法调用进行日志记录、事务管理或者其他 AOP 操作。
切入点
切入点则明确了哪些连接点需要进行 AOP 增强。它就像一个过滤器,决定了 AOP 的横切逻辑应该应用到哪些具体的位置。例如,我们通过一个切入点表达式确定了只对某个特定模块下的方法调用进行性能监控,那么只有符合这个表达式的连接点才会被性能监控这个 AOP 功能所增强。
二、基于注解的AOP的实现
1、实现步骤
a>导入aop的场景启动器
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
b>创建切面,并将目标对象和切面类的对象交给IOC容器管理
c>使用**@Aspect注解标记切面组件,并加上@Component**把切面类交给IOC作为一个bean使用
d>在配置类上使用**@EnableAspectJAutoProxy**注解开启AspectJ的自动代理功能,注意,在SpringBoot的环境中可以省略不写
2、通知
前置通知:使用@Before注解标记切面中方法,将其标记为前置通知的方法,在目标对象方法执行之前执行
后置通知:使用@After注解标记切面中的方法,将其标记为后置通知的方法,在目标对象方法的finally中执行
后置通知也叫做最终通知,不管目标对象中是否有异常都会执行
返回通知:使用@AfterReturning注解标记切面中的方法,将其标记为返回通知的方法,在目标对象方法的返回值之后执行
异常通知:使用@AfterThrowing注解标记切面中的方法,将其标记为异常通知的方法,在目标对象方法的catch中执行
环绕通知:使用@Around注解标记切面中的方法,将其标记为环绕通知的方法,相当于以上四种通知结合使用
注意:
1、环绕通知的方法的形参位置必须设置ProceedingJoinPoint类型的形参,通过joinPoint.proceed()表示目标对象方法的执行
2、环绕通知的方法必须设置Object类型的返回值,将joinPoint.proceed()方法的返回值进行返回,joinPoint.proceed()方法的返回值就是目标对象方法的返回值;若环绕通知没有返回值则直接抛出异常AopInvocationException
java@Around("pointCut()") public Object aroundMethod(ProceedingJoinPoint joinPoint) { Object result = null; try { System.out.println("环绕通知(前置)"); //调用目标对象的方法 result = joinPoint.proceed(); System.out.println("环绕通知(返回)"); } catch (Throwable e) { System.out.println("环绕通知(异常)"); throw new RuntimeException(e); } finally { System.out.println("环绕通知(后置)"); } return result; }
3、切入点表达式
@Before("execution(public int com.yuan.aop.CalcImpl.add(int, int))")
* 包名.类名.方法名(参数类型)
--->(* yuan.*.*(..))
任意的访问修饰符和返回值类型使用* ,这里用一个* 代替类似于 public int 的访问修饰符和返回值
类中任意的方法使用*
包下任意的类使用*
包名和类名以指定的内容开始使用xxx*
包名和类名以指定的内容结束使用*xxx
任意的参数类型使用...
- 切入点表达式通常使用 AspectJ 的语法。例如,
execution(* com.example.service.UserService.update*(..))
就是一个切入点表达式。其中execution
是关键字,表示执行方法的连接点。*
在返回值类型位置表示任意返回值类型,com.example.service.UserService.update*(..)
表示com.example.service.UserService
类中以update
开头的所有方法,(..)
表示方法可以有任意参数。
4、公共的切入点表达式
作用:把切入点表达式作为一个过滤器,对要插入额外功能的连接点方法进行筛选,然后插入切面类
a>声明公共的切入点表达式
java
//声明公共的切入点表达式
@Pointcut("execution(* com.yuan.aop.*.*(..))")
public void pointCut(){}
b>使用公共的切入点表达式
java
@Before("pointCut()")
public void beforeMethod() {
System.out.println("前置通知");
}
5、获取连接点信息
a>获取连接点所对应方法的方法名和参数
在通知方法的形参位置设置JoinPoint类型的形参,其中封装了连接点相关信息
java
//获取连接点所对应方法的方法名
String methodName = joinPoint.getSignature().getName();
//获取连接点所对应方法的参数
Object[] args = joinPoint.getArgs();
b>在返回通知中获取目标对象方法的返回值
在通知方法的形参位置设置一个Object类型的形参,使用@AfterReturning注解中的returning属性,将该参数设置为接收目标对象方法的返回值的参数
java
@AfterReturning(value = "pointCut()", returning = "result")
public void afterReturningMethod(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
System.out.println("返回通知,方法名:"+methodName+",结果:"+result);
}
c>在异常通知中获取目标对象方法出现的异常
在通知方法的形参位置设置一个Exception或Throwable类型的形参,使用@AfterThrowing注解中的throwing属性,将该参数设置为接收目标对象方法出现的异常的参数
java
@AfterThrowing(value = "pointCut()", throwing = "e")
public void afterThrowingMethod(JoinPoint joinPoint, Throwable e) {
String methodName = joinPoint.getSignature().getName();
System.out.println("异常通知,方法名:"+methodName+",异常:"+e);
}
6、切面的优先级
使用@Order注解标记切面,设置优先级,通过value属性设置一个整数,值越小优先级越高,
java
@Component
@Aspect //标记为一个切面组件
@Order(1) //优先级,默认优先级都是Integer的最大值,所以你无论写多大,都会比Integer的最大值小,都会先执行设置优先级的切面类
public class ValidateAspect {
@Before("com.yuan.aop.LoggerAspect.poinCut()")
public void before(JoinPoint joinPoint){
System.out.println("ValidateAspect------->前置通知");
}
}