Spring学习笔记 | AOP 面向切面编程

2024-05-11 17:10:04 浏览数 (2)

什么是AOP

AOP,全称是面向切面编程(Aspect-Oriented Programming)。

AOP是一种编程范式,其主要目标是提高模块化程度,以提高代码的可重用性和可维护性。

为什么需要AOP

在传统的面向对象编程(OOP)中,我们通常会将代码按照功能进行模块化。然而,有些功能(例如日志记录、事务管理、安全性检查等)可能会跨越多个模块,这些功能我们通常称之为"横切关注点"。在OOP中处理这些横切关注点通常会导致代码的重复和分散,这就是所谓的"代码污染"。

AOP的主要思想就是将这些横切关注点从业务逻辑代码中分离出来,单独进行模块化,然后在运行时将它们动态地"织入"到需要的业务逻辑中。这样,我们就可以将关注点的代码集中管理,提高代码的可重用性和可维护性。

Spring AOP 的底层实现原理

Spring AOP使用了代理模式,通过在运行时创建代理对象来实现切面的织入。这样,当调用一个被代理的方法时,Spring AOP就可以在方法调用前后插入切面代码,实现例如日志记录、事务管理等功能。

AOP的核心概念

  1. 横切关注点-Crosscutting Concerns 横切关注点是指软件系统中跨越多个模块的功能和关注点。例如日志记录、安全检测、权限控制等。
  2. 切面-Aspect 切面是AOP的基本构建块,它是对横切关注点的模块化表示。切面可以包含一些通知(Advice)和切入点(Pointcut)。切面是将横切关注点封装起来的模块。
  3. 连接点-JoinPoint 连接点是在应用程序中你想要插入切面的点。在Spring AOP中,连接点总是表示一个方法的执行。
  4. 通知-Advice 通知是切面模块的具体实现,它定义了切面需要完成的工作。Spring AOP支持五种类型的通知
    • 前置通知(Before)
    • 后置通知(After)
    • 返回后通知(After Returning)
    • 抛出异常后通知(After Throwing)
    • 环绕通知(Around)。
  5. 切入点-PointCut 切入点是一组连接点的集合,它定义了通知应该在哪些连接点上执行。 Spring AOP使用AspectJ的切入点表达式语言来定义切入点。
  6. 目标对象-Target Object 目标对象是被一个或者多个切面所通知的对象。也就是包含连接点的对象。
  7. 代理-Proxy 代理是一个被AOP框架动态创建的对象,它用来实现切面代码的插入。 在Spring AOP中,代理有两种
    • JDK动态代理
    • CGLIB代理。
  8. 织入-Weaving 织入是将切面插入到目标对象以创建新的代理对象的过程。织入可以在编译时(使用AspectJ编译器),类加载时,或者运行时(如Spring AOP)进行。

如何使用AOP

好的,让我们以日志记录为例,来看一下如何使用Spring AOP进行面向切面编程。

识别横切关注点:在这个例子中,我们的横切关注点是日志记录,因为我们希望在每个方法执行前后都记录日志。

定义切面:我们可以创建一个新的Java类,并使用@Aspect注解来定义这个类为一个切面:

代码语言:javascript复制
@Aspect
@Component
public class LoggingAspect {
    // ...
}

定义通知:在切面类中,我们可以定义一些通知方法。例如,我们可以定义一个前置通知和一个后置通知,分别在方法执行前后记录日志

代码语言:javascript复制
@Aspect
@Component
public class LoggingAspect {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        logger.info("开始执行方法:{}", joinPoint.getSignature().getName());
    }

    @After("execution(* com.example.service.*.*(..))")
    public void logAfter(JoinPoint joinPoint) {
        logger.info("方法执行结束:{}", joinPoint.getSignature().getName());
    }
}

定义切入点:在这个例子中,我们的切入点是com.example.service包下的所有方法。我们使用execution表达式来定义这个切入点,并在@Before@After注解中直接使用。如果你希望在多个通知中复用同一个切入点,你可以使用@Pointcut注解来定义一个切入点:

代码语言:javascript复制
@Aspect
@Component
public class LoggingAspect {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceMethods() {}

    @Before("serviceMethods()")
    public void logBefore(JoinPoint joinPoint) {
        logger.info("开始执行方法:{}", joinPoint.getSignature().getName());
    }

    @After("serviceMethods()")
    public void logAfter(JoinPoint joinPoint) {
        logger.info("方法执行结束:{}", joinPoint.getSignature().getName());
    }
}

配置AOP:最后,我们需要在Spring的配置中启用AOP。如果你使用的是Spring Boot,那么AOP已经默认启用了。如果你使用的是传统的Spring,你可以在你的配置类中添加@EnableAspectJAutoProxy注解来启用AOP:

代码语言:javascript复制
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
    // ...
}

0 人点赞