AOP(面向切面编程)

1. AOP 概述

AOP (Aspect-Oriented Programming) 是 Spring 框架的核心功能之一,旨在通过切面来增强程序的功能,特别是在不修改原始代码的情况下为方法添加额外的逻辑,比如日志记录、权限校验、事务管理等。

AOP的作用:在程序运行期间在不修改源代码的基础上对已有方法进行增强(无侵入性: 解耦)

AOP面向切面编程和OOP面向对象编程一样,它们都仅仅是一种编程思想,而动态代理技术是这种思想最主流的实现方式。而Spring的AOP是Spring框架的高级技术,旨在管理bean对象的过程中底层使用动态代理机制,对特定的方法进行编程(功能增强)

举例:

我们要想完成统计各个业务方法执行耗时的需求,我们只需要定义一个模板方法,将记录方法执行耗时这一部分公共的逻辑代码,定义在模板方法当中,在这个方法开始运行之前,来记录这个方法运行的开始时间,在方法结束运行的时候,再来记录方法运行的结束时间,中间就来运行原始的业务方法。

2. AOP 核心概念

SpringAOP底层是基于动态代理技术来实现的,也就是说在程序运行的时候,会自动的基于动态代理技术为目标对象生成一个对应的代理对象。在代理对象当中就会对目标对象当中的原始方法进行功能的增强。

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;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cyt涛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值