实现自定义 Spring AOP 注解

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了实现自定义 Spring AOP 注解相关的知识,希望对你有一定的参考价值。

实现自定义 Spring AOP 注解

翻译原文链接 Implementing a Custom Spring AOP Annotation

1. 介绍

在本文中,我们将使用 Spring 中的 AOP 支持来实现自定义 AOP 注解。

首先,我们将给出 AOP 的高级概述,解释它是什么及其优点。在此之后,我们将逐步实现我们的注解,逐步建立对 AOP 概念的更深入理解。

(这样做的)结果将是(帮助我们)更好地理解 AOP 以及 将来(拥有)创建自定义 Spring 注解的能力。

2. 什么是 AOP 注解?

(让我们)快速总结下,AOP 代表面向切面的编程。本质上,这是一种向现有代码添加行为而不修改该代码的方法。

对于 AOP 的详细介绍,(这里)有关于 AOP 切点通知 的文章(以供参考)。本文假设我们已经具备基本知识。

我们将在本文中实现的 AOP 类型是注解驱动的。如果我们使用过 Spring@Transactional 注解,可能已经熟悉了这一点了:

@Transactional
public void orderGoods(Order order) 
	// 要在事务中执行的一系列数据库调用
    // A series of database calls to be performed in a transaction

这里的关键是非侵入性。通过使用注解元数据,我们的核心业务逻辑不会被我们的事务代码污染。这使得单独推理、重构和测试变得更容易。

有些时,开发 Spring 应用程序的人可以将其视为 “Spring Magic”,而无需详细考虑它的工作原理。实际上,发生的事情并不是特别复杂。然而,一旦我们完成了本文中的(实现)步骤,我们将能够创建我们自己的自定义注解,以便理解和利用 AOP

3. Maven 依赖

首先,让我们添加我们的 Maven 依赖项

例如下面,我们将使用 Spring Boot,因为它的约定优于配置(的特点)让我们能够尽快启动和运行:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.2.RELEASE</version>
</parent>

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

请注意,(上述 Maven 依赖中)我们已经包含了 AOP 启动器 ,它引入了我们开始实现切面所需的库。

4. 创建自定义注解

我们将要创建的注解将用于记录方法执行所需的时间。让我们创建注解:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime 


尽管(上述代码)是一个相对简单的实现,但两个元注解的用途却值得关注。

@Target 注解告诉我们注解将适用于何处。这里我们使用 ElementType.Method ,这就意味着它只适用于方法。如果我们试图在其他任何地方使用该注解,那么我们的代码将无法编译通过。这里的特性也说得通,因为我们的注解将用于记录方法执行时间。

@Retention 只是说明注解在运行时是否可用于 JVM。默认情况下不是,因此 Spring AOP 将无法看到注解。这也就是它被重新配置的原因。

5. 创建切面

现在我们有了注解,(接下来)让我们创建切面。这只是一个模块,用来封装我们的横切关注点,我们的例子是方法执行时间记录。它只是一个类,用 @Aspect 注解:

@Aspect
@Component
public class ExampleAspect 


我们还包含了 @Component 注解,因为我们的类也需要是一个 Spring bean 才能被检测到。本质上,我们即将在这个类里实现的,就是我们希望自定义注解注入的逻辑。

6. 创建切点和通知

现在,让我们创建切点和通知。这将是一个存在于我们切面中的带注解的方法:

@Around("@annotation(LogExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable 
    return joinPoint.proceed();

从技术上讲,上述代码还没有改变任何事物的行为,但仍有很多事情需要分析。

首先,我们用 @Around 注解了我们的方法。这是我们的通知,而环绕通知意味着我们在方法执行之前和之后都添加了额外的代码。还有其他类型的通知,例如之前和之后,但它们不在本文的讨论范围之内。

接下来,我们的 @Around 注解有一个切点参数。它只是表明,“将此通知应用于任何用 @LogExecutionTime 注解的方法”。还有很多其他类型的切点,但如果它们在范围内,同样将被排除在外。

logExecutionTime() 方法本身就是我们的通知。有一个参数,ProceedingJoinPoint。在我们(下面)的例子中,将有一个执行方法,它是用 @LogExecutionTime 注解的。

最后,当我们的注解方法最终被调用时,将会首先调用我们的通知。然后由我们的通知来决定下一步做什么。在我们的例子中,我们的通知除了调用 proceed() 之外什么都不做,它只是调用原始的带注解的方法。

7. 记录方法的执行时间

现在我们(记录方法的执行时间)的骨架(代码)已就位,我们需要做的就是在我们的通知中添加一些额外的逻辑。除了调用原始方法之外,这还将记录执行时间。让我们将这个额外的行为添加到我们的通知中:

@Around("@annotation(LogExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable 
    long start = System.currentTimeMillis();

    Object proceed = joinPoint.proceed();

    long executionTime = System.currentTimeMillis() - start;

    System.out.println(joinPoint.getSignature() + " executed in " + executionTime + "ms");
    return proceed;

同样,我们在这里没有做任何特别复杂的事情。我们刚刚记录了当前时间,执行了该方法,然后将花费的时间打印到控制台。我们还记录了方法签名,提供它以使用连接点实例。如果我们愿意,我们还可以访问其他信息,例如方法参数。

现在,让我们尝试用 @LogExecutionTime 注解一个方法,然后执行它看看会发生什么。请注意,这必须是 Spring Bean 才能正常工作:

@LogExecutionTime
public void serve() throws InterruptedException 
    Thread.sleep(2000);

执行后,我们应该看到如下记录到控制台:

void org.baeldung.Service.serve() executed in 2030ms

8. 总结

在本文中,我们利用 Spring Boot AOP 来创建自定义注解,我们可以将其应用到 Spring bean 中,以在运行时向它们注入额外的行为。

我们应用程序的源代码可以在 GitHub 上找到;这是一个 Maven 项目,应该能够按原样运行。

以上是关于实现自定义 Spring AOP 注解的主要内容,如果未能解决你的问题,请参考以下文章

SpringBoot利用自定义注解实现AOP

学习笔记Spring中自定义注解

spring AOP自定义注解方式实现日志管理

spring AOP自定义注解 实现日志管理

spring AOP自定义注解方式实现日志管理

使用Spring Aop自定义注解实现自动记录日志