spring-projects / spring-boot

Spring Boot

Home Page:https://spring.io/projects/spring-boot

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Need the ability to conditional add configuration when a property is missing.

tkvangorder opened this issue · comments

I have a use case where I only want to configure beans if and ONLY if a given property is NOT present within the environment.

I was using @ConditionalOnProperty and setting matchOnMissing = true. And this condition evaluates to "true" when the property is missing OR the property is present and has any value other than "false".

I either need a new annotation @ConditionalOnMissingProperty or a new configuration option added to @ConditionalOnProperty that allows "only match if not present"

I was able to work around this issue with @ConditonalOnExpression but this ended up being quite tricky because the property I was testing for was an MSSQL server jdbc url....which had "" in it...

The parser was replacing my variable with the jdbc URL and then re-evaluating the expression which ended up with a syntax error on the backslash. The solution was to enclose my environment property in single quotes to get around this parsing error but then I am evaluating string literals. This ended up being a lot more complicated than it should have been.

so if the property is absent, I end up with a string literal "true"....ugh.

    @Configuration
    @ConditionalOnExpression("'${datasource.reporter.url:true}' == 'true'")
    protected static class AliasReporterDataSourceConfig {

If you have a preference on an approach I could take a stab at a pull request for this.

I'm not sure that we want to add another attribute to @ConditionalOnProperty. @ConditionalOnMissing property would be a better option, IMO. That said, this is the first time (as far as I can tell) that we've seen this request so I'm not sure how common a need it is.

Have you considered writing your own Conditional or using @NoneNestedConditions to negate @ConditionalOnProperty?

We actually had that request several times (can't find the references right now but I remember that @dsyer was part of the conversation). W always said that that relying on the fact a property was not set was asking for troubles. I really think that you should have a property for this and a clear signal whether this is enabled or disabled by default.

If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.

Our use case:

We have two data sources: A primary data source that is used by most of the application for transactional workflows and reporter data source that is used to run reports on a slightly older version of the "Real" database. The reporter works on a replicated copy of the primary data source and allows more intensive reports to be run without impacting the transactional work in the primrary data source. This replication is only done in our larger environments and in cases where there is only one active database, we want the reporter data source to just be an alias to the primary data source.

So the configuration of the reporter data source is based on if a reporter database url is present in the application configuration. If it is, we create a new separate data source, if it is not we create a single data source and provide two aliases (primary and reporter).

I agree with Andy, that having a new @ConditionalOnMissingProperty is more clear then mangling the @ConditionalOnProperty. We did come up with a work-around using @ConditionalOnExpressionand I opened this issue more as a "Ease of Use" enhancement.

The thing is you don't actually reply to And'y's questions

Have you considered writing your own Conditional or using @NoneNestedConditions to negate @ConditionalOnProperty?

I wouldn't provide a first class support for such use case. Relying on the fact something is not set can be hard to track since there are so many places where you can set a value. As I said already, this has been asked before and got rejected. Maybe @dsyer or @philwebb think differently?

I think writing my own Conditional would have been more elegant then using the Expression, however,since I had found a workaround I had stopped exploring the issue. I just found it surprising that there was nothing analogous to @ConditionalOnMissingBean, @ConditionalOnMissingBinding, @ConditionalOnMissingClass, etc. This certainly isn't a high priority item, I just thought it would be a nice usability enhancement.

I understand our situation is an edge case, but it equates to performance gains for us because if we can alias the reporter data source when it has NOT been defined in the environment. We don't have to initialize a second instance of SqlSessionFactory (Mybatis) to the same underlying database as the primary data source.

Since detecting missing properties can be problematic, and we've not got a direct need for this ourselves, I think your own custom condition is the best approach. Thanks anyway for the suggestion.

commented

I need this, too, and tried to build a very basic version here. Any comments are welcome.

Though the issue is closed, but I landed up to this thread in pursuit of my requirement. I have to switch the database backend DynamoDB and MySQl using a property say database=dynamo/mysql. If it's not mysql, I require to exclude DataSourceAutoConfiguration.

@anandbikas

If I am understanding your use case:

Exclude DataSourceAutoConfiguration, you can use the property spring.autoconfigure.exclude to do that.

Then you can create a conditional Configuration:

@Configuration
public static class MyDatabaseConfiguration {

	@Configuration
	@ConditionalOnProperty(name = "database", havingValue="mysql")
	@Import(DataSourceAutoConfiguration.class)
	protected static class MySqlConfig {
.
. The import of the DataSourceAutoConfiguration will only happen when using mysql. 
. Any additional beans required for use with mssql
.
	}

	@Configuration
	@ConditionalOnProperty(name = "database", havingValue="dynamo")
	protected static class SeparateReporterDataSourceConfig {
.
. Any additional beans required for use with dynamo
.

	}

Thanks @tkvangorder for a quick reply!

Actually I started from here itself. After excluding it using property, the @import(DataSourceAutoConfiguration.class) is not working. I get error
required a bean named 'entityManagerFactory' that could not be found
.
Then I thought to do it in an opposite way and ended up to this thread discussion.

@tkvangorder ! Here is an update.
After lots of struggle I found that my DBConfig class with @import(DataSourceAutoConfiguration ) was lying in a different rooted package than the SpringBootApplication.

Though DBConfig is working fine from a different rooted package, but exclusion and inclusion of boot auto configurations must be in the same hierarchy of package structure.

This seems weird to me. There may be a reason behind this. Will follow up with Spring team.

Thanks again !

@anandbikas I have the exact same requirement and I am still not able to fix that issue. Is it possible to share your code snippet?

@KetkiThosar I actually achieved that by excluding the DataSourceAutoConfiguration.class from the main application and imported my custom conditional DataSourceAutoConfig which in turn was importing DataSourceAutoConfiguration.class

Refer:
https://github.com/anandbikas/springproject/blob/master/springproject-library/src/main/java/com/anand/springproject/library/sql/DataSourceAutoConfig.java

https://github.com/anandbikas/springproject/blob/master/springproject-service/src/main/java/com/anand/springproject/service/SpringprojectServiceApplication.java

This is an old thread, but maybe this can be useful for other people. We had a similar issue and the solution adopted by my team was creating a custom condition:

    public class DataSourceReportMissingCondition implements Condition {
        @Override
        public boolean matches(final ConditionContext context, final AnnotatedTypeMetadata metadata) {
            return !context.getEnvironment().containsProperty("MY_PROPERTY_NAME");
        }
    }