1. AOP 概述
AOP (Aspect-Oriented Programming) 是 Spring 框架的核心功能之一,旨在通过切面来增强程序的功能,特别是在不修改原始代码的情况下为方法添加额外的逻辑,比如日志记录、权限校验、事务管理等。
AOP的作用:在程序运行期间在不修改源代码的基础上对已有方法进行增强(无侵入性: 解耦)
AOP面向切面编程和OOP面向对象编程一样,它们都仅仅是一种编程思想,而动态代理技术是这种思想最主流的实现方式。而Spring的AOP是Spring框架的高级技术,旨在管理bean对象的过程中底层使用动态代理机制,对特定的方法进行编程(功能增强)
举例:
我们要想完成统计各个业务方法执行耗时的需求,我们只需要定义一个模板方法,将记录方法执行耗时这一部分公共的逻辑代码,定义在模板方法当中,在这个方法开始运行之前,来记录这个方法运行的开始时间,在方法结束运行的时候,再来记录方法运行的结束时间,中间就来运行原始的业务方法。
2. AOP 核心概念
Spring的AOP底层是基于动态代理技术来实现的,也就是说在程序运行的时候,会自动的基于动态代理技术为目标对象生成一个对应的代理对象。在代理对象当中就会对目标对象当中的原始方法进行功能的增强。
2.1 连接点 (JoinPoint)
可以被AOP控制的方法(暗含方法执行时的相关信息)
2.2 通知 (Advice)
在连接点执行的代码,可以在方法执行前、后或环绕执行。
在AOP面向切面编程当中,我们只需要将这部分重复的代码逻辑抽取出来单独定义。抽取出来的这一部分重复的逻辑,也就是共性的功能。
2.3 切入点 (PointCut)
匹配连接点的条件,通知仅会在切入点方法执行时被应用。
2.4 切面 (Aspect)
通知和切入点的结合,描述了在哪里、何时执行增强逻辑。
2.5 目标对象(Target)
目标对象指的就是通知所应用的对象,我们就称之为目标对象。
3. AOP使用细节
3.1 切入点表达式
3.1.1 execution
根据方法的签名来匹配。
通过 execution
表达式,可以精确控制在哪些方法上应用 AOP:
execution主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,语法为:
execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws 异常?)
其中带 ? 的表示可以省略的部分
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}
(1)切入点表达式的语法规则:
1. 方法的访问修饰符可以省略
2. 返回值可以使用 * 号代替(任意返回值类型)
3. 包名可以使用 * 号代替,代表任意包(一层包使用一个 * )
4. 使用 .. 配置包名,标识此包以及此包下的所有子包
5. 类名可以使用 * 号代替,标识任意类
6. 方法名可以使用 * 号代替,表示任意方法
7. 可以使用 * 配置参数,一个任意类型的参数
8. 可以使用 .. 配置参数,任意个任意类型的参数
(2)注意事项
根据业务需要,可以使用 且(&&)、或(||)、非(!) 来组合比较复杂的切入点表达式。
execution(* com.itheima.service.DeptService.list(..)) ||
execution(* com.itheima.service.DeptService.delete(..))
(3)切入点表达式的书写建议
1. 所有业务方法名在命名时尽量规范,方便切入点表达式快速匹配。如:查询类方法都是 find 开 头,更新类方法都是update开头
2.描述切入点方法通常基于接口描述,而不是直接描述实现类,增强拓展性
3.在满足业务需要的前提下,尽量缩小切入点的匹配范围。如:包名匹配尽量不使用 ..,使用 * 匹配单个包。
3.1.2 @annotation
根据注解匹配。
如果我们要匹配多个无规则的方法,比如:list() 和 delete()这两个方法。这个时候我们基于execution这种切入点表达式来描述就不是很方便了。 而在之前我们是将两个切入点表达式组合在了一起完成的需求,这个是比较繁琐的。 我们可以借助于另一种切入点表达式annotation来描述这一类的切入点,从而来简化切入点表达式的书写。
还可以通过 @annotation
切入点来根据注解匹配方法:
@Before("@annotation(com.example.annotation.MyLog)")
public void logBefore(JoinPoint joinPoint) {
log.info("Log before method");
}
实现步骤:
(1)编写自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {
}
(2)在业务类要做为连接点的方法上添加自定义注解
3.1.3 抽取切点表达式
Spring提供了@PointCut注解,该注解的作用是将公共的切入点表达式抽取出来,需要用到时引用该切入点表达式即可。
@Slf4j
@Component
@Aspect
public class MyAspect1 {
//切入点方法(公共的切入点表达式)
@Pointcut("execution(* com.cyt.service.*.*(..))")
private void pt(){
}
//前置通知(引用切入点)
@Before("pt()")
public void before(JoinPoint joinPoint){
log.info("before ...");
}
}
注意事项:
当切入点方法使用private修饰时,仅能在当前切面类中引用该表达式, 当外部其他切面类中也要引用当前类中的切入点表达式,就需要把private改为public,而在引用的时候,具体的语法为:
全类名.方法名()
3.2 通知类型
- @Before:在目标方法执行前执行。
- @After:在目标方法执行后执行,无论是否发生异常。
- @AfterReturning:在目标方法成功返回后执行。
- @AfterThrowing:在目标方法抛出异常后执行。
- @Around:环绕目标方法执行,既可以在方法前后执行,也可以控制方法是否执行。
3.2.1 注意事项
(1)@Around环绕通知需要自己调用 ProceedingJoinPoint.proceed() 来让原始方法执行,其他通知不需要考虑目标方法执行
(2)@Around环绕通知方法的返回值,必须指定为Object,来接收原始方法的返回值,否则原始方法 执行完毕,是获取不到返回值的。
3.2.2 获取方法信息
在Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法 名、方法参数等。
(1)对于@Around通知,获取连接点信息只能使用ProceedingJoinPoint类型
(2)对于其他四种通知,获取连接点信息只能使用JoinPoint,它是ProceedingJoinPoint的父类
型
3.3 通知顺序
当一个方法匹配多个切面时,通知的执行顺序可以通过 @Order
注解控制:
@Aspect
@Order(1)
public class FirstAspect {
// 通知代码
}
@Aspect
@Order(2)
public class SecondAspect {
// 通知代码
}
4. AOP 案例
4.1 需求
统计各个业务层方法执行耗时。
4.2 实现步骤
4.2.1 导入依赖
在pom.xml中导入AOP的依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
4.2.2 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {}
4.2.3 添加自定义注解
在业务类要做为连接点的方法上添加自定义注解,即所有的需要统计业务耗时的方法上。
4.2.4 切面类
@Aspect
@Component
public class LogAspect {
@Around("@annotation(com.example.annotation.Log)")
public Object logExecutionDetails(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
long duration = System.currentTimeMillis() - start;
// 记录日志
log.info("Method executed in " + duration + "ms");
return result;
}
}
标签:切入点,编程,切面,AOP,执行,方法,public,表达式
From: https://blog.csdn.net/qq_46637011/article/details/142733106