【问题标题】:Spring Boot Web (Gradle config) breaks when I add AOP/Advice当我添加 AOP/Advice 时 Spring Boot Web(Gradle 配置)中断
【发布时间】:2021-08-05 16:45:05
【问题描述】:

我很确定这是一个错误配置,我只是不知道是哪一部分,因为我发现 AOP 教程都使用了 Maven,但我使用的是 Gradle。

所以我采用了 start.spring.io 创建的项目(Kotlin 1.5.21、Gradle 7.1.1、Spring Boot 2.5.3)和 spring-boot-starter-web 的启动器。

plugins {
    id("org.springframework.boot") version "2.5.3"
    id("io.spring.dependency-management") version "1.0.11.RELEASE"
    kotlin("jvm") version "1.5.21"
    kotlin("plugin.spring") version "1.5.21"
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
}

然后我添加了

implementation("org.springframework.boot:spring-boot-starter-aop")

和一个建议

@Target(AnnotationTarget.FUNCTION) @MustBeDocumented
annotation class UserAuthorized(val roles: Array<String>)

@Component @Aspect
class UserAuthorizedAdvice {
    @Pointcut("@target(com.example.springbootplayground2.auth.UserAuthorized)")
    fun userAuthorizedMethods() {}

    @Before("userAuthorizedMethods()")
    fun checkIfUserHasRequiredRole(jp: JoinPoint) {...}
}

现在应用程序在启动时崩溃(最后出现错误消息)。我尝试通过添加 @EnableAspectJAutoProxy 注释来修复它,但它没有帮助。

@SpringBootApplication
@EnableAspectJAutoProxy(proxyTargetClass=true)
class SpringbootPlayground2Application

在 Linux 上,这会崩溃

org.springframework.context.ApplicationContextException: Unable to start web server; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'tomcatServletWebServerFactory' defined in class path resource [org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryConfiguration$EmbeddedTomcat.class]: Initialization of bean failed; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'errorPageCustomizer' defined in class path resource [org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfiguration.class]: Unsatisfied dependency expressed through method 'errorPageCustomizer' parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dispatcherServletRegistration' defined in class path resource [org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean]: Factory method 'dispatcherServletRegistration' threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'multipartConfigElement' defined in class path resource [org/springframework/boot/autoconfigure/web/servlet/MultipartAutoConfiguration.class]: Initialization of bean failed; nested exception is org.springframework.aop.framework.AopConfigException: Unexpected AOP exception; nested exception is java.lang.NullPointerException: Cannot invoke "Object.getClass()" because "cause" is null

引起

Caused by: java.lang.NullPointerException: Cannot invoke "Object.getClass()" because "cause" is null
    at org.springframework.cglib.core.CodeGenerationException.<init>(CodeGenerationException.java:25) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:587) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:363) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:585) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:110) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:108) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.cglib.core.internal.LoadingCache$2.call(LoadingCache.java:54) ~[spring-core-5.3.9.jar:5.3.9]
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) ~[na:na]
    at org.springframework.cglib.core.internal.LoadingCache.createEntry(LoadingCache.java:61) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.cglib.core.internal.LoadingCache.get(LoadingCache.java:34) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:134) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:319) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:572) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.cglib.proxy.Enhancer.createClass(Enhancer.java:419) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.aop.framework.ObjenesisCglibAopProxy.createProxyClassAndInstance(ObjenesisCglibAopProxy.java:57) ~[spring-aop-5.3.9.jar:5.3.9]
    at org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:206) ~[spring-aop-5.3.9.jar:5.3.9]

在 MacOs (M1-cpu) 上,这会因java.lang.NullPointerException: null 而崩溃,原因是

Caused by: org.springframework.boot.web.server.WebServerException: Unable to start embedded Tomcat
                at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize(TomcatWebServer.java:142) ~[spring-boot-2.5.3.jar:2.5.3]
                at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.<init>(TomcatWebServer.java:104) ~[spring-boot-2.5.3.jar:2.5.3]
                at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getTomcatWebServer(TomcatServletWebServerFactory.java:450) ~[spring-boot-2.5.3.jar:2.5.3]
                at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getWebServer(TomcatServletWebServerFactory.java:199) ~[spring-boot-2.5.3.jar:2.5.3]
                at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory$$FastClassBySpringCGLIB$$9c83fa9f.invoke(<generated>) ~[spring-boot-2.5.3.jar:2.5.3]
                at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.3.9.jar:5.3.9]
                at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:779) ~[spring-aop-5.3.9.jar:5.3.9]
                at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.3.9.jar:5.3.9]
                at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) ~[spring-aop-5.3.9.jar:5.3.9]
                at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:180) ~[spring-aop-5.3.9.jar:5.3.9]
                at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) ~[spring-aop-5.3.9.jar:5.3.9]
                at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97) ~[spring-aop-5.3.9.jar:5.3.9]
                at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.9.jar:5.3.9]
                at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) ~[spring-aop-5.3.9.jar:5.3.9]
                at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692) ~[spring-aop-5.3.9.jar:5.3.9]
                at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory$$EnhancerBySpringCGLIB$$f87b4803.getWebServer(<generated>) ~[spring-boot-2.5.3.jar:2.5.3]
                at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer(ServletWebServerApplicationContext.java:182) ~[spring-boot-2.5.3.jar:2.5.3]
                at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:160) ~[spring-boot-2.5.3.jar:2.5.3]
                ... 8 common frames omitted
Caused by: java.lang.IllegalStateException: StandardEngine[Tomcat].StandardHost[localhost].TomcatEmbeddedContext[] failed to start
                at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.rethrowDeferredStartupExceptions(TomcatWebServer.java:187) ~[spring-boot-2.5.3.jar:2.5.3]
                at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize(TomcatWebServer.java:126) ~[spring-boot-2.5.3.jar:2.5.3]
                ... 25 common frames omitted

更新

在这里你可以找到minimal version of my project

更新2

该项目现在有一个 solution branch 包含 kriegaex 的修复。

【问题讨论】:

  • 在要求您提供MCVE 让我重现您的问题之前,我冒着风险猜测:您是否尝试限制切入点范围,添加&amp;&amp; within(my.base.pkg..*)
  • 我的切入点现在看起来像这样:@Pointcut("@target(com.example.springbootplayground2.auth.UserAuthorized) &amp;&amp; within(com.example..* )")。正面:应用程序在启动时不再失败。否定:建议甚至没有添加到我自己的课程中,特别是com.example.springbootplayground2.auth.AuthTestController
  • 就像我想的那样,你的切入点太宽泛了。但是现在没有应用任何方面,不应该发生。你确定没有错字,比如com.example.*(单点)而不是com.example..*(双点)? Spring AOP 和组件扫描是否配置正确?另一个简单的切入点,例如execution(* my.package.MyClass.myMethod(..)) 是否有效?我认为,GitHub 上的 MCVE 会有所帮助。
  • a) 我不明白切入点怎么会太宽泛(尽管看起来确实如此)。这不像注释com.example.springbootplayground2.auth.UserAuthorized 可能存在于其他地方。 b) 不,我现在在我的切入点中看不到任何错字 c) Spring AOP 可能配置错误,但我不知道更好但幸运的是您可以签入 d) MCVE:github.com/simon-void/SpringWebAOP-MCVE
  • (a) 因为@target 是一个动态切入点,其可能的目标只能在运行时确定。因此,Spring 为它找到的所有组件创建代理,包括 Spring 内部的。使用 AspectJ,您不会遇到这个问题,因此如果您愿意,可以切换到 AspectJ。但实际上,在这种情况下,限制切入点范围要容易得多。 (d) 我去看看。感谢您提供 GitHub 链接。

标签: spring spring-mvc gradle spring-aop


【解决方案1】:

实际上,我非常专注于帮助您解决范围界定问题,然后考虑您对未提及方面的描述,以至于我忽略了明显的:

您只是使用了错误的切入点类型。 @target() 用于类型注解,而您使用需要@annotation() 切入点的方法注解:

@Pointcut("@annotation(com.example.springbootplayground2.UserAuthorized)")
fun userAuthorizedMethods() {}

现在你的方面开始了,因为@annotation()可以静态确定,你甚至不需要&amp;&amp; within(...)。但添加它作为保护措施总是一个好主意。

为您吸取的教训:始终尝试提供所有相关代码,如果您不确定错误的位置,请始终发布MCVE。您没有使用带注释的方法发布您的控制器,否则我可能会立即注意到。现在,我只有在克隆存储库并等待 Gradle 完成占用带宽的下载操作后才有机会注意到故障。 ?

【讨论】:

  • 感谢您的澄清。我将删除我的评论以避免编辑。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-10-11
  • 1970-01-01
相关资源
最近更新 更多