在@Around建议中处理带有和不带有@RequestBody的请求



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

@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 *, ..))的内容将匹配
    为了实现这一点,您需要将参数本身放入括号中,如(*),即
    execution(* *(@org.springframework.web.bind.annotation.RequestBody (*), ..))

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

    • @Loggable注释的方法,带有由@RequestBody注释的第一个方法参数
    • @Loggable注释的方法,第一个方法参数而不是由@RequestBody注释的
    • 不带任何参数的@Loggable注释的方法

这里有一个普通Java+AAspectJ(没有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

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

最新更新