Spring Modulith导致@ConfigurationProperties类CGLIB子类生成异常
Spring Modulith + Kotlin 配置类CGLIB代理异常问题分析与解决
问题描述
我们基于Kotlin搭建了Spring Boot项目,使用Spring Modulith管控模块边界,包结构如下:
my.company.myapp -> MySpringBootApp -> config -> SpringSecurityConfig.kt -> SecurityProperties.kt -> usecase1 -> <fleshed out implementation for use case 1> -> usecase2..usecaseN -> no implementation as of yet
SecurityProperties类代码:
@ConfigurationProperties(prefix = "security") data class SecurityProperties( val users: List<User> ) data class User( val username: String, val password: String, val roles: Set<Role> ) enum class Role { MONITORING, API_USER }
使用TestContainers时,在src/test/kotlin编写了本地启动类:
object MyLocalSpringBootApp { @JvmStatic fun main(args: Array<String>) { fromApplication<MySpringBootApp>() .with(ContainersConfig::class.java) .run(*args) } }
启动时抛出异常:
... Caused by: org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class my.company.myapp.config.SecurityProperties: Common causes of this problem include using a final class or a non-visible class at org.springframework.aop.framework.CglibAopProxy.buildProxy(CglibAopProxy.java:227) at org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:160) at org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:110) at org.springframework.modulith.observability.ModuleTracingSupport.addAdvisor(ModuleTracingSupport.java:58) at org.springframework.modulith.observability.ModuleTracingSupport.addAdvisor(ModuleTracingSupport.java:42) at org.springframework.modulith.observability.ModuleTracingBeanPostProcessor.lambda$postProcessAfterInitialization$0(ModuleTracingBeanPostProcessor.java:92) at java.base/java.util.Optional.map(Optional.java:260) at org.springframework.modulith.observability.ModuleTracingBeanPostProcessor.postProcessAfterInitialization(ModuleTracingBeanPostProcessor.java:87) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:437) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1776) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:599) ... 51 common frames omitted
已使用带spring插件的kotlin-maven-plugin,理论上不可变Kotlin数据类不应出现final类问题。
临时解决方法
- 移除pom.xml中所有Spring Modulith依赖,应用可正常启动;
- 为
SecurityProperties添加冗余@Configuration注解并保留Spring Modulith依赖,应用可正常启动; - 将"config"下的类移至"usecase1"目录,应用可正常启动,但推测实现其他用例后会失效。
疑问
该问题是配置/使用不当,还是Spring Modulith的Bug?
更多细节
- Spring Boot 3.2.1
- Spring Modulith 1.1.2
- Kotlin 1.9.21
kotlin-maven-plugin配置:
<plugin> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-maven-plugin</artifactId> <version>${kotlin.version}</version> <executions> <execution> <id>compile</id> <goals> <goal>compile</goal> </goals> <configuration> <sourceDirs> <sourceDir>${project.basedir}/src/main/kotlin</sourceDir> </sourceDirs> </configuration> </execution> <execution> <id>test-compile</id> <goals> <goal>test-compile</goal> </goals> <configuration> <sourceDirs> <sourceDir>${project.basedir}/src/test/kotlin</sourceDir> </sourceDirs> </configuration> </execution> </executions> <configuration> <args> <arg>-Xjsr305=strict</arg> <arg>-opt-in=kotlin.RequiresOptIn</arg> <arg>-java-parameters</arg> </args> <compilerPlugins> <plugin>spring</plugin> <plugin>jpa</plugin> </compilerPlugins> </configuration> <dependencies> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-maven-allopen</artifactId> <version>${kotlin.version}</version> </dependency> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-maven-noarg</artifactId> <version>${kotlin.version}</version> </dependency> </dependencies> </plugin>
根因分析
这并非Spring Modulith的Bug,而是Kotlin allopen插件的默认配置未覆盖@ConfigurationProperties注解导致的:
- Spring Modulith的
ModuleTracingBeanPostProcessor会为跨模块调用的Bean生成CGLIB代理,用于模块间调用追踪; - Kotlin数据类默认是final的,
spring插件(即allopen)仅会自动开放带有@Configuration、@Controller、@Service等Spring stereotype注解的类; @ConfigurationProperties不在allopen默认开放的注解列表中,因此SecurityProperties仍为final类,CGLIB无法生成其子类,触发异常。
临时解决方法的原理:
- 移除Spring Modulith依赖后,
ModuleTracingBeanPostProcessor不存在,不会触发代理创建; - 添加
@Configuration注解后,allopen插件将类改为非final,CGLIB可生成代理; - 移至
usecase1目录后,该类被识别为模块内部Bean,Spring Modulith暂时跳过代理逻辑,但后续跨模块调用时仍可能复现问题。
正确解决方案
方案1:让@ConfigurationProperties类被allopen插件处理
修改kotlin-maven-plugin的配置,添加@ConfigurationProperties到开放注解列表:
<plugin> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-maven-plugin</artifactId> <!-- 其他配置不变 --> <configuration> <!-- 其他args不变 --> <compilerPlugins> <plugin>spring</plugin> <plugin>jpa</plugin> </compilerPlugins> <pluginOptions> <option>spring:annotation=org.springframework.boot.context.properties.ConfigurationProperties</option> </pluginOptions> </configuration> <!-- 其他依赖不变 --> </plugin>
此方案会自动将所有带有@ConfigurationProperties的类改为非final,从根本上解决CGLIB代理问题。
方案2:排除SecurityProperties被模块追踪代理
自定义Spring配置,让ModuleTracingBeanPostProcessor跳过该类:
@Configuration class CustomModuleTracingConfiguration { @Bean fun moduleTracingBeanPostProcessor(applicationModules: ApplicationModules): ModuleTracingBeanPostProcessor { return ModuleTracingBeanPostProcessor(applicationModules) .excludeBeansOfType(SecurityProperties::class.java) } }
方案3:将config包标记为共享模块
在Spring Modulith中,共享模块的Bean不会被视为跨模块调用的代理目标。可通过application-modules.properties配置:
spring.modulith.modules.config.name=config spring.modulith.modules.config.shared=true
或在包上添加@Module(shared = true)注解(需确保包被Spring扫描到)。
内容的提问来源于stack exchange,提问作者Oliver Plaehn
相关产品推荐
相关产品推荐

