Перейти к основному содержимому

Получите рекомендованную информацию о методе в Spring AOP

· 5 мин. чтения

1. Введение

В этом руководстве мы покажем вам, как получить всю информацию о сигнатуре, аргументах и аннотациях метода, используя аспект Spring AOP .

2. Зависимости Maven

Начнем с добавления зависимости библиотеки Spring Boot AOP Starter в pom.xml :

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

3. Создание нашей аннотации Pointcut

Давайте создадим аннотацию AccountOperation . Чтобы уточнить, мы будем использовать его как pointcut в нашем аспекте:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AccountOperation {
String operation();
}

Обратите внимание, что создание аннотации не является обязательным для определения точечного разреза. Другими словами, мы можем определить другие типы pointcut, такие как определенные методы в классе, методы, начинающиеся с некоторого префикса, и т. д., используя язык определения pointcut, предоставляемый Spring AOP.

4. Создание нашего примера сервиса

4.1. Класс счета

Давайте создадим Account POJO со свойствами accountNumber и balance . Мы будем использовать его в качестве аргумента метода в наших сервисных методах:

public class Account {

private String accountNumber;
private double balance;

// getter / setters / toString
}

4.2. Класс обслуживания

Давайте теперь создадим класс BankAccountService с двумя методами, которые мы аннотируем аннотацией @AccountOperation , чтобы мы могли получить информацию о методах в нашем аспекте. Обратите внимание, что метод remove создает проверенное исключение 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. Определение нашего аспекта

Давайте создадим BankAccountAspect , чтобы получить всю необходимую информацию от связанных методов, вызываемых в нашем BankAccountService:

@Aspect
@Component
public class BankAccountAspect {

@Before(value = "@annotation(com.foreach.method.info.AccountOperation)")
public void getAccountOperationInfo(JoinPoint joinPoint) {

// Method Information
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());

// Method args
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()));

// Additional Information
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 annotation
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 как аннотацию, поэтому, поскольку метод getBalance в нашем BankAccountService не аннотирован AccountOperation, аспект не будет его перехватывать.

Давайте теперь подробно разберем каждую часть нашего аспекта и посмотрим, что мы получаем в консоли при вызове методов BankAccountService .

5.1. Получение информации о сигнатуре метода

Чтобы иметь возможность получить информацию о подписи нашего метода, нам нужно получить MethodSignature из объекта 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());

Давайте теперь вызовем метод remove() нашего сервиса:

@Test
void withdraw() {
bankAccountService.withdraw(account, 500.0);
assertTrue(account.getBalance() == 1500.0);
}

После запуска теста remove() мы видим в консоли следующие результаты:

full method description: public void com.foreach.method.info.BankAccountService.withdraw(com.foreach.method.info.Account,java.lang.Double) throws com.foreach.method.info.WithdrawLimitException
method name: withdraw
declaring type: class com.foreach.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 :

@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.foreach.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. Получение информации об аннотациях методов

Мы можем получить информацию об аннотации, используя метод getAnnotation() класса Method :

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());

Теперь давайте снова запустим наш тест remove() и проверим, что мы получили:

Account operation annotation: @com.foreach.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));

Давайте теперь создадим новый тест removeWhenLimitReached , который заставит метод remove() превышать установленный лимит на снятие средств:

@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.foreach.method.info.WithdrawLimitException

Наш последний тест будет полезен для демонстрации метода getBalance() . Как мы уже говорили ранее, аспект не перехватывает его, потому что в объявлении метода нет аннотации AccountOperation :

@Test
void getBalance() {
bankAccountService.getBalance();
}

При запуске этого теста в консоли нет вывода, как мы и ожидали.

6. Заключение

В этой статье мы увидели, как получить всю доступную информацию о методе, используя аспект Spring AOP. Мы сделали это, определив pointcut, распечатав информацию в консоли и проверив результаты выполнения тестов.

Исходный код нашего приложения доступен на GitHub .