Как ссылаться на бин по типу в SpEL? - PullRequest
0 голосов
/ 16 января 2020

Ниже приведен минимальный пример, показывающий мою проблему.

Main.kt:

package com.mycompany.configurationpropertiestest

import org.slf4j.LoggerFactory
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.ConstructorBinding
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.boot.runApplication
import org.springframework.scheduling.annotation.EnableScheduling
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Service


@SpringBootApplication
@EnableScheduling
@EnableConfigurationProperties(FooServiceConfig::class)
class Application

fun main(args: Array<String>) {
    runApplication<Application>(*args)
}


@ConstructorBinding
@ConfigurationProperties("configurationpropertiestest.foo")
data class FooServiceConfig(
    val interval: Int = 1000,
    val message: String = "hi"
)


@Service
class FooService(
    private val myConfig: FooServiceConfig
) {
    private val log = LoggerFactory.getLogger(this.javaClass)
    //@Scheduled(fixedDelayString = "#{@FooServiceConfig.interval}")
    //@Scheduled(fixedDelayString = "#{@myConfig.interval}")
    @Scheduled(fixedDelayString = "\${configurationpropertiestest.foo.interval}")
    fun talk() {
        log.info(myConfig.message)
    }
}

(@ConstructorBinding используется для разрешения неизменности членов FooServiceConfig. )

application.yml:

configurationpropertiestest:
  foo:
    interval: 500
    message: "hi"

Test.kt:

package com.mycompany.configurationpropertiestest

import org.junit.Test
import org.junit.runner.RunWith
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.junit4.SpringRunner


@RunWith(SpringRunner::class)
@SpringBootTest
class Test {
    @Test
    fun `sit and wait`() {
        Thread.sleep(3000)
    }
}

Это работает, но работает только потому, что я ссылаюсь interval в @Scheduled аннотация примерно такая:

@Scheduled(fixedDelayString = "\${configurationpropertiestest.foo.interval}")

Это несколько нарушает красиво изолированную конфигурацию моего сервиса. Внезапно он должен знать о внешних вещах, о которых он должен теперь знать.

В идеале он должен был бы получить доступ к своей конфигурации либо по типу компонента:

@Scheduled(fixedDelayString = "#{@FooServiceConfig.interval}")

или с помощью внедренного экземпляра:

@Scheduled(fixedDelayString = "#{@myConfig.interval}")

Но эти попытки приводят к No bean named 'FooServiceConfig' available и No bean named 'myConfig' available соответственно.

Любая идея о том, как я могу получить доступ только к бину конфигурации, а не к значение глобальной конфигурации?

Ответы [ 2 ]

1 голос
/ 18 января 2020

Если вы не против сделать FooService.myConfig publi c, это должно сработать:

@Service
class FooService(val myConfig: FooServiceConfig) {

    val log = LoggerFactory.getLogger(this.javaClass)

    @Scheduled(fixedDelayString = "#{@fooService.myConfig.interval}")
    fun talk() {
        log.info(myConfig.message)
    }
}

ОБНОВЛЕНИЕ:

Видимо Spring изменяет имена бобов с аннотацией @ConstructorBinding к [configuration-properties-value]-[fully-qualified-bean-name]. FooServiceConfig заканчивается как configurationpropertiestest.foo-com.mycompany.configurationpropertiestest.FooServiceConfig

Так что, несмотря на то, что это довольно уродливо, это также должно работать:

@Service
class FooService(private val myConfig: FooServiceConfig) {

    val log = LoggerFactory.getLogger(this.javaClass)

    @Scheduled(fixedDelayString = "#{@'configurationpropertiestest.foo-com.mycompany.configurationpropertiestest.FooServiceConfig'.interval}")
    fun talk() {
        log.info(myConfig.message)
    }
}

Наконец, последний вариант, отвечающий на заглавный вопрос: Как ссылаться на бин по типу в SpEL? Вы можете сделать это, позвонив beanFactory.getBean:

@Service
class FooService(private val myConfig: FooServiceConfig) {

    val log = LoggerFactory.getLogger(this.javaClass)

    @Scheduled(fixedDelayString = "#{beanFactory.getBean(T(com.mycompany.configurationpropertiestest.FooServiceConfig)).interval}")
    fun talk() {
        log.info(myConfig.message)
    }
}
1 голос
/ 17 января 2020

Я немного изменил твой код и у меня это сработало. Главное изменение было введено FooServiceConfig с @Autowired. Тогда в планировщике я мог бы написать: "#{@fooServiceConfig.interval}"

@SpringBootApplication
@EnableScheduling
class Application

fun main(args: Array<String>) {
    SpringApplication.run(Application::class.java, *args)
}

@Configuration
@EnableConfigurationProperties
@ConfigurationProperties("configurationpropertiestest.foo")
data class FooServiceConfig(
        var interval: Int = 1000,
        var message: String = "hi"
)

@Service
class FooService {
    private val log = LoggerFactory.getLogger(this.javaClass)

    @Autowired
    lateinit var fooServiceConfig:FooServiceConfig

    @Scheduled(fixedDelayString = "#{@fooServiceConfig.interval}")
    fun talk() {
        log.info(fooServiceConfig.message)
    }
}

ОБНОВЛЕНИЕ

Если вам нужно @ConstructorBinding, вы можете получить доступ к его значениям другим способом. Введите другой класс конфигурации, который, например, извлекает значение интервала и представляет его как новый компонент. После этого вы можете обратиться к этому компоненту позже в @Scheduled

@Configuration
class DelayConfig{

    @Bean(name = ["talkInterval"])
    fun talkInterval(fooServiceConfig: FooServiceConfig): Int {
        return fooServiceConfig.interval
    }
}

@Service
class FooService(
        private val myConfig: FooServiceConfig
) {
    private val log = LoggerFactory.getLogger(this.javaClass)

    @Scheduled(fixedDelayString = "#{@talkInterval}")
    fun talk() {
        log.info(myConfig.message)
    }
}
...