如何在 SpEL 中按类型引用 bean?
How to reference a bean by type in a SpEL?
下面是一个最小的例子,展示了我的问题。
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)
}
}
它有效,但它只是有效,因为我在 @Scheduled
注释中引用了 interval
,如下所示:
@Scheduled(fixedDelayString = "${configurationpropertiestest.foo.interval}")
这在某种程度上破坏了我服务的良好隔离配置。它突然需要了解外部事物,而现在它应该需要了解这些事物。
理想情况下,它只会通过 bean 的类型访问其配置:
@Scheduled(fixedDelayString = "#{@FooServiceConfig.interval}")
或通过注入的实例:
@Scheduled(fixedDelayString = "#{@myConfig.interval}")
但是这些尝试分别导致 No bean named 'FooServiceConfig' available
和 No bean named 'myConfig' available
。
知道如何实现只访问配置 bean 而不是全局配置值吗?
我稍微更改了您的代码,对我来说它可以正常工作。主要变化是用 @Autowired
注入 FooServiceConfig
。然后在调度程序中我可以写:"#{@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
,您可以通过其他方式访问它的值。
引入其他配置 class,例如提取间隔值并将其作为新 bean 公开。之后你可以在后面的@Scheduled
中引用这个bean
@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)
}
}
如果您不介意制作 FooService.myConfig
public,这应该可行:
@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
注释注释的 bean 的名称更改为 [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)
}
}
最后一个选项,回答标题问题:How to reference a bean by type in a 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)
}
}
下面是一个最小的例子,展示了我的问题。
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)
}
}
它有效,但它只是有效,因为我在 @Scheduled
注释中引用了 interval
,如下所示:
@Scheduled(fixedDelayString = "${configurationpropertiestest.foo.interval}")
这在某种程度上破坏了我服务的良好隔离配置。它突然需要了解外部事物,而现在它应该需要了解这些事物。
理想情况下,它只会通过 bean 的类型访问其配置:
@Scheduled(fixedDelayString = "#{@FooServiceConfig.interval}")
或通过注入的实例:
@Scheduled(fixedDelayString = "#{@myConfig.interval}")
但是这些尝试分别导致 No bean named 'FooServiceConfig' available
和 No bean named 'myConfig' available
。
知道如何实现只访问配置 bean 而不是全局配置值吗?
我稍微更改了您的代码,对我来说它可以正常工作。主要变化是用 @Autowired
注入 FooServiceConfig
。然后在调度程序中我可以写:"#{@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
,您可以通过其他方式访问它的值。
引入其他配置 class,例如提取间隔值并将其作为新 bean 公开。之后你可以在后面的@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)
}
}
如果您不介意制作 FooService.myConfig
public,这应该可行:
@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
注释注释的 bean 的名称更改为 [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)
}
}
最后一个选项,回答标题问题:How to reference a bean by type in a 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)
}
}