在 Spring AOP 中获取 Advise 方法信息
1、简介
本文将带你了解如何使用 Spring AOP Aspect 获取 Advise 方法的签名、参数、注解以及其他的额外信息。
2、Maven 依赖
首先,在 pom.xml
中添加 spring-boot-starter-aop
Starter 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
3、创建 Pointcut 注解
创建一个 AccountOperation
注解,作为切面中的切点:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AccountOperation {
String operation();
}
注意,切点(Pointcut)不一定非要使用注解定义。也可以使用 Spring AOP 提供的 Pointcut 定义语言定义其他 Pointcut 类型,如类中的某些方法、以某些前缀开头的方法等。
4、创建示例 Service
4.1、Account 类
创建一个 Account
POJO,带有 accountNumber
和 balance
属性。
在 service 方法中使用它作为方法参数:
public class Account {
private String accountNumber;
private double balance;
// get/set toString方法省略
}
4.2、Service 类
现在,创建 BankAccountService
类,它有 2 个方法,并且注解了 @AccountOperation
,以便在切面中获取方法的信息。注意,withdraw
方法会抛出一个受检异常 WithdrawLimitException
,以演示如何获取方法抛出的异常信息。
此外,注意 getBalance
方法没有 AccountOperation
注解,因此不会被切面拦截:
@Component
public class BankAccountService {
@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 RandomUtils.nextDouble();
}
}
5、定义切面(Aspect)
创建 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());
}
}
将 Pointcut
(切点)定义为注解,因此,由于 BankAccountService
中的 getBalance
方法没有注解为 AccountOperation
,因此切面不会拦截它。
接下来详细分析一每个部分,并查看在调用 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());
现在调用 Service 的 withdraw()
方法:
@Test
void withdraw() {
bankAccountService.withdraw(account, 500.0);
assertTrue(account.getBalance() == 1500.0);
}
运行 withdraw()
测试后,可以在控制台看到以下输出:
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()));
调用 BankAccountService
中的 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、获取其他信息
还可以获得有关方法的一些附加信息,如返回类型、Modifier(权限修饰符)以及抛出的异常(如果有的话):
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()
方法超过其定义的 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()
方法。如前所述,由于方法声明中没有 AccountOperation
注解,因此该切面不会拦截该方法:
@Test
void getBalance() {
bankAccountService.getBalance();
}
运行该测试时,控制台中没有任何输出,符合预期。
6、总结
本文介绍了如何使用 Spring AOP 切面获取方法的所有可用信息。
Ref:https://www.baeldung.com/spring-aop-get-advised-method-info