Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce dedicated annotation to deal with boolean property conditions #43704

Closed
philwebb opened this issue Jan 6, 2025 · 10 comments
Closed
Assignees
Labels
type: enhancement A general enhancement
Milestone

Comments

@philwebb
Copy link
Member

philwebb commented Jan 6, 2025

As shown in #43641 we have inconsistencies with the way we use @ConditionalOnProperty for enabled annotations. Introducing a dedicated set of annotations will help remove the inconsistencies and allow us to identify more easily configurations which depend on .enabled properties.

@philwebb philwebb self-assigned this Jan 6, 2025
@philwebb philwebb added the type: enhancement A general enhancement label Jan 6, 2025
@philwebb philwebb added this to the 3.5.x milestone Jan 6, 2025
@bclozel bclozel added the status: pending-design-work Needs design work before any code can be developed label Jan 6, 2025
@philwebb philwebb changed the title Introduce dedicate annotations to deal with 'enabled' property conditions Introduce dedicated annotations to deal with 'enabled' property conditions Jan 6, 2025
@philwebb
Copy link
Member Author

philwebb commented Jan 6, 2025

I'd like the rest of the team to review https://github.com/philwebb/spring-boot/tree/gh-43704 before we merge.

@philwebb philwebb added the for: team-meeting An issue we'd like to discuss as a team to make progress label Jan 6, 2025
@quaff
Copy link
Contributor

quaff commented Jan 7, 2025

Nice work, one small suggestion, maybe the name of annotations should reflect that .enabled suffix are using, how about:

ConditionalOnNoOptOut -> ConditionalOnImplicitEnabled
ConditionalOnOptOut -> ConditionalOnNotEnabled or ConditionalOnExplicitNotEnabled
ConditionalOnOptIn -> ConditionalOnEnabled or ConditionalOnExplicitEnabled

@nosan
Copy link
Contributor

nosan commented Jan 7, 2025

Honestly, when I saw those annotations for the first time, I had no clue which one I should use and when until I read Javadoc.

My suggestion is to have two annotations:

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@ConditionalOnProperty(name = "enabled", havingValue = "true")
public @interface ConditionalOnEnabledProperty {
	@AliasFor(annotation = ConditionalOnProperty.class, attribute = "prefix")
	String value();
	@AliasFor(annotation = ConditionalOnProperty.class, attribute = "prefix")
	String prefix();
	@AliasFor(annotation = ConditionalOnProperty.class, attribute = "matchIfMissing")
	boolean matchIfMissing () default false;
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@ConditionalOnProperty(name = "enabled", havingValue = "false")
public @interface ConditionalOnNotEnabledProperty {
	@AliasFor(annotation = ConditionalOnProperty.class, attribute = "prefix")
	String value();
	@AliasFor(annotation = ConditionalOnProperty.class, attribute = "prefix")
	String prefix();
	@AliasFor(annotation = ConditionalOnProperty.class, attribute = "matchIfMissing")
	boolean matchIfMissing () default false;
}

In that case, it would be possible to cover all possible outcomes:

//spring.jmx.enabled property must be present and has 'true' value
@ConditionalOnEnabledProperty("spring.jmx")
static class JmxConfiguration{}

//spring.jmx.enabled property may be empty but if present must have 'true' value
@ConditionalOnEnabledProperty(prefix = "spring.jmx", matchIfMissing = true)
static class JmxConfiguration{}

//-------------------------------


//spring.jmx.enabled property must be present and has 'false' value
@ConditionalOnNotEnabledProperty("spring.jmx")
static class JmxConfiguration{}

//spring.jmx.enabled property may be empty but if present must have 'false' value
@ConditionalOnNotEnabledProperty(prefix = "spring.jmx", matchIfMissing = true)
static class JmxConfiguration{}

@wilkinsona
Copy link
Member

wilkinsona commented Jan 7, 2025

+1 for a pair of annotations, although I'd maybe use Disabled rather than NotEnabled:

  • @ConditionalOnEnabledProperty
  • @ConditionalOnDisabledProperty

Not 100% sure on this. Does ConditionalOnDisabledProperty suggest that the property's name would be .disabled more so than ConditionalOnNotEnabledProperty suggests that it would be .not-enabled?

I wonder if the Property suffix is needed? Perhaps it is to avoid confusion with the other @ConditionalOnEnabled… annotations.

I also wonder about renaming matchIfMissing. Something like enabledByDefault and disabledByDefault instead? Does that make the attribute's purpose more clear?

@nosan
Copy link
Contributor

nosan commented Jan 7, 2025

I was thinking about @ConditionalOnDisabledProperty instead of ConditionalOnNotEnabledProperty but for me, the first one sounds like the name of the property should be disabled rather than enabled with havingValue = false.

Perhaps it is to avoid confusion with the other @ConditionalOnEnabled… annotations.

Exactly

I also wonder about renaming matchIfMissing. Something like enabledByDefault and disabledByDefault instead? Does that make the attribute's purpose more clear?

For me, both enabledByDefault and matchIfMissing sounds good. Maybe matchIfMissing is preferable because is a well-known attribute for a long time.

@philwebb
Copy link
Member Author

philwebb commented Jan 7, 2025

Thanks for all the feedback!

My suggestion is to have two annotations:

I didn't think we could alias a matchIfMissing boolean attribute to a matchIfMissing String, but perhaps we can. If not, we could easily develop a new annotation and not use @AliasFor.

+1 for a pair of annotations, although I'd maybe use Disabled rather than NotEnabled:

I'm not so keen on Disabled because it makes me think there's a foo.disabled=true property rather than foo.enabled=false.


I'm starting to wonder if we should have a single dedicated annotation and always be explicit about what we want. It might be more verbose, but it will at least be clear. The annotations in my branch map to one of three options:

@ConditionalOnProperty(name = "enabled", matchIfMissing = true,  havingValue = "true")
@ConditionalOnProperty(name = "enabled", matchIfMissing = false, havingValue = "true")
@ConditionalOnProperty(name = "enabled", matchIfMissing = false, havingValue = "false")

I'm thinking perhaps an enum attribute value on a single annotation might work well:

@ConditionalOnEnabledProperty(prefix="spring.jmx", havingValue=Enabled.TRUE)
@ConditionalOnEnabledProperty(prefix="spring.jmx", havingValue=Enabled.TRUE_OR_MISSING)
@ConditionalOnEnabledProperty(prefix="spring.jmx", havingValue=Enabled.FALSE)

@nosan
Copy link
Contributor

nosan commented Jan 7, 2025

I am wondering if the following syntax could be used:

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
//custom condition (alias for `@ConditionalOnProperty.havingValue` does not work (type mismatch) )
@Conditional(OnEnabledPropertyCondition.class)
public @interface ConditionalOnEnabledProperty {
	String prefix();
	boolean matchIfMissing() default false;
	Enabled havingValue() default Enabled.TRUE;
	enum Enabled {
		FALSE, TRUE
	}
}

@ConditionalOnEnabledProperty(prefix = "management.tracing.baggage")
@ConditionalOnEnabledProperty(prefix = "server.servlet.encoding", matchIfMissing = true)
@ConditionalOnEnabledProperty(prefix = "management.tracing.baggage", havingValue = Enabled.FALSE)

@philwebb philwebb changed the title Introduce dedicated annotations to deal with 'enabled' property conditions Introduce dedicated annotation to deal with boolean property conditions Jan 7, 2025
@philwebb
Copy link
Member Author

philwebb commented Jan 7, 2025

Here's an updated proposal that uses a single ConditionalOnBooleanProperty annotation. https://github.com/philwebb/spring-boot/tree/gh-43704-2. It's no longer tied to .enabled which means we can use it in a few more places.

@nosan
Copy link
Contributor

nosan commented Jan 7, 2025

I like @ConditionalOnBooleanProperty as it gives a generic solution for any boolean
properties, but I wonder why the prefix attribute was not added.

With a prefix attribute it would be possible to extend @ConditionalOnBooleanProperty
with a dedicated annotation such as:

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@ConditionalOnBooleanProperty(name = "enabled")
public @interface ConditionalOnEnabledProperty {

	@AliasFor(annotation = ConditionalOnBooleanProperty.class)
	String prefix();

	@AliasFor(annotation = ConditionalOnBooleanProperty.class)
	boolean matchIfMissing() default false;

	@AliasFor(annotation = ConditionalOnBooleanProperty.class)
	boolean havingValue() default true;
}

@philwebb
Copy link
Member Author

philwebb commented Jan 7, 2025

I didn't add it because we've been moving away from using prefixes, but it's easy enough to put back. I'm not convinced we should have a @ConditionalOnEnabledProperty if we add @ConditionalOnBooleanProperty, it doesn't buy us very much.

@philwebb philwebb removed status: pending-design-work Needs design work before any code can be developed for: team-meeting An issue we'd like to discuss as a team to make progress labels Jan 8, 2025
@philwebb philwebb modified the milestones: 3.5.x, 3.5.0-M1 Jan 8, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

5 participants