Spring 中的Advice类型介绍

时间:2023-01-11 12:09:24

Spring 中的 Advice 类型介绍

翻译原文链接 Introduction to Advice Types in Spring

1. 概述

在本文中,我们将讨论可以在 Spring 中创建的不同类型的 AOP 通知。

In this article, we'll discuss different types of AOP advice that can be created in Spring.

通知是切面在特定连接点采取的行动。不同类型的通知包括 环绕前置后置 通知。切面的主要目的是支持横切关注点,例如日志记录、分析、缓存和事务管理。

Advice is an action taken by an aspect at a particular join point. Different types of advice include “around”, “before” and “after” advice. The main purpose of aspects is to support cross-cutting concerns, such as logging, profiling, caching, and transaction management.

如果您想更深入地了解切点表达式,请查看前面的介绍(Spring 中的切点表达式介绍)。

And if you want to go deeper into pointcut expressions, check out the previous intro to these.

2. 启用通知

Spring 中,您可以使用 AspectJ 注解声明通知,但您必须首先将 @EnableAspectJAutoProxy 注解应用到您的配置类,这将支持处理标记有 AspectJ@Aspect 注解的组件。

With Spring, you can declare advice using AspectJ annotations, but you must first apply the @EnableAspectJAutoProxy annotation to your configuration class, which will enable support for handling components marked with AspectJ's @Aspect annotation.

@Configuration
@EnableAspectJAutoProxy
public class AopConfiguration {
    // ...
}

2.1. Spring Boot

Spring Boot 项目中,我们不必显式使用 @EnableAspectJAutoProxy。如果 AspectAdvice 在类路径上,则有一个专用的 AopAutoConfiguration 可以启用 SpringAOP 支持。

In Spring Boot projects, we don't have to explicitly use the @EnableAspectJAutoProxy. There's a dedicated AopAutoConfiguration that enables Spring's AOP support if the Aspect or Advice is on the classpath.

3. 前置通知

顾名思义,该通知在连接点之前执行。除非抛出异常,否则它不会阻止它通知的方法的继续执行。

This advice, as the name implies, is executed before the join point. It doesn't prevent the continued execution of the method it advises unless an exception is thrown.

(现在我们来)考虑下面的切面,(该切面用来)在调用之前简单记录方法名称:

Consider the following aspect that simply logs the method name before it is called:

@Component
@Aspect
public class LoggingAspect {

    private Logger logger = Logger.getLogger(LoggingAspect.class.getName());

    @Pointcut("@target(org.springframework.stereotype.Repository)")
    public void repositoryMethods() {};

    @Before("repositoryMethods()")
    public void logMethodCall(JoinPoint jp) {
        String methodName = jp.getSignature().getName();
        logger.info("Before " + methodName);
    }
}

The logMethodCall advice will be executed before any repository method defined by the repositoryMethods pointcut.

4. 后置通知

使用 @After 注解声明的 后置通知在匹配的方法执行后执行,无论是否抛出异常。

After advice, declared by using the @After annotation, is executed after a matched method's execution, whether or not an exception was thrown.

在某些方面,它类似于 finally 块。如果你需要仅在正常执行后触发通知,则应使用 @AfterReturning 注解声明的返回通知。如果你希望仅在目标方法抛出异常时触发您的通知,您应该使用抛出通知,通过使用 @AfterThrowing 注解声明。

In some ways, it is similar to a finally block. In case you need advice to be triggered only after normal execution, you should use the returning advice declared by @AfterReturning annotation. If you want your advice to be triggered only when the target method throws an exception, you should use throwing advice, declared by using the @AfterThrowing annotation.

假设我们希望在创建 Foo 的新实例时通知某些应用程序组件。我们可以从 FooDao 发布一个事件,但这会违反单一职责原则。

Suppose that we wish to notify some application components when a new instance of Foo is created. We could publish an event from FooDao, but this would violate the single responsibility principle.

相反,我们可以通过定义以下的切面来实现这一点:

Instead, we can accomplish this by defining the following aspect:

@Component
@Aspect
public class PublishingAspect {

    private ApplicationEventPublisher eventPublisher;

    @Autowired
    public void setEventPublisher(ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }

    @Pointcut("@target(org.springframework.stereotype.Repository)")
    public void repositoryMethods() {}

    @Pointcut("execution(* *..create*(Long,..))")
    public void firstLongParamMethods() {}

    @Pointcut("repositoryMethods() && firstLongParamMethods()")
    public void entityCreationMethods() {}

    @AfterReturning(value = "entityCreationMethods()", returning = "entity")
    public void logMethodCall(JoinPoint jp, Object entity) throws Throwable {
        eventPublisher.publishEvent(new FooCreationEvent(entity));
    }
}

请注意,首先,通过使用 @AfterReturning 注解,我们可以访问目标方法的返回值。其次,通过声明 JoinPoint 类型的参数,我们可以访问目标方法调用的参数。

Notice, first, that by using the @AfterReturning annotation we can access the target method's return value. Second, by declaring a parameter of type JoinPoint, we can access the arguments of the target method's invocation.

接下来我们创建一个监听器,它会简单地记录事件:

Next we create a listener which will simply log the event:

@Component
public class FooCreationEventListener implements ApplicationListener<FooCreationEvent> {

    private Logger logger = Logger.getLogger(getClass().getName());

    @Override
    public void onApplicationEvent(FooCreationEvent event) {
        logger.info("Created foo instance: " + event.getSource().toString());
    }
}

5. 环绕通知

环绕通知围绕一个连接点,例如方法调用。

Around advice surrounds a join point such as a method invocation.

这是(功能)最强大的一种通知。环绕通知可以在方法调用之前和之后执行自定义行为。它还负责选择是继续连接点还是通过提供自己的返回值或抛出异常来缩短建议的方法执行。

This is the most powerful kind of advice. Around advice can perform custom behavior both before and after the method invocation. It's also responsible for choosing whether to proceed to the join point or to shortcut the advised method execution by providing its own return value or throwing an exception.

为了演示它的用法,假设我们要测量方法执行时间。让我们为此创建一个切面:

To demonstrate its use, suppose that we want to measure method execution time. Let's create an Aspect for this:

@Aspect
@Component
public class PerformanceAspect {

    private Logger logger = Logger.getLogger(getClass().getName());

    @Pointcut("within(@org.springframework.stereotype.Repository *)")
    public void repositoryClassMethods() {};

    @Around("repositoryClassMethods()")
    public Object measureMethodExecutionTime(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.nanoTime();
        Object retval = pjp.proceed();
        long end = System.nanoTime();
        String methodName = pjp.getSignature().getName();
        logger.info("Execution of " + methodName + " took " + TimeUnit.NANOSECONDS.toMillis(end - start) + " ms");
        return retval;
    }
}

当执行 repositoryClassMethods 切点匹配的任何连接点时,会触发此通知。

This advice is triggered when any of the join points matched by the repositoryClassMethods pointcut is executed.

该通知采用 ProceedingJointPoint 类型的一个参数。该参数使我们有机会在目标方法调用之前采取行动。在这种情况下,我们只需保存方法启动时间。

This advice takes one parameter of type ProceedingJointPoint. The parameter gives us an opportunity to take action before the target method call. In this case, we simply save the method start time.

其次,通知返回类型是 Object,因为目标方法可以返回任何类型的结果。如果目标方法为 void,则返回 null。在目标方法调用之后,我们可以测量时间,记录它,并将方法的结果值返回给调用者。

Second, the advice return type is Object since the target method can return a result of any type. If target method is void, null will be returned. After the target method call, we can measure the timing, log it, and return the method's result value to the caller.

6. 总结

在本文中,我们学习了 Spring 中不同类型的通知及其声明和实现。我们使用基于模式的方法和 AspectJ 注解来定义切面。

In this article, we've learned the different types of advice in Spring and their declarations and implementations. We defined aspects using schema-based approach and using AspectJ annotations.

所有这些示例和代码片段的实现都可以在我的 GitHub 项目 中找到。

The implementation of all these examples and code snippets can be found in my GitHub project.