Spring Boot 3.3中@Validated与@ConfigurationProperties结合记录类时触发AopConfigException异常问题
我来帮你拆解这个问题哈~
首先还原下你的场景:你在Spring Boot 3.3里尝试用Java记录类(Record)配合@ConfigurationProperties和@Validated注解来做配置属性的校验,代码大概是这样的:
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.validation.annotation.Validated; @Validated @ConfigurationProperties("blah") public record Blah(int foo) { }
但启动时却抛出了这个异常:
Caused by: org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class com.example.Blah: Common causes of this problem include using a final class or a non-visible class
而且去掉@Validated之后就一切正常,你疑惑是不是这俩注解和记录类的组合本身就不被支持?
问题根源
这个问题的核心其实和Java记录类的特性有关:Java的Record默认是final类,而且它的所有属性访问方法(比如这里的foo())也是final的。
而Spring的@Validated注解是通过AOP切面来实现校验逻辑的,默认情况下会使用CGLIB动态代理——CGLIB的工作原理是生成目标类的子类,通过子类来织入切面逻辑。但final类是不能被继承的,这就直接导致CGLIB无法生成代理子类,从而抛出这个异常。
解决方案
给你几个可行的解决思路,你可以根据自己的场景选择:
1. 改用普通Java类替代记录类
如果你的业务场景允许,把Record改成普通的Java类(可以配合Lombok简化代码),这样CGLIB就能正常生成子类代理了:
import lombok.Getter; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.validation.annotation.Validated; @Validated @ConfigurationProperties("blah") @Getter public class Blah { private final int foo; public Blah(int foo) { this.foo = foo; } }
2. 直接在属性上使用校验注解,去掉类上的@Validated
其实Spring Boot的@ConfigurationProperties本身就支持属性校验,不需要依赖@Validated的AOP代理。你可以直接在Record的属性上标注校验注解(比如@Min、@Max、@NotNull等),Spring会在绑定配置属性的时候直接触发校验:
import jakarta.validation.constraints.Min; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties("blah") public record Blah(@Min(1) int foo) { }
记得要确保你的项目里引入了spring-boot-starter-validation依赖,同时在启动类或者配置类上加上@EnableConfigurationProperties(Blah.class)来启用这个配置属性类。
这种方式不需要AOP代理,完美避开了Record的final特性限制,是比较推荐的方案。
备注:内容来源于stack exchange,提问作者levant pied

