如何在Scala Object中使用Spring Beans?注入异常与配置问题求解
问题描述
Scala代码示例
@Component class A (@Autowired val componentB: B) {Something} @Component class B (@Autowired val beanC: C) {Something} @Component class D () { @Value("${configProperty}") val configProperty = "" @Bean def foobar: C = {Something} } @SpringBootApplication class MainClass extends ApplicationRunner { def main(): Unit = { SpringApplication.run(classOf[MainClass]) } override def run(): Unit = {Do nothing} } @EnableAutoConfiguration @Component object SparkRunnable extends MainClass { override def run(): Unit = { val annotationContext = new AnnotationConfigApplicationContext(classOf[SparkRunnable]) val wiredClass = annotationContext.getBean(classOf[A]) } }
遇到的错误
初始运行错误
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'A': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'B' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
手动装配后的错误
尝试手动装配代码:
val uselessWiringVal = annotationContext.getBean(classOf[D]) val uselessWiringObj = new B(uselessWiringVal.foobar) val wiredClass = new A(uselessWiringObj)
出现错误:
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'A' defined in URL[SparkRunnable]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'D': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'configProperty' in value "${configProperty}"
核心疑问
- 组件类的配置是否存在问题?
- 如何在
SparkRunnable这个object类型的类中获取可用的Spring组件?
注:因Spark运行要求,SparkRunnable必须是object类型。尝试过object SparkRunnable (@Autowired A) extends MainClass {}这类语法但无效;可在SparkRunnable对应的类中实现Autowired注入,但无法传递给伴生对象。
解决方案
1. 组件类配置问题分析
你的组件类定义本身没问题,错误根源是你在SparkRunnable的run方法里新建了一个独立的AnnotationConfigApplicationContext——这个新容器和SpringBoot启动的主容器完全隔离,主容器中初始化的Bean在新容器里根本不存在,所以会出现找不到B的错误。
手动装配时读不到配置,也是因为新容器没有加载SpringBoot的配置环境,无法解析${configProperty}这类占位符。
2. 在SparkRunnable Object中获取组件的正确方式
Scala的object是语言层面的单例,Spring无法直接为它注入依赖,可通过以下两种方式解决:
方式一:复用Spring主容器上下文
在SpringBoot启动时,把主容器上下文保存到全局可访问的地方,SparkRunnable直接从这里获取Bean:
@SpringBootApplication class MainClass extends ApplicationRunner { override def run(args: ApplicationArguments): Unit = { // 启动Spring并保存上下文到全局持有类 val context = SpringApplication.run(classOf[MainClass], args.getSourceArgs: _*) SpringContextHolder.setContext(context) } } // 全局Spring上下文持有类,需要被Spring扫描到 @Component object SpringContextHolder extends ApplicationContextAware { private var context: ApplicationContext = _ override def setApplicationContext(applicationContext: ApplicationContext): Unit = { context = applicationContext } def getContext: ApplicationContext = context } @EnableAutoConfiguration @Component object SparkRunnable extends MainClass { override def run(): Unit = { // 直接从全局上下文获取Bean val wiredClass = SpringContextHolder.getContext.getBean(classOf[A]) // 后续业务逻辑 } }
方式二:将业务逻辑转移到Spring管理的组件中
把需要使用Spring组件的逻辑放到一个被Spring管理的类里,SparkRunnable仅作为入口,通过全局上下文调用这个组件:
// 被Spring管理的业务类,可直接注入依赖 @Component class SparkBusinessService @Autowired()(val a: A) { def execute(): Unit = { // 直接使用注入的A组件 println(a.componentB.beanC) } } @EnableAutoConfiguration @Component object SparkRunnable extends MainClass { override def run(): Unit = { val service = SpringContextHolder.getContext.getBean(classOf[SparkBusinessService]) service.execute() } }
关键注意事项
- 绝对不要在
SparkRunnable里创建新的Spring容器,必须复用SpringBoot启动的主容器。 - Scala的object无法被Spring直接注入,只能通过全局上下文间接获取Bean。
SpringContextHolder要放在Spring扫描的包下(比如主包或子包),确保Spring能识别并设置上下文。
内容的提问来源于stack exchange,提问作者Blu2718
相关产品推荐
相关产品推荐

