在 @Configuration bean 的 SpEL 表达式中引用 ConfigurationProperties Beans
Referring to ConfigurationProperties Beans in SpEL expression in @Configuration bean
我有这个属性class:
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("some")
public class SomeProperties {
private List<String> stuff;
public List<String> getStuff() {
return stuff;
}
public void setStuff(List<String> stuff) {
this.stuff = stuff;
}
}
我在 @Configuration class 中启用配置属性,如下所示:
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(SomeProperties.class)
public class SomeAutoConfiguration {
}
在同一个 class ("SomeAutoConfiguration") 中,我想创建另一个 bean,具体取决于 SomeProperties 中的列表属性是否为空。我以为我可以将 @ConditionalExpression 与以下 SpEl 一起使用:
@Bean
@ConditionalOnExpression("!(#someProperties.stuff?.isEmpty()?:true)")
public Object someBean(final SomeProperties someProperties) {
return new Object();
}
SpEl 是正确的,但我没能获得包含我的属性的 bean。使用上面的表达式 i 运行 变成
EL1007E:(pos 43): Property or field 'stuff' cannot be found on
null
并尝试通过其名称获取 bean
@Bean
@ConditionalOnExpression("!(@'some.CONFIGURATION_PROPERTIES'.stuff?.isEmpty()?:true)")
public Object someBean(final SomeProperties someProperties) {
return new Object();
}
最终在
NoSuchBeanDefinitionException: No bean named 'some.CONFIGURATION_PROPERTIES' is defined
有什么想法吗?我已经尝试在另一个 class 中启用 ConfigurationProperties,但这也没有用。
我认为您面临的问题是 @Conditions
在 @Configuration
类 被解析时被评估,因此不能保证 SomeProperties
bean 已经定义。即使它已定义,您可能不希望它提前初始化,所以我建议采用不同的方法。
您可以尝试 @ConditionalOnPropety
,这是 Spring Boot 在有条件地想要启用基于 属性 的自动配置时内部使用的注释。如果这不够灵活,您可以创建自己的 Condition
并直接访问 Environment
以判断 属性 值是否为空。如果你想支持灵活绑定你可以使用RelaxedPropertyResolver
。 Here's an example.
为了补充@PhilWebb 关于 Spring Boot 2+ 的回答,RelaxedPropertyResolver
已被删除,取而代之的是更强大的替代方案 Binder
。这是一个非常简单的例子:
@Configuration
@AutoConfigureBefore(JacksonAutoConfiguration.class)
@ConditionalOnMissingBean(ObjectMapper.class)
@Conditional(SpringJacksonPropertiesMissing.class)
public class ObjectMapperConfiguration {
@Bean
public ObjectMapper objectMapper() {
return new ObjectMapper()
.disable(FAIL_ON_UNKNOWN_PROPERTIES)
.setSerializationInclusion(JsonInclude.Include.NON_NULL);
}
static class SpringJacksonPropertiesMissing extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
return new ConditionOutcome(hasJacksonPropertiesDefined(context),
"Spring Jackson property based configuration missing");
}
private boolean hasJackonPropertiesDefined(ConditionContext context) {
return Binder.get(context.getEnvironment())
.bind(ConfigurationPropertyName.of("spring.jackson"),
Bindable.of(Map.class))
.orElse(Collections.emptyMap())
.isEmpty();
}
}
}
免责声明:此代码用于逐步淘汰有关 jackson 对象映射器的一些不良做法,以便将某些代码转换为 spring boot way to configure an object mapper。
基于我做了一个抽象条件:
public abstract class ConfigurationPropertyCondition<T> extends SpringBootCondition {
private final Class<T> propertiesClass;
private final Supplier<T> constructor;
private final String prefix;
private final Predicate<T> predicate;
public ConfigurationPropertyCondition(
Class<T> propertiesClass, Supplier<T> constructor, String prefix, Predicate<T> predicate) {
this.propertiesClass = propertiesClass;
this.constructor = constructor;
this.prefix = prefix;
this.predicate = predicate;
}
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
return new ConditionOutcome(predicate.test(properties(context)), (String) null);
}
private T properties(ConditionContext context) {
return Binder.get(context.getEnvironment())
.bind(ConfigurationPropertyName.of(prefix), Bindable.of(propertiesClass))
.orElseGet(constructor);
}
}
现在我可以做类似的事情:
...
@ConfigurationProperties(prefix = MY_FEATURE_SETTINGS)
@Bean
public MyFeatureProperties myFeatureProperties() {
return new MyFeatureProperties();
}
...
@Conditional(MyFeatureConfiguration.MyFeatureEnabled.class)
@Configuration
public class MyFeatureConfiguration {
...
static class MyFeatureEnabled extends ConfigurationPropertyCondition<MyFeatureProperties> {
public MyFeatureEnabled() {
super(MyFeatureProperties.class, MyFeatureProperties::new, MY_FEATURE_SETTINGS, MyFeatureProperties::isEnabled);
}
}
}
我有这个属性class:
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("some")
public class SomeProperties {
private List<String> stuff;
public List<String> getStuff() {
return stuff;
}
public void setStuff(List<String> stuff) {
this.stuff = stuff;
}
}
我在 @Configuration class 中启用配置属性,如下所示:
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(SomeProperties.class)
public class SomeAutoConfiguration {
}
在同一个 class ("SomeAutoConfiguration") 中,我想创建另一个 bean,具体取决于 SomeProperties 中的列表属性是否为空。我以为我可以将 @ConditionalExpression 与以下 SpEl 一起使用:
@Bean
@ConditionalOnExpression("!(#someProperties.stuff?.isEmpty()?:true)")
public Object someBean(final SomeProperties someProperties) {
return new Object();
}
SpEl 是正确的,但我没能获得包含我的属性的 bean。使用上面的表达式 i 运行 变成
EL1007E:(pos 43): Property or field 'stuff' cannot be found on null
并尝试通过其名称获取 bean
@Bean
@ConditionalOnExpression("!(@'some.CONFIGURATION_PROPERTIES'.stuff?.isEmpty()?:true)")
public Object someBean(final SomeProperties someProperties) {
return new Object();
}
最终在
NoSuchBeanDefinitionException: No bean named 'some.CONFIGURATION_PROPERTIES' is defined
有什么想法吗?我已经尝试在另一个 class 中启用 ConfigurationProperties,但这也没有用。
我认为您面临的问题是 @Conditions
在 @Configuration
类 被解析时被评估,因此不能保证 SomeProperties
bean 已经定义。即使它已定义,您可能不希望它提前初始化,所以我建议采用不同的方法。
您可以尝试 @ConditionalOnPropety
,这是 Spring Boot 在有条件地想要启用基于 属性 的自动配置时内部使用的注释。如果这不够灵活,您可以创建自己的 Condition
并直接访问 Environment
以判断 属性 值是否为空。如果你想支持灵活绑定你可以使用RelaxedPropertyResolver
。 Here's an example.
为了补充@PhilWebb 关于 Spring Boot 2+ 的回答,RelaxedPropertyResolver
已被删除,取而代之的是更强大的替代方案 Binder
。这是一个非常简单的例子:
@Configuration
@AutoConfigureBefore(JacksonAutoConfiguration.class)
@ConditionalOnMissingBean(ObjectMapper.class)
@Conditional(SpringJacksonPropertiesMissing.class)
public class ObjectMapperConfiguration {
@Bean
public ObjectMapper objectMapper() {
return new ObjectMapper()
.disable(FAIL_ON_UNKNOWN_PROPERTIES)
.setSerializationInclusion(JsonInclude.Include.NON_NULL);
}
static class SpringJacksonPropertiesMissing extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
return new ConditionOutcome(hasJacksonPropertiesDefined(context),
"Spring Jackson property based configuration missing");
}
private boolean hasJackonPropertiesDefined(ConditionContext context) {
return Binder.get(context.getEnvironment())
.bind(ConfigurationPropertyName.of("spring.jackson"),
Bindable.of(Map.class))
.orElse(Collections.emptyMap())
.isEmpty();
}
}
}
免责声明:此代码用于逐步淘汰有关 jackson 对象映射器的一些不良做法,以便将某些代码转换为 spring boot way to configure an object mapper。
基于
public abstract class ConfigurationPropertyCondition<T> extends SpringBootCondition {
private final Class<T> propertiesClass;
private final Supplier<T> constructor;
private final String prefix;
private final Predicate<T> predicate;
public ConfigurationPropertyCondition(
Class<T> propertiesClass, Supplier<T> constructor, String prefix, Predicate<T> predicate) {
this.propertiesClass = propertiesClass;
this.constructor = constructor;
this.prefix = prefix;
this.predicate = predicate;
}
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
return new ConditionOutcome(predicate.test(properties(context)), (String) null);
}
private T properties(ConditionContext context) {
return Binder.get(context.getEnvironment())
.bind(ConfigurationPropertyName.of(prefix), Bindable.of(propertiesClass))
.orElseGet(constructor);
}
}
现在我可以做类似的事情:
...
@ConfigurationProperties(prefix = MY_FEATURE_SETTINGS)
@Bean
public MyFeatureProperties myFeatureProperties() {
return new MyFeatureProperties();
}
...
@Conditional(MyFeatureConfiguration.MyFeatureEnabled.class)
@Configuration
public class MyFeatureConfiguration {
...
static class MyFeatureEnabled extends ConfigurationPropertyCondition<MyFeatureProperties> {
public MyFeatureEnabled() {
super(MyFeatureProperties.class, MyFeatureProperties::new, MY_FEATURE_SETTINGS, MyFeatureProperties::isEnabled);
}
}
}