You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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注解导致的:

  1. Spring Modulith的ModuleTracingBeanPostProcessor会为跨模块调用的Bean生成CGLIB代理,用于模块间调用追踪;
  2. Kotlin数据类默认是final的,spring插件(即allopen)仅会自动开放带有@Configuration、@Controller、@Service等Spring stereotype注解的类;
  3. @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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 22:45:54