在@Around 建议中使用和不使用@RequestBody 处理请求

Process requests with and without @RequestBody in the @Around advice

我有这样的基于方面的日志记录:

@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@RequestParamsimpleProcess() 没有被触发,而是在 processWithBody() 我收到路径变量值作为 body 参数。

为什么会发生这种情况以及我如何以不同方式处理两种类型的请求(如果可能,在同一个建议中)?

您犯了三个基本错误:

  • 您正在尝试从 args() 中匹配参数参数注释,但它没有任何效果,这就是为什么 processWithBody(..) 匹配不需要的参数并将其绑定到 body。它应该被转移到一个execution()切入点。

  • 你的切入点语法是错误的,即使你将它转移到 execution(),即如果类型(!)该参数有一个 @RequestBody 注释,而不是参数本身。
    为了实现这一点,您需要将参数本身放在括号中,如 (*),即
    execution(* *(@org.springframework.web.bind.annotation.RequestBody (*), ..)).

  • 你必须确保切入点是互斥的,否则多个通知将在同一个连接点上匹配。准确的说,需要区分以下几种情况:

    • @Loggable 注释的方法,第一个方法参数由 @RequestBody
    • 注释
    • @Loggable 注释的方法,第一个方法参数 not@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

P.S.: execution(* *(@MyAnn *))execution(* *(@MyAnn (*))) 之间的区别是微妙的,因此很棘手。不幸的是,它没有正确记录 here 它应该在哪里。准确地说,后一种情况根本没有记录,可能只有在某些 AspectJ 发行说明中,当然还有单元测试中。但是没有普通用户会看那里。