Обрабатывать запросы с использованием и без @RequestBody в рекомендации @Around - PullRequest
0 голосов
/ 17 апреля 2020

У меня есть такие журналы, основанные на аспектах:

@Pointcut("@annotation(Loggable)")
public void loggableAnnotation() {}

@Around("loggableAnnotation()")
public Object simpleProcess(ProceedingJoinPoint joinPoint) throws Throwable {
  return this.processWithBody(joinPoint, null);
}

@Around("loggableAnnotation() && args(@org.springframework.web.bind.annotation.RequestBody body,..)")
public Object processWithBody(ProceedingJoinPoint joinPoint, Object body) throws Throwable {
  // do things
}

И это прекрасно работает, когда я выполняю запрос с @RequestBody, совет processWithBody() срабатывает. Но когда я выполняю запрос, который не имеет @RequestBody (только @PathVariable и @RequestParam), simpleProcess() не запускается, вместо этого в processWithBody() я получаю значение переменной пути в качестве параметра body.

Почему это происходит и как я могу по-разному обрабатывать два типа запросов (по возможности, в одном совете)?

1 Ответ

1 голос
/ 18 апреля 2020

Вы делаете три базовых c неверных значения:

  • Вы пытаетесь сопоставить аннотации аргументов параметров из args(), но там это не имеет никакого эффекта, поэтому processWithBody(..) соответствует нежелательному параметру и связывает его с body. Он должен быть переведен в execution() pointcut.

  • Ваш синтаксис pointcut неверен, даже если вы перенесете его в execution(), то есть что-то вроде
    execution(* *(@org.springframework.web.bind.annotation.RequestBody *, ..)) будет соответствовать если тип (!) параметра имеет аннотацию @RequestBody, а не сам параметр.
    Для этого необходимо поместить сам параметр в скобки, например (*), то есть
    execution(* *(@org.springframework.web.bind.annotation.RequestBody (*), ..)) .

  • Вы должны убедиться, что pointcut являются взаимоисключающими, в противном случае несколько советов должны совпадать на одной точке соединения. Чтобы быть точным, необходимо различать следующие случаи:

    • методы, аннотированные @Loggable с первым параметром метода, аннотированным @RequestBody
    • методы, аннотированные @Loggable с первым параметром метода не , аннотированным @RequestBody
    • методами, аннотированными @Loggable без каких-либо параметров

Вот простой пример Java + AspectJ (без Spring или Spring AOP), но синтаксис аспекта должен быть идентичен в Spring AOP:

Аннотация + приложение драйвера:

package de.scrum_master.app;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Retention(RUNTIME)
@Target(METHOD)
public @interface Loggable {}
package de.scrum_master.app;

import org.springframework.web.bind.annotation.RequestBody;

public class Application {
  public static void main(String[] args) {
    Application application = new Application();
    application.doNotLogMe("foo", 11);
    application.doNotLogMeEither();
    application.doNotLogMeEither("foo", 11);
    application.logMe("foo", 11);
    application.logMeToo("foo", 11);
    application.logMeToo();
  }

  public void doNotLogMe(@RequestBody String body, int number) {}
  public void doNotLogMeEither() {}
  public void doNotLogMeEither(String body, int number) {}
  @Loggable public void logMe(@RequestBody String body, int number) {}
  @Loggable public void logMeToo(String body, int number) {}
  @Loggable public void logMeToo() {}
}

Аспект:

Как вы можете видеть, я использую дифференцирование трех упомянутых выше случаев, а также удовлетворяю вашу потребность в общем вспомогательном методе, который я назвал logIt(..). Там вы можете поместить все сложные средства ведения журналов, которые хотите использовать, без дублирования кода в ваших методах рекомендаций.

package de.scrum_master.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class MyAspect {
  @Pointcut("@annotation(de.scrum_master.app.Loggable)")
  public void loggableAnnotation() {}

  @Around(
    "loggableAnnotation() && " + 
    "execution(* *())"
  )
  public Object simpleProcessWithoutParameters(ProceedingJoinPoint joinPoint) throws Throwable {
    return logIt(joinPoint, null);
  }

  @Around(
    "loggableAnnotation() && " +
    "execution(* *(!@org.springframework.web.bind.annotation.RequestBody (*), ..))"
  )
  public Object simpleProcessWithParameters(ProceedingJoinPoint joinPoint) throws Throwable {
    return logIt(joinPoint, null);
  }

  @Around(
    "loggableAnnotation() && " + 
    "execution(* *(@org.springframework.web.bind.annotation.RequestBody (*), ..)) && " +
    "args(body, ..)"
  )
  public Object processWithBody(ProceedingJoinPoint joinPoint, Object body) throws Throwable {
    return logIt(joinPoint, body);
  }

  private Object logIt(ProceedingJoinPoint joinPoint, Object body) throws Throwable {
    System.out.println(joinPoint + " -> " + body);
    return joinPoint.proceed();
  }
}

Журнал консоли:

execution(void de.scrum_master.app.Application.logMe(String, int)) -> foo
execution(void de.scrum_master.app.Application.logMeToo(String, int)) -> null
execution(void de.scrum_master.app.Application.logMeToo()) -> null

PS: разница между execution(* *(@MyAnn *)) и execution(* *(@MyAnn (*))) тонкая и поэтому хитрая. К сожалению, это не задокументировано должным образом здесь , где это должно быть. Если быть точным, последний случай вообще не документирован, возможно, только в некоторых заметках о выпуске AspectJ и, конечно, в модульных тестах. Но ни один нормальный пользователь не заглянет туда.

...