将 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
中,并从两种建议方法中简单地引用它。
我想将用于日志记录的 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
中,并从两种建议方法中简单地引用它。