将 AOP 添加到 Spring 引导进程将方法返回的对象更改为 null

Adding AOP to Spring Boot process changes object returned by method to null

我想将用于日志记录的 AOP 添加到我的 Spring 启动应用程序。但它似乎以意想不到的方式改变了我的应用程序的行为。

例如,我的应用程序有一个方法 doThis(),它创建一个 MyObject 实例:

MyObject myObect = doThis();  // a non-null myObject is returned from doThis()

效果很好,myObject 按预期填充了 return 来自 doThis() 的实例。但我希望 doThis() 方法 通过 AOP 记录一些消息。

那么我补充一下class:

@Aspect
public class LoggingAspect {

    private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);

    @Around("execution(* my.service.package.*.*(..))")
    public void log(ProceedingJoinPoint joinPoint) throws Throwable {
        logger.info("before");
        joinPoint.proceed();
        logger.info("after");
    }
}

我也添加了这个配置class:

@Configuration
@ComponentScan(basePackages = "my.service.package")
@EnableAspectJAutoProxy
public class AppConfig {
    @Bean
    public LoggingAspect aspect() {
        return new LoggingAspect();
    }
}

然而,现在当我 运行 我的应用程序时,日志记录语句确实出现了,正如预期的那样 - 但现在相同的 doThis() 方法显然 return 是一个空对象:

MyObject myObect = doThis(); // myObject is now unexplainedly null

但这不是真的!我的意思是当我在 doThis() 的最后一行设置断点时,它即将 return 的 MyObject 实例非常清楚 not null .它已在 doThis() 方法中创建和填充。那么它去了哪里?为什么 myObject 没有 doThis() 显然 return 编辑了一个非空 MyObject 实例时得到填充?

这方面似乎以某种方式使 return 从 doThis() 编辑的对象无效。谁看过这个吗?有什么解决办法吗?

我相信我的 execution 语句中的第一个 * 应该表明拦截的方法可以具有任何 return 类型。但是,我截获的方法中的 returned 值似乎以某种方式变为 null。

我正在研究如何根据评论创建一个“最小可重现示例”,我将在此处添加,但这似乎是一个相当标准的 AOP 用例,因此将它扔到那里与此同时,以防有人有一些见识。

您犯了一个简单的初学者 AOP 错误:您的 @Around 建议继续进行,但没有 return proceed() 调用的结果。你的建议方法有一个 void return 类型,你拦截的目标方法没有。所以建议隐式 returns null。顺便说一下,对于像 int 这样的基本类型,这甚至不会工作,并且会因为不兼容的 return 类型而抛出异常。我真的很惊讶 Spring AOP,奇怪的是,如果 around-advice return 无效,甚至会拦截 non-void 方法,因为 AFAIR 本机 AspectJ 不匹配 non-void本例中的方法。

那你能做什么?

  • 要么保留 @Around 建议,如果你真的认为你需要它。通常,只有当你想做的不仅仅是记录事情时才会出现这种情况,例如修改方法参数或 return 值,处理异常或其他可能改变控制流的事情:

    @Around("execution(* my.service.package.*.*(..))")
    public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
      logger.info("[BEFORE] " + joinPoint);
      try {
        return joinPoint.proceed();
      }
      finally {
        logger.info("[AFTER] " + joinPoint);
      }
    }
    
  • 或者保持简单,只使用一对 @Before@After 通知方法,如果不需要将数据从一个通知传输到另一个通知。这要简单得多,因为你不需要继续,使用 try-finally 或 return anything:

    @Before("execution(* my.service.package.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
      logger.info("[BEFORE] " + joinPoint);
    }
    
    @After("execution(* my.service.package.*.*(..))")
    public void logAfter(JoinPoint joinPoint) {
      logger.info("[AFTER] " + joinPoint);
    }
    

    在这里,您还可以将重复的切入点表达式提取到它自己的 @Pointcut 中,并从两种建议方法中简单地引用它。