1. Introduction
1.介绍
In this article, we’ll implement a custom AOP annotation using the AOP support in Spring.
在这篇文章中,我们将使用Spring中的AOP支持实现一个自定义AOP注解。
First, we’ll give a high-level overview of AOP, explaining what it is and its advantages. Following this, we’ll implement our annotation step by step, gradually building up a more in-depth understanding of AOP concepts as we go.
首先,我们将对AOP做一个高层次的概述,解释它是什么以及它的优势。之后,我们将一步一步地实现我们的注释,在此过程中逐渐建立起对AOP概念更深入的理解。
The outcome will be a better understanding of AOP and the ability to create our custom Spring annotations in the future.
其结果将是对AOP有更好的理解,并能在未来创建我们的自定义Spring注释。
2. What Is an AOP Annotation?
2.什么是AOP注释?
To quickly summarize, AOP stands for aspect orientated programming. Essentially, it is a way for adding behavior to existing code without modifying that code.
简而言之,AOP是面向方面的编程的意思。从本质上讲,它是一种在不修改现有代码的情况下向现有代码添加行为的方法。
For a detailed introduction to AOP, there are articles on AOP pointcuts and advice. This article assumes we have a basic knowledge already.
对于AOP的详细介绍,有关于AOP的文章pointcuts和advice。这篇文章假设我们已经有了基本的知识。
The type of AOP that we will be implementing in this article is annotation driven. We may be familiar with this already if we’ve used the Spring @Transactional annotation:
我们将在本文中实现的AOP类型是注解驱动。如果我们使用过Spring的@Transactional注解,我们可能已经熟悉了。
@Transactional
public void orderGoods(Order order) {
// A series of database calls to be performed in a transaction
}
The key here is non-invasiveness. By using annotation meta-data, our core business logic isn’t polluted with our transaction code. This makes it easier to reason about, refactor, and to test in isolation.
这里的关键在于非侵入性。通过使用注解元数据,我们的核心业务逻辑不会被交易代码所污染。这使得我们更容易进行推理、重构和隔离测试。
Sometimes, people developing Spring applications can see this as ‘Spring Magic’, without thinking in much detail about how it’s working. In reality, what’s happening isn’t particularly complicated. However, once we’ve completed the steps in this article, we will be able to create our own custom annotation in order to understand and leverage AOP.
有时,开发Spring应用程序的人可以将其视为‘Spring魔法’,而不去详细思考它是如何工作的。实际上,所发生的事情并不特别复杂。然而,一旦我们完成了本文的步骤,我们就能创建自己的自定义注解,以便理解和利用AOP。
3. Maven Dependency
3.Maven的依赖性
First, let’s add our Maven dependencies.
首先,让我们添加我们的Maven dependencies.
For this example, we’ll be using Spring Boot, as its convention over configuration approach lets us get up and running as quickly as possible:
在这个例子中,我们将使用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>
Note that we’ve included the AOP starter, which pulls in the libraries we need to start implementing aspects.
请注意,我们已经包括了AOP启动器,它拉入了我们需要的库,以开始实现各个方面。
4. Creating Our Custom Annotation
4.创建我们的自定义注释
The annotation we are going to create is one which will be used to log the amount of time it takes a method to execute. Let’s create our annotation:
我们要创建的注解是用来记录一个方法的执行时间的。让我们来创建我们的注解。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
}
Although a relatively simple implementation, it’s worth noting what the two meta-annotations are used for.
虽然是一个相对简单的实现,但值得注意的是,这两个元注释的用途是什么。
The @Target annotation tells us where our annotation will be applicable. Here we are using ElementType.Method, which means it will only work on methods. If we tried to use the annotation anywhere else, then our code would fail to compile. This behavior makes sense, as our annotation will be used for logging method execution time.
@Target注解告诉我们我们的注解将适用于哪里。这里我们使用的是ElementType.Method,这意味着它只对方法有效。如果我们试图在其他地方使用该注解,那么我们的代码将无法编译。这种行为是有意义的,因为我们的注解将被用于记录方法的执行时间。
And @Retention just states whether the annotation will be available to the JVM at runtime or not. By default it is not, so Spring AOP would not be able to see the annotation. This is why it’s been reconfigured.
而@Retention只是说明注解在运行时是否会被JVM使用。默认情况下,它不是,所以Spring AOP将无法看到该注解。这就是它被重新配置的原因。
5. Creating Our Aspect
5.创造我们的前景
Now we have our annotation, let’s create our aspect. This is just the module that will encapsulate our cross-cutting concern, which is our case is method execution time logging. All it is is a class, annotated with @Aspect:
现在我们有了我们的注解,让我们来创建我们的方面。这只是一个模块,它将封装我们的跨领域关注点,在我们的例子中是方法执行时间记录。它只是一个类,用@Aspect注解:。
@Aspect
@Component
public class ExampleAspect {
}
We’ve also included the @Component annotation, as our class also needs to be a Spring bean to be detected. Essentially, this is the class where we will implement the logic that we want our custom annotation to inject.
我们还包含了@Component注解,因为我们的类也需要是一个Spring Bean才能被检测到。从本质上讲,这是我们将实现我们希望自定义注解注入的逻辑的类。
6. Creating Our Pointcut and Advice
6.创建我们的指路和建议
Now, let’s create our pointcut and advice. This will be an annotated method that lives in our aspect:
现在,让我们来创建我们的切点和建议。这将是一个注解的方法,住在我们的方面。
@Around("@annotation(LogExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
return joinPoint.proceed();
}
Technically this doesn’t change the behavior of anything yet, but there’s still quite a lot going on that needs analysis.
技术上这还没有改变任何行为,但仍有相当多的事情需要分析。
First, we have annotated our method with @Around. This is our advice, and around advice means we are adding extra code both before and after method execution. There are other types of advice, such as before and after but they will be left out of scope for this article.
首先,我们用@Around注释了我们的方法。这是我们的建议,环绕建议意味着我们在方法执行之前和之后都要添加额外的代码。还有其他类型的建议,如before 和after,但它们将被排除在本文的范围之外。
Next, our @Around annotation has a point cut argument. Our pointcut just says, ‘Apply this advice any method which is annotated with @LogExecutionTime.’ There are lots of other types of pointcuts, but they will again be left out if scope.
接下来,我们的@Around注解有一个点切参数。我们的点切只是说:”将此建议应用于任何被@LogExecutionTime注释的方法。还有很多其他类型的点切,但是如果有范围的话,它们又会被排除在外。
The method logExecutionTime() itself is our advice. There is a single argument, ProceedingJoinPoint. In our case, this will be an executing method which has been annotated with @LogExecutionTime.
方法logExecutionTime()本身就是我们的建议。有一个参数,ProceedingJoinPoint。在我们的案例中,这将是一个被注解为@LogExecutionTime的执行方法。
Finally, when our annotated method ends up being called, what will happen is our advice will be called first. Then it’s up to our advice to decide what to do next. In our case, our advice is doing nothing other than calling proceed(), which is the just calling the original annotated method.
最后,当我们的注释方法最终被调用时,会发生的情况是我们的建议会被首先调用。然后由我们的建议来决定下一步该做什么。在我们的例子中,我们的建议除了调用proceed()之外,什么都不做,也就是调用原始注释方法。
7. Logging Our Execution Time
7.记录我们的执行时间
Now we have our skeleton in place, all we need to do is add some extra logic to our advice. This will be what logs the execution time in addition to calling the original method. Let’s add this extra behavior to our advice:
现在我们已经有了我们的骨架,我们需要做的就是给我们的建议添加一些额外的逻辑。这将是除了调用原始方法之外记录执行时间的东西。让我们把这个额外的行为添加到我们的建议中。
@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;
}
Again, we’ve not done anything that’s particularly complicated here. We’ve just recorded the current time, executed the method, then printed the amount of time it took to the console. We’re also logging the method signature, which is provided to use the joinpoint instance. We would also be able to gain access to other bits of information if we wanted to, such as method arguments.
同样,我们在这里没有做任何特别复杂的事情。我们只是记录了当前的时间,执行了这个方法,然后把它所花费的时间打印到控制台。我们还记录了方法的签名,这个签名是为使用joinpoint instance而提供的。如果我们想的话,我们也能获得其他的信息,比如方法参数。
Now, let’s try annotating a method with @LogExecutionTime, and then executing it to see what happens. Note that this must be a Spring Bean to work correctly:
现在,让我们试着用@LogExecutionTime, 来注释一个方法,然后执行它,看看会发生什么。请注意,这必须是一个Spring Bean才能正确工作。
@LogExecutionTime
public void serve() throws InterruptedException {
Thread.sleep(2000);
}
After execution, we should see the following logged to the console:
执行后,我们应该看到以下内容被记录到控制台。
void org.baeldung.Service.serve() executed in 2030ms
8. Conclusion
8.结论
In this article, we’ve leveraged Spring Boot AOP to create our custom annotation, which we can apply to Spring beans to inject extra behavior to them at runtime.
在本文中,我们利用Spring Boot AOP创建了我们的自定义注解,我们可以将其应用于Spring Bean,以便在运行时向其注入额外的行为。
The source code for our application is available on over on GitHub; this is a Maven project which should be able to run as is.
我们应用程序的源代码可在GitHub上找到;这是一个Maven项目,应该可以按原样运行。