当我在Bean方法上定义了Aspect时,SpringBoot应用程序启动失败



使用 Springboot 2.7.0。我有一个工作的应用程序,我在其上进行了这些更改

方面配置

@Configuration
@EnableAspectJAutoProxy
@ComponentScan
public class AspectConfig {}

方面接口

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Timed  { }

用于测量方法执行时间的方面类

@Around("@annotation(Timed)")
public Object measureExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object proceed = joinPoint.proceed();
LOG.info("Time taken for {} is {} ms, joinPoint.getSignature(), System.currentTimeMillis() - start,);
return proceed;
}

将新的@Timed注释添加到 Bean 中的现有方法(省略不相关的代码)

@Component
@ConditionalOnExpression("${oauth.enabled}")
public class JwtAuthFilter extends OncePerRequestFilter {
@Timed
public boolean verifySignatureAndExpiry(String bearerToken){
// method logic
}
}

这会导致 Springboot 应用程序无法启动。

如果我将@Aspect添加到 JwtAuthFilter 类,我可以让它启动。

但是为什么我需要这样做呢?如果我必须对每个需要使用它的类进行注释,它会使@Timed注释的使用受到限制@Aspect。更不用说,虽然没有错误,但该功能将无法正常工作,因为一个方面无法在另一个方面工作。

不过,@Timed适用于我的控制器方法。

@RestController
@RequestMapping(value = "/api/v1", produces = MediaType.APPLICATION_JSON_VALUE)
public class HealthController {
@GetMapping("/health")
@Timed
public Map<String, String> health(){
return Map.of("status", "up");
}
}

这会导致 Spring 引导应用程序启动失败。

您应该始终发布错误消息和相关堆栈跟踪,而不仅仅是说"无法启动"。你很幸运,在这种情况下,我记得情况,所以我可以回答你的问题。通常,如果没有进一步的信息,我将无法这样做。

如果我将@Aspect添加到JwtAuthFilter类中,我可以让它启动。

这没有任何意义。你为什么要在不是方面的东西上加上@Aspect?当然,它会使启动错误消失,但它也会使您的真实方面不会触发,因为正如您已经提到的,一个Spring AOP方面不能建议另一个方面。因此,恕我直言,这种方法完全是胡说八道。

异常的原因是:您不能通过Spring AOP建议您的过滤器,因为它是从GenericFilterBean派生的,它有一些最终方法。最终方法不能被覆盖,因此也不能被代理。这具有直接在代理实例上调用这些方法而不是委托给目标对象的效果,即,如果这样的方法访问实例字段,它将发现它未初始化,因为代理的字段不是要初始化的,只有目标对象的字段。另请参阅我的回答 此处 了解更多信息。

在这种情况下,最终方法org.springframework.web.filter.GenericFilterBean#init是尝试访问this.logger,这会导致NPE,这使得Spring Boot的Tomcat无法启动。春季问题 #27963 的此评论中已报告并简要解释了此问题,该评论已因无效而关闭。

不过,@Timed适用于我的控制器方法。

是的,因为您的控制器在从最终方法访问实例字段时没有问题。

如果你绝对认为你需要从某个方面来衡量你的过滤器方法的执行时间,你可以从Spring AOP切换到原生AspectJ,要么通过加载时编织来为整个项目切换,也可以通过编译时编织有选择地为某些目标类切换。我已经在本地尝试过,它适用于正确的切入点。然后,您还可以建议过滤器。仅供参考,切入点是这样的:

// Annotated class
@Around("execution(* *(..)) && !within(MyAspect) && @target(Timed)")
// Annotated method
@Around("execution(* *(..)) && !within(MyAspect) && @annotation(Timed)")

AspectJ 比 Spring AOP 更强大,因此您明确需要限制对方法执行的匹配,否则其他连接点(如方法调用、构造函数调用等)也会受到影响。您还需要确保该方面不会建议自己或其他方面,这在AspectJ中是完全可能的。

相关内容

最新更新