在@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
和 @RequestParam
) simpleProcess()
没有被触发,而是在 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 发行说明中,当然还有单元测试中。但是没有普通用户会看那里。
我有这样的基于方面的日志记录:
@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
参数。
为什么会发生这种情况以及我如何以不同方式处理两种类型的请求(如果可能,在同一个建议中)?
您犯了三个基本错误:
您正在尝试从
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 发行说明中,当然还有单元测试中。但是没有普通用户会看那里。