天天看點

Spring 中的Advice類型介紹Spring 中的 Advice 類型介紹

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

。如果 Aspect 或 Advice 在類路徑上,則有一個專用的 AopAutoConfiguration 可以啟用 Spring 的 AOP 支援。

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.