在@Configuration bean中引用SpEL表达式中的ConfigurationProperties Beans

时间:2015-07-19 11:39:17

标签: java spring spring-boot spring-el

我有这个属性类:

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类中启用配置属性,如下所示:

import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableConfigurationProperties(SomeProperties.class)
public class SomeAutoConfiguration {    
}

在同一个类(“SomeAutoConfiguration”)中,我想创建另一个bean,具体取决于SomeProperties中的list属性是否为空。我以为我可以使用@ConditionalExpression和以下SpEl:

@Bean
@ConditionalOnExpression("!(#someProperties.stuff?.isEmpty()?:true)")   
    public Object someBean(final SomeProperties someProperties) {
    return new Object();
}    

SpEl是正确的,但我无法控制包含我的属性的bean。使用上面的表达式我遇到了

  

EL1007E:(pos 43):无法找到属性或字段'stuff'   空

尝试通过其名称获取bean,如

@Bean
@ConditionalOnExpression("!(@'some.CONFIGURATION_PROPERTIES'.stuff?.isEmpty()?:true)")  
    public Object someBean(final SomeProperties someProperties) {
    return new Object();
} 

结束于

  

NoSuchBeanDefinitionException:没有定义名为'some.CONFIGURATION_PROPERTIES'的bean

有什么想法吗?我已经尝试在另一个类中启用ConfigurationProperties,但这也不起作用。

3 个答案:

答案 0 :(得分:2)

我认为您遇到的问题是@Conditions在解析@Configuration类时进行评估,因此不保证SomeProperties bean已定义。即使它被定义了,你可能也不希望它提前初始化,所以我建议采用不同的方法。

您可以尝试@ConditionalOnPropety,这是Spring Boot内部使用的注释,只要它有条件地想要根据属性启用自动配置。如果这不够灵活,您可以创建自己的Condition并直接访问Environment以判断属性值是否为空。如果要支持灵活绑定,可以使用RelaxedPropertyResolverHere's an example

答案 1 :(得分:0)

为了补充有关Spring Boot 2+的@PhilWebb答案,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();
        }
    }
}

免责声明:该代码用于逐步淘汰关于杰克逊对象映射器的一些不良做法,以便将某些代码过渡到spring boot way to configure an object mapper

答案 2 :(得分:0)

基于Brice's answer,我提出了一个抽象条件:

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);
        }
    }
}