Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Referring to ConfigurationProperties Beans in SpEL expression in @Configuration bean

I have this properties 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;
        }    
    }

and I enable configuration properties in an @Configuration class like so:

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

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

In the same class ("SomeAutoConfiguration") I want to create another bean depending if the list attribute in SomeProperties is empty or not. I thought i could use @ConditionalExpression with the following SpEl:

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

The SpEl is correct, but i don't manage to get a hold of the bean containing my properties. Using the above expression i run into

EL1007E:(pos 43): Property or field 'stuff' cannot be found on null

And trying to get the bean through its name like

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

ends up in

NoSuchBeanDefinitionException: No bean named 'some.CONFIGURATION_PROPERTIES' is defined

Any ideas? I already tried enabling ConfigurationProperties in another class but that didn't work either.

like image 258
Michael Simons Avatar asked Jul 19 '15 11:07

Michael Simons


2 Answers

I think the problem that you're facing is that @Conditions are evaluated when @Configuration classes are parsed, so there isn't any guarantee that the SomeProperties bean has been defined. Even if it were defined, you probably don't want it to be initialized early so I'd recommend a different approach.

You could try @ConditionalOnPropety, that's the annotation that is used internally by Spring Boot whenever it conditionally wants to enable auto-configuration based on a property. If that's not quite flexible enough you can create your own Condition and access the Environment directly to tell if the property value is empty. If you want to support flexible binding you can use RelaxedPropertyResolver. Here's an example.

like image 184
Phil Webb Avatar answered Oct 29 '22 13:10

Phil Webb


To complement @PhilWebb answer in regard of Spring Boot 2+, RelaxedPropertyResolver has been removed in favor of a more robust alternative named Binder. Here's a very simple example :

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

DISCLAIMER: This code is used to phase out some bad practice regarding jackson object mapper, in order to transition some code to the spring boot way to configure an object mapper.

like image 26
Brice Avatar answered Oct 29 '22 14:10

Brice