Scala开发Spring Boot:Configuration类注入ConfigurationProperties异常排查
这个问题我之前也踩过坑,核心原因是Spring对@Configuration类的特殊代理机制,和Scala的构造函数编译逻辑撞在了一起,我来给你拆解清楚原因,再给几个可行的解决方案:
为什么会出现这些问题?
1. 构造注入报错NoSuchMethodException
Spring默认会给@Configuration类生成CGLIB动态代理,用来拦截@Bean方法调用,确保bean的单例特性。这个代理类需要调用父类(也就是你的Config类)的无参构造函数来实例化,但你的Config类只有带@Autowired参数的构造函数——Scala编译这类类时,默认不会生成无参构造,所以代理类找不到对应的构造方法,直接抛出异常。
2. 字段注入后properties为null
同样是CGLIB代理的锅:代理类的字段注入时机,晚于你可能使用这个字段的时机(比如在@Bean方法里直接引用properties)。当Spring调用配置类的@Bean方法时,代理类还没完成对原始类字段的注入,所以你拿到的就是null。
而@Service类的构造注入能正常工作,是因为Spring对服务类的代理逻辑不同:哪怕用CGLIB代理,服务类的代理会直接通过带参数的构造函数传递依赖,不需要依赖无参构造。
解决方案
方案1:关闭@Configuration的代理(最推荐)
从Spring Boot 2.2+开始,官方推荐关闭配置类的代理(当你的@Bean方法之间没有互相调用时),这样Spring就不会生成CGLIB代理,直接用原始类实例,构造注入就能正常工作:
@Configuration(proxyBeanMethods = false) class Config @Autowired()(var properties: MessagePathProperties) { // 在这里可以正常使用properties @Bean def messageService(): MessageService = new MessageService(properties) }
这个方式不仅解决了注入问题,还能提升应用启动性能。
方案2:给Config类添加无参构造函数
如果你的配置类必须保留代理(比如@Bean方法之间有依赖调用),可以通过Scala的辅助构造函数实现无参构造:
@Configuration class Config { private var properties: MessagePathProperties = _ // 带参数的构造函数用于Spring注入 @Autowired def this(properties: MessagePathProperties) { this() // 调用无参辅助构造 this.properties = properties } // 现在可以正常使用properties了 }
这样CGLIB代理类能正常实例化,Spring也会调用带参数的构造函数完成注入。
方案3:在@Bean方法中直接注入依赖
更简洁的方式是,不在Config类中保存properties字段,而是直接在@Bean方法的参数里注入:
@Configuration class Config { @Bean def messageService(properties: MessagePathProperties): MessageService = { new MessageService(properties) } }
Spring会自动解析@Bean方法的参数,注入对应的bean,完全绕开配置类的注入问题。
额外注意事项
- 确保你的
MessagePathProperties类正确标注了@ConfigurationProperties(prefix = "你的配置前缀"),你的应用类已经加了@EnableConfigurationProperties,这部分你已经做对了。 - 不要把
@Configuration类定义为Scala的case class——case class默认是final的,CGLIB无法生成代理类,会导致配置类失效。
内容的提问来源于stack exchange,提问作者Tonny Tc

