Spring AOP 教程

在 Spring 框架中,AOP 模块是核心组件之一,它实现了诸如应用程序审计、事务管理和安全等重要功能。

AOP 被称为实现横切关注点的工具。术语横切关注点指的是应用程序中无法从其余应用程序分解的逻辑,并且可能导致代码重复和紧密耦合。通过使用 AOP 对独立的逻辑块(称为关注点)进行模块化,可以将它们应用于应用程序的许多部分,而无需重复代码或创建硬依赖关系。

日志记录和安全是存在于许多应用程序中的典型横切关注点。您通常需要在程序的许多部分记录操作或检查某人是否具有权限。AOP 帮助您在不重复编写相同代码的情况下完成此操作。

1. 什么是 Spring AOP?

面向切面编程是一种补充面向对象编程 (OOP) 的范式。虽然 OOP 关注将代码组织成类和对象,但 AOP 专注于横切关注点——影响应用程序多个部分的功能。横切关注点包括日志记录、安全、事务等。

在 Spring 框架中,Spring AOP 是一个核心模块,它提供了一个强大而优雅的解决方案来解决横切关注点带来的挑战。它允许我们对这些关注点进行模块化,从而提高代码的可维护性并减少冗余。Spring AOP 与 Spring IoC (控制反转) 容器无缝集成,使其成为 Spring 生态系统不可或缺的一部分。

2. Spring AOP 架构

Spring AOP 的核心架构基于代理。当应用程序初始化时,一个类的建议实例是通过ProxyFactory 创建该类的代理实例并将所有方面编织到代理中而创建的。

在运行时,Spring 会分析ApplicationContext 中定义的横切关注点,并动态生成代理 bean(它们包装了底层的目标 bean)。调用者不是直接访问目标 bean,而是注入了代理 bean。

在内部,Spring 有两种代理实现

  • JDK 动态代理:当要建议的目标对象实现接口时。
  • CGLIB 代理:当建议的目标对象不实现接口时。例如,它是一个具体类。

请注意,JDK 动态代理仅支持接口的代理。

请记住,Spring AOP 存在一些限制。例如

  • 由于无法扩展,因此无法代理最终类或方法。
  • 此外,由于代理实现,Spring AOP 仅适用于 Spring Bean 上的公共非静态方法。
  • 如果在同一类中从一个方法到另一个方法的内部方法调用,则不会为内部方法调用执行建议。

3. 什么是 Advice、Joinpoint 和 Pointcut?

  • Joinpoint:是程序执行的点,例如执行方法或处理异常。在 Spring AOP 中,joinpoint 始终代表方法执行。 Joinpoint 定义了您可以在应用程序中插入其他逻辑的位置,使用 AOP。
  • Advice:是在特定 joinpoint 执行的代码。有许多类型的 advice,例如before,它在 joinpoint 之前执行,以及after,它在 joinpoint 之后执行。
  • Aspect:是在一个类中封装的 advice 和 pointcut 的组合。
  • Pointcut:是匹配 joinpoint 的谓词或表达式。
  • Weaving:是将方面插入到应用程序代码的适当位置的过程。AspectJ 支持一种称为loadtime weaving (LTW) 的 weaving 机制,它拦截底层的 JVM 类加载器并在类加载器加载字节码时提供 weaving。
  • Target:是由 AOP 过程修改其执行流程的对象。通常您会看到目标对象被称为advised object
  • Spring 默认使用 AspectJ pointcut 表达式语言。
Spring AOP
Spring AOP

4. Spring AOP 中的 Advice 类型

Spring AOP 中有五种类型的 advice。

  • Before advice:在 join point 之前执行的 advice,但无法阻止执行流程继续到 join point(除非它抛出异常)。
  • After returning advice:在 join point 正常完成后执行的 advice:例如,如果方法返回而没有抛出异常。
  • After throwing advice:如果方法通过抛出异常退出时执行的 advice。
  • After advice:无论 join point 如何退出(正常或异常返回),都将执行的 advice。
  • Around advice:围绕 join point(例如方法调用)的 advice。这是功能最强大的 advice 类型。Around advice 可以在方法调用之前和之后执行自定义行为。它还负责选择是继续到 join point 还是通过返回自己的返回值或抛出异常来短路建议的方法执行。

选择 advice 类型基于应用程序的要求,但我们应为我们的需求选择最具体的 advice 类型。当 before advice 就能满足需求时,不要使用 around advice。

在大多数情况下,围绕(around)类型的Advice可以完成其他三种Advice类型所能完成的一切,但对于您尝试实现的目标来说,它可能过于复杂。 通过尽可能地将Advice类型保持专注,我们可以减少出错的范围。

5. Spring AOP 入门

让我们看看如何在 Spring 应用程序中使用 AOP。

5.1. Maven

在编写任何代码之前,您需要将Spring AOP 依赖项导入到您的项目中。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>6.1.1</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>6.1.1</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>6.1.1</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.9.20.1</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.20.1</version>
</dependency>

在 Spring Boot 应用程序中,添加依赖项会容易得多。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

5.2. 启用 Spring AOP

我们可以使用配置上的 @EnableAspectJAutoProxy 注解来 启用 Spring AOP。

@Configuration
@EnableAspectJAutoProxy
@ComponentScan
public class AopConfig {
 
}

5.3. 使用 Aspect 和 Pointcut 表达式定义 Advice

每个 Aspect 类都应该使用 @Aspect 注解进行注解。 在该类中,我们然后指定 Pointcut 和 Advice。 它也应该使用 @Component 进行注解,以便通过组件扫描(或者以另一种方式配置为 Spring bean)被拾取。

@Aspect
public class EmployeeCRUDAspect {
      
    @Before("execution(* EmployeeManager.getEmployeeById(..))")     //point-cut expression
    public void logBeforeV1(JoinPoint joinPoint) {

        System.out.println("EmployeeCRUDAspect.logBeforeV1() : " + joinPoint.getSignature().getName());
    }
}

5.4. 受 Advice 影响的方法(连接点)

编写您想要执行 Advice 的方法,并且这些方法与 Pointcut 表达式匹配。

@Component
public class EmployeeManager {

    public EmployeeDTO getEmployeeById(Integer employeeId) {
        System.out.println("Method getEmployeeById() called");
        return new EmployeeDTO();
    }
}

在上面的例子中,logBeforeV1() 将在 之前 执行 getEmployeeById() 方法,因为它匹配了连接点表达式。

5.5. 运行应用程序

运行应用程序并观察控制台。

public class TestAOP
{
    @SuppressWarnings("resource")
    public static void main(String[] args) {
  
        ApplicationContext context = new ClassPathXmlApplicationContext
                  ("com/howtodoinjava/demo/aop/applicationContext.xml");
 
        EmployeeManager manager = context.getBean(EmployeeManager.class);
  
        manager.getEmployeeById(1);
    }
}

程序输出

EmployeeCRUDAspect.logBeforeV1() : getEmployeeById
Method getEmployeeById() called

Spring aop 教程,面向初学者,附带示例。

6. Spring AOP 注解示例

7. Spring AOP 高级教程

8. Spring AOP 面试题

祝您学习愉快!!

Github 上的源代码

发表评论

  1. 并且此示例中还有一个问题,您应该在项目中添加配置类

    @Configuration
    @EnableAspectJAutoProxy
    public class Config {

    }

  2. 我花了整整一天的时间才让这段代码正常工作。 请使用以下代码修复此代码

    @Aspect
    @Component
    public class EmployeeCRUDAspect {

    而不是

    @Aspect
    public class EmployeeCRUDAspect {

评论已关闭。

关于我们

HowToDoInJava 提供 Java 和相关技术的教程和操作指南。

它还分享最佳实践、算法和解决方案以及经常被问到的面试题。

我们的博客

REST API 教程

关注我们