1. 简介
在本教程中,我们将介绍如何使用 Spring AOP 切面获取方法的签名、参数和注解等信息。
2. Maven 依赖
首先,在 pom.xml
文件中添加 Spring Boot AOP Starter 和 commons-rng-simple 库的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-rng-simple</artifactId>
</dependency>
3. 创建切点注解
我们创建一个名为 AccountOperation
的注解,并将其作为切点使用:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AccountOperation {
String operation();
}
⚠️ 注意:定义切点并不强制要求使用注解。我们也可以通过 Spring AOP 提供的切点表达式语言,定义其他类型的切点,比如类中的特定方法、以某个前缀开头的方法等。
4. 创建示例服务
4.1. Account 类
我们先创建一个简单的 POJO 类 Account
,包含账户号和余额两个属性,后续会作为方法参数使用:
public class Account {
private String accountNumber;
private double balance;
// getter / setters / toString
}
4.2. Service 类
接着创建 BankAccountService
类,并在其中定义两个使用 @AccountOperation
注解的方法,方便我们在切面中获取方法信息。注意 withdraw
方法抛出了一个受检异常 WithdrawLimitException
,用于演示如何获取方法抛出的异常信息。
我们还会使用 Apache Commons RNG 库中的 UniformRandomProvider
来模拟账户余额。
⚠️ 注意:getBalance
方法没有使用 @AccountOperation
注解,因此不会被切面拦截:
@Component
public class BankAccountService {
private final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
@AccountOperation(operation = "deposit")
public void deposit(Account account, Double amount) {
account.setBalance(account.getBalance() + amount);
}
@AccountOperation(operation = "withdraw")
public void withdraw(Account account, Double amount) throws WithdrawLimitException {
if(amount > 500.0) {
throw new WithdrawLimitException("Withdraw limit exceeded.");
}
account.setBalance(account.getBalance() - amount);
}
public double getBalance() {
return rng.nextDouble();
}
}
5. 定义切面
我们创建一个 BankAccountAspect
切面类,用于获取 BankAccountService
中被注解方法的详细信息:
@Aspect
@Component
public class BankAccountAspect {
@Before(value = "@annotation(com.baeldung.method.info.AccountOperation)")
public void getAccountOperationInfo(JoinPoint joinPoint) {
// 方法信息
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
System.out.println("full method description: " + signature.getMethod());
System.out.println("method name: " + signature.getMethod().getName());
System.out.println("declaring type: " + signature.getDeclaringType());
// 方法参数
System.out.println("Method args names:");
Arrays.stream(signature.getParameterNames())
.forEach(s -> System.out.println("arg name: " + s));
System.out.println("Method args types:");
Arrays.stream(signature.getParameterTypes())
.forEach(s -> System.out.println("arg type: " + s));
System.out.println("Method args values:");
Arrays.stream(joinPoint.getArgs())
.forEach(o -> System.out.println("arg value: " + o.toString()));
// 其他信息
System.out.println("returning type: " + signature.getReturnType());
System.out.println("method modifier: " + Modifier.toString(signature.getModifiers()));
Arrays.stream(signature.getExceptionTypes())
.forEach(aClass -> System.out.println("exception type: " + aClass));
// 方法注解
Method method = signature.getMethod();
AccountOperation accountOperation = method.getAnnotation(AccountOperation.class);
System.out.println("Account operation annotation: " + accountOperation);
System.out.println("Account operation value: " + accountOperation.operation());
}
}
由于我们使用注解作为切点,因此 getBalance
方法不会被拦截。
接下来我们逐个分析切面中的各个部分,并观察调用 BankAccountService
方法时控制台输出的内容。
5.1. 获取方法签名信息
要获取方法签名信息,我们需要从 JoinPoint
对象中获取 MethodSignature
:
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
System.out.println("full method description: " + signature.getMethod());
System.out.println("method name: " + signature.getMethod().getName());
System.out.println("declaring type: " + signature.getDeclaringType());
调用 withdraw()
方法:
@Test
void withdraw() {
bankAccountService.withdraw(account, 500.0);
assertTrue(account.getBalance() == 1500.0);
}
控制台输出如下:
full method description: public void com.baeldung.method.info.BankAccountService.withdraw(com.baeldung.method.info.Account,java.lang.Double) throws com.baeldung.method.info.WithdrawLimitException
method name: withdraw
declaring type: class com.baeldung.method.info.BankAccountService
5.2. 获取方法参数信息
我们可以通过 MethodSignature
获取方法参数信息:
System.out.println("Method args names:");
Arrays.stream(signature.getParameterNames()).forEach(s -> System.out.println("arg name: " + s));
System.out.println("Method args types:");
Arrays.stream(signature.getParameterTypes()).forEach(s -> System.out.println("arg type: " + s));
System.out.println("Method args values:");
Arrays.stream(joinPoint.getArgs()).forEach(o -> System.out.println("arg value: " + o.toString()));
调用 deposit()
方法进行测试:
@Test
void deposit() {
bankAccountService.deposit(account, 500.0);
assertTrue(account.getBalance() == 2500.0);
}
控制台输出如下:
Method args names:
arg name: account
arg name: amount
Method args types:
arg type: class com.baeldung.method.info.Account
arg type: class java.lang.Double
Method args values:
arg value: Account{accountNumber='12345', balance=2000.0}
arg value: 500.0
5.3. 获取方法注解信息
可以通过 Method
类的 getAnnotation()
方法获取方法上的注解信息:
Method method = signature.getMethod();
AccountOperation accountOperation = method.getAnnotation(AccountOperation.class);
System.out.println("Account operation annotation: " + accountOperation);
System.out.println("Account operation value: " + accountOperation.operation());
再次运行 withdraw()
测试,输出如下:
Account operation annotation: @com.baeldung.method.info.AccountOperation(operation=withdraw)
Account operation value: withdraw
5.4. 获取其他方法信息
我们还可以获取方法返回类型、修饰符以及抛出的异常等信息:
System.out.println("returning type: " + signature.getReturnType());
System.out.println("method modifier: " + Modifier.toString(signature.getModifiers()));
Arrays.stream(signature.getExceptionTypes())
.forEach(aClass -> System.out.println("exception type: " + aClass));
创建一个测试方法 withdrawWhenLimitReached()
,使 withdraw()
抛出异常:
@Test
void withdrawWhenLimitReached()
{
Assertions.assertThatExceptionOfType(WithdrawLimitException.class)
.isThrownBy(() -> bankAccountService.withdraw(account, 600.0));
assertTrue(account.getBalance() == 2000.0);
}
控制台输出如下:
returning type: void
method modifier: public
exception type: class com.baeldung.method.info.WithdrawLimitException
最后测试 getBalance()
方法,由于未使用注解,不会被切面拦截,控制台无输出:
@Test
void getBalance() {
bankAccountService.getBalance();
}
6. 总结
通过本教程,我们学习了如何使用 Spring AOP 切面获取方法的签名、参数、注解以及其他相关信息。我们定义了一个切点,将信息打印到控制台,并通过运行测试验证了输出结果。
源码地址:GitHub 仓库 ✅