Spring AOP 中的环绕通知 – @Around

简介
在本文中,您将了解 Spring AOP 中围绕匹配的 Joinpoint 执行运行的环绕建议。使用 @Around 注解声明环绕通知。
您已经了解了上一篇文章中列出的 5 种通知类型,如下所示。
- Before advice –
@Before
- After returning –
@AfterReturning
- After throwing –
@AfterThrowing
- After (finally) advice –
@After
- Around advice –
@Around
环绕通知执行 - @Around
使用 @Around 注释声明环绕通知。它包含在匹配方法(JoinPoint)之前和之后执行的代码
通知方法的第一个参数必须是 ProceedingJoinPoint 类型。
在通知正文中,对 ProceedingJoinPoint 调用 proceed() 会导致执行底层方法。
在大多数情况下,从通知方法返回一个值很重要,因为匹配的 JoinPoint 可以有一个返回类型。
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;
@Aspect
public class AroundExample {
@Around("execution (* com.jbd.myapp.service.*.*(..))")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// Custom code before method execution
Object retVal = pjp.proceed(); //method execution
// Custom code after method execution
return retVal; //exit the advice
}
}
环绕通知的例子——@Around
问题陈述——我们将使用之前示例中的相同 UserRepository 并添加一个新功能来跟踪方法完成其执行所花费的时间。使用 @ExecutionTime 注释的任何方法都将在控制台中记录总执行时间。 @ExecutionTime 是一个自定义注解。
步骤 1:添加依赖项
创建一个maven项目,在pom.xml中添加如下依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
<!-- Step-1 Add the dependencies -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
<dependencies>
步骤 2:创建示例类
我们将创建带有@ExecutionTime
注释的方法。所以我们创建了示例代码——UserRepository
、配置类和注释,如下所示。正如我们之前讨论过的,注解@EnableAspectJAutoProxy
用于启用Spring-AOP。
UserRepository.java
package com.jbd.saop.around.dao;
import com.jbd.saop.around.ExecutionTime;
import org.springframework.stereotype.Repository;
//A very stupid demo repository
@Repository
public class UserRepository {
//Add a user
@ExecutionTime
public UserRepository add(String username) throws InterruptedException {
Thread.sleep(100);
if(username == null) {
throw new RuntimeException("username is null", new NullPointerException());
}
System.out.println("New user added: " + username);
return this;
}
//Update an user
public UserRepository update(String username, String email) {
System.out.println("Update email: " + email);
return this;
}
//Delete an user
@ExecutionTime
public boolean delete(String username){
if (username == null) {
throw new RuntimeException("username is null", new NullPointerException());
}
System.out.println("User deleted: " + username);
return true;
}
}
@ExecutionTime
package com.jbd.saop.around;
import java.lang.annotation.*;
/**
* Tracks the execution time.
*/
//Allowed to use only on methods
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExecutionTime {
}
ApplicationConfig.java
package com.jbd.saop.around;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan("com.jbd.saop.around")
@EnableAspectJAutoProxy
public class ApplicationConfig {
}
步骤 3:创建切入点表达式和通知
切入点表达式匹配所有使用@ExecutionTime
注释的方法。如果任何方法抛出 RuntimeException,它将被记录。创建方面类,如下所示。
package com.jbd.saop.around.advice;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.context.annotation.Configuration;
import java.time.LocalTime;
@Configuration
@Aspect
public class ExecutionTimeLogger {
@Around("@annotation(com.jbd.saop.around.ExecutionTime)")
public Object logExecTime(ProceedingJoinPoint pJoinPoint){
System.out.println("Before method: "
+ pJoinPoint.getSignature().toShortString());
long beforeTime = System.currentTimeMillis();
Object result = null;
try {
result = pJoinPoint.proceed();//Important
//If method throws Exception or any error occurs
} catch (Throwable throwable) {
throwable.printStackTrace();
}
long afterTime = System.currentTimeMillis();
System.out.println("Time taken to execute: "
+ (afterTime - beforeTime) + "ms");
return result;
}
}
第 4 步:测试示例
最后,我们将测试该示例以查看其输出。在运行测试用例之前,在 pom.xml 中添加测试依赖项。
<dependencies>
...
<!-- Test Dependencies-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.5.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.5.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.2.RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
...
<!-- Important for Jupiter-engine -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M3</version>
</plugin>
</plugins>
</build>
</project>
添加测试依赖项后,在 src\test 中创建 Test 类。
package com.jbd.saop.around;
import com.jbd.saop.around.dao.UserRepository;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
@SpringJUnitConfig(ApplicationConfig.class)
public class TestAroundAdvice {
@Autowired
private UserRepository userRepository;
@Test
public void testNull(){
Assertions.assertNotNull(userRepository);
}
@Test
public void testAddUser() throws InterruptedException {
userRepository.add("sample_username");
}
@Test
public void testAddUserFailure() throws InterruptedException {
userRepository.add(null);
}
}
测试用例应通过控制台中的以下输出。顺序可能不同。我建议分别运行每个测试用例并检查输出。
Before method: UserRepository.add(..)
New user added: sample_username
Time taken to execute: 124ms
Before method: UserRepository.add(..)
java.lang.RuntimeException: username is null
at com.jbd.saop.around.dao.UserRepository.add(UserRepository.java:14)
at com.jbd.saop.around.dao.UserRepository$$FastClassBySpringCGLIB$$dcb57c42.invoke()
...
Caused by: java.lang.NullPointerException
... 86 more
Time taken to execute: 120ms
如您所见,Spring AOP 中的 around 建议非常强大。您可以更好地控制匹配的方法执行,尤其是由于 ProceedingJoinPoint。永远记得在通知方法中调用proceed()并返回Object值。