【问题标题】:Adding AOP to Spring Boot process changes object returned by method to null将 AOP 添加到 Spring Boot 进程会将方法返回的对象更改为 null
【发布时间】:2023-01-10 00:03:03
【问题描述】:

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

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

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

效果很好,并且 myObject 已按预期填充了从 doThis() 返回的实例。但是我想要doThis()方法通过 AOP 记录一些消息。

然后我添加以下方面类:

@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");
    }
}

我还添加了这个配置类:

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

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

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

但事实并非如此!我的意思是当我在doThis()的最后一行设置断点时,它即将返回的MyObject实例非常清楚不为空.它已在 doThis() 方法中创建和填充。那么它去了哪里?为什么myObject不是doThis() 明确返回一个非空 MyObject 实例时得到填充?

似乎这个方面以某种方式使从 doThis() 返回的对象无效。有没有人见过这个?有什么办法吗?

我相信我的第一个*执行语句应该表明被拦截的方法可以有任何返回类型。但是我截获的方法的返回值似乎以某种方式更改为 null。

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

【问题讨论】:

  • 请提供minimal reproducible example。你确认myService.doThis()返回null了吗?
  • myService.doThis()其实返回的是一个实体对象。而且这方面似乎有效。但是当我实际尝试保存返回的实体对象时它不起作用。
  • 我要关闭你的错误消息“实体不能为空”
  • 有趣的是 doThis() 似乎确实返回 null - 但仅在我添加方面代码之后。
  • 请提供 minimal reproducible example 以便我们了解原因。

标签: java spring aop aspectj spring-aop


【解决方案1】:

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

所以,你可以做什么?

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

    @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 或返回任何内容:

    @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 中,并从两种建议方法中简单地引用它。

【讨论】:

  • 效果很好-谢谢!我实际上直接从我买的一本书“Spring Start Here”中举了例子。试了几次都不知道为什么不行!
  • 我很快下载了那本书的免费源代码包,可以看到您的方面取自第 6 章示例 1。在这种情况下,示例是正确的,因为它试图拦截的服务方法都有 void 返回类型。日志记录方面在示例 2-7 中得到了改进,并且在这些示例中的任何地方都返回 Object,就像我的解决方案一样。因此,将示例代码复制并粘贴到它不适合的目标应用程序中是一个坏主意。至少,在你这样做之前,读完并尝试完全理解那一章。 ?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-09
  • 1970-01-01
  • 1970-01-01
  • 2019-07-15
  • 1970-01-01
相关资源
最近更新 更多