Вопрос или проблема
У меня есть код на Scala, который выглядит примерно так
@Component
class A (@Autowired val componentB: B) {Что-то}
@Component
class B (@Autowired val beanC: C) {Что-то}
@Component
class D () {
@Value("${configProperty}")
val configProperty = ""
@Bean
def foobar: C = {Что-то}
}
@SpringBootApplication
class MainClass extends ApplicationRunner {
def main(): Unit = {
SpringApplication.run(classOf[MainClass])
}
override def run(): Unit = {Ничего не делать}
}
@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: Ошибка создания бина с именем 'A': Неудовлетворенная зависимость, выраженная через параметр конструктора 0; вложенное исключение - org.springframework.beans.factory.NoSuchBeanDefinitionException: Нет подходящего бина типа 'B': ожидался хотя бы 1 бин, который соответствует кандидату на автодополнение. Аннотации зависимости: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
Мне нужно, чтобы SparkRunnable был объектом, потому что он должен выполняться в Spark. Я вижу, что все бины и классы компонентов устанавливаются правильно из отладки и журналов, но я просто не могу получить бины таким образом, чтобы использовать их внутри объекта SparkRunnable.
Я попытался подключить модули вручную, как ниже, но у него даже нет контекста для получения свойств, сконфигурированных в yaml файлах.
val uselessWiringVal = annotationContext.getBean(classOf[D])
val uselessWiringObj = new B(uselessWiringVal.foobar)
val wiredClass = new A(uselessWiringObj)
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Ошибка создания бина с именем 'A', определенного по URL[SparkRunnable]: Неудовлетворенная зависимость, выраженная через параметр конструктора 0; вложенное исключение - org.springframework.beans.factory.BeanCreationException: Ошибка создания бина с именем 'D': Инъекция зависимостей, отмеченных как автозаполняемые, не удалась; вложенное исключение - java.lang.IllegalArgumentException: Не удалось разрешить плейсхолдер 'configProperty' в значении "${configProperty}"
Я начинающий в Scala и, вероятно, сделал ошибку, пытаясь использовать SpringBoot для этой работы. Мои вопросы:
- Есть ли что-то неправильное в том, как я настраиваю свои классы компонентов?
- Как мне фактически получить компоненты для использования из моего RunnableObject?
Было бы идеально сделать что-то вроде
object SparkRunnable (@Autowired A) extends MainClass {}
но такой синтаксис не работает.
Я могу получить компонент с автозаполнением в классе SparkRunnable, но не могу найти способ передать его в объект-компаньон.
Ответ или решение
Чтобы использовать Spring Beans внутри объекта Scala, необходимо учитывать некоторые особенности взаимодействия между Spring и Scala. Эта задача может показаться сложной для начинающих, однако с правильным подходом вы сможете успешно интегрировать Spring в ваше приложение на Scala. Рассмотрим ваши вопросы подробно.
Ответ на вопросы
1. Есть ли ошибки в настройке компонентных классов?
Ваши компонентные классы имеют правильный синтаксис для использования аннотаций Spring (например, @Component
, @Autowired
, @Value
). Однако вы сталкиваетесь с двумя основными проблемами:
-
Недостаточная конфигурация контекста: Ваше приложение может не находить компоненты из-за неправильной конфигурации контекста. Убедитесь, что все необходимые компоненты правильно сканируются и создаются в контексте Spring.
-
Неинициализированные свойства: Ошибка
Could not resolve placeholder 'configProperty'
указывает на то, что свойствоconfigProperty
не было найдено в ваших конфигурационных файлах, таких какapplication.yml
илиapplication.properties
. Убедитесь, что данный файл правильно загружается вашим приложением.
2. Как получить компоненты для использования в объекте SparkRunnable?
В Scala нет прямой возможности инъекции зависимостей в объекты (companion objects) таким же образом, как это делается в классах. Однако вы можете использовать несколько обходных путей:
Использование @Component
для класса и объектного контекста
Вместо расположения логики внутри объекта SparkRunnable
, используйте класс, чтобы иметь возможность полагается на механизм инъекции Spring.
Пример:
@Component
class SparkRunnable(@Autowired val a: A) {
def runJob(): Unit = {
// Здесь вы можете использовать 'a' для работы с вашим приложением
}
}
Теперь вы можете создать этот класс через Spring контекст, а затем выполнять нужный метод runJob
.
Изменение вашей основной конфигурации
Вот как может выглядеть ваша основная конфигурация, чтобы всё заработало правильно:
@SpringBootApplication
class MainClass extends ApplicationRunner {
override def run(args: String*): Unit = {
// Здесь можно добавить логику, если необходимо
}
}
object Application {
def main(args: Array[String]): Unit = {
val ctx = SpringApplication.run(classOf[MainClass])
val sparkRunnable = ctx.getBean(classOf[SparkRunnable])
sparkRunnable.runJob()
}
}
Таким образом, в MainClass
, вы инициализируете контекст и можете получить все необходимые бины и запустить нужное вам приложение.
Заключение
Ваши компоненты в целом настроены правильно, но из-за специфик Scala и внедрения зависимостей с помощью Spring, лучше всего использовать классы вместо объектов для инъекции. Убедитесь, что все конфигурационные параметры корректно загружены, и пробуйте использовать @Component
для классов, чтобы получить доступ к необходимым бинам через контекст. Возможно, потребуется внести изменения в способ загрузки конфигурации для устранения ошибки configProperty
.
С помощью правильного подхода вы сможете успешно интегрировать Spring и Scala и создать высококачественное приложение.