Allow @ConditionalOnEnabledEndpoint to be used on any component

Closes gh-14787
pull/14774/merge
Andy Wilkinson 6 years ago
parent 21ebb94d49
commit 861587ec78

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2017 the original author or authors. * Copyright 2012-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -23,6 +23,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension;
import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Conditional;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
@ -31,6 +32,60 @@ import org.springframework.core.env.Environment;
* according to the endpoints specific {@link Environment} property, falling back to * according to the endpoints specific {@link Environment} property, falling back to
* {@code management.endpoints.enabled-by-default} or failing that * {@code management.endpoints.enabled-by-default} or failing that
* {@link Endpoint#enableByDefault()}. * {@link Endpoint#enableByDefault()}.
* <p>
* When placed on a {@code @Bean} method, the endpoint defaults to the return type of the
* factory method:
*
* <pre class="code">
* &#064;Configuration
* public class MyConfiguration {
*
* &#064;ConditionalOnEnableEndpoint
* &#064;Bean
* public MyEndpoint myEndpoint() {
* ...
* }
*
* }</pre>
* <p>
* It is also possible to use the same mechanism for extensions:
*
* <pre class="code">
* &#064;Configuration
* public class MyConfiguration {
*
* &#064;ConditionalOnEnableEndpoint
* &#064;Bean
* public MyEndpointWebExtension myEndpointWebExtension() {
* ...
* }
*
* }</pre>
* <p>
* In the sample above, {@code MyEndpointWebExtension} will be created if the endpoint is
* enabled as defined by the rules above. {@code MyEndpointWebExtension} must be a regular
* extension that refers to an endpoint, something like:
*
* <pre class="code">
* &#064;EndpointWebExtension(endpoint = MyEndpoint.class)
* public class MyEndpointWebExtension {
*
* }</pre>
* <p>
* Alternatively, the target endpoint can be manually specified for components that should
* only be created when a given endpoint is enabled:
*
* <pre class="code">
* &#064;Configuration
* public class MyConfiguration {
*
* &#064;ConditionalOnEnableEndpoint(endpoint = MyEndpoint.class)
* &#064;Bean
* public MyComponent myComponent() {
* ...
* }
*
* }</pre>
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @since 2.0.0 * @since 2.0.0
@ -42,4 +97,12 @@ import org.springframework.core.env.Environment;
@Conditional(OnEnabledEndpointCondition.class) @Conditional(OnEnabledEndpointCondition.class)
public @interface ConditionalOnEnabledEndpoint { public @interface ConditionalOnEnabledEndpoint {
/**
* The endpoint type that should be checked. Inferred when the return type of the
* {@code @Bean} method is either an {@link Endpoint} or an {@link EndpointExtension}.
* @return the endpoint type to check
* @since 2.0.6
*/
Class<?> endpoint() default Void.class;
} }

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2017 the original author or authors. * Copyright 2012-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,6 +16,7 @@
package org.springframework.boot.actuate.autoconfigure.endpoint.condition; package org.springframework.boot.actuate.autoconfigure.endpoint.condition;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
@ -92,16 +93,23 @@ class OnEnabledEndpointCondition extends SpringBootCondition {
metadata instanceof MethodMetadata metadata instanceof MethodMetadata
&& metadata.isAnnotated(Bean.class.getName()), && metadata.isAnnotated(Bean.class.getName()),
"OnEnabledEndpointCondition may only be used on @Bean methods"); "OnEnabledEndpointCondition may only be used on @Bean methods");
return getEndpointAttributes(context, (MethodMetadata) metadata); Class<?> endpointType = getEndpointType(context, (MethodMetadata) metadata);
return getEndpointAttributes(endpointType);
} }
private AnnotationAttributes getEndpointAttributes(ConditionContext context, private Class<?> getEndpointType(ConditionContext context, MethodMetadata metadata) {
MethodMetadata metadata) { Map<String, Object> attributes = metadata
.getAnnotationAttributes(ConditionalOnEnabledEndpoint.class.getName());
if (attributes != null && attributes.containsKey("endpoint")) {
Class<?> target = (Class<?>) attributes.get("endpoint");
if (target != Void.class) {
return target;
}
}
// We should be safe to load at this point since we are in the REGISTER_BEAN phase // We should be safe to load at this point since we are in the REGISTER_BEAN phase
try { try {
Class<?> returnType = ClassUtils.forName(metadata.getReturnTypeName(), return ClassUtils.forName(metadata.getReturnTypeName(),
context.getClassLoader()); context.getClassLoader());
return getEndpointAttributes(returnType);
} }
catch (Throwable ex) { catch (Throwable ex) {
throw new IllegalStateException("Failed to extract endpoint id for " throw new IllegalStateException("Failed to extract endpoint id for "
@ -119,8 +127,8 @@ class OnEnabledEndpointCondition extends SpringBootCondition {
attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(type, attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(type,
EndpointExtension.class, false, true); EndpointExtension.class, false, true);
Assert.state(attributes != null, Assert.state(attributes != null,
"OnEnabledEndpointCondition may only be used on @Bean methods that " "No endpoint is specified and the return type of the @Bean method is "
+ "return an @Endpoint or @EndpointExtension"); + "neither an @Endpoint, nor an @EndpointExtension");
return getEndpointAttributes(attributes.getClass("endpoint")); return getEndpointAttributes(attributes.getClass("endpoint"));
} }

@ -102,6 +102,44 @@ public class ConditionalOnEnabledEndpointTests {
.doesNotHaveBean("fooExt")); .doesNotHaveBean("fooExt"));
} }
@Test
public void outcomeWithReferenceWhenNoPropertiesShouldMatch() {
this.contextRunner
.withUserConfiguration(FooEndpointEnabledByDefaultTrue.class,
ComponentEnabledIfEndpointIsEnabledConfiguration.class)
.run((context) -> assertThat(context).hasBean("fooComponent"));
}
@Test
public void outcomeWithReferenceWhenEndpointEnabledPropertyIsTrueShouldMatch() {
this.contextRunner.withPropertyValues("management.endpoint.foo.enabled=true")
.withUserConfiguration(FooEndpointEnabledByDefaultTrue.class,
ComponentEnabledIfEndpointIsEnabledConfiguration.class)
.run((context) -> assertThat(context).hasBean("fooComponent"));
}
@Test
public void outcomeWithReferenceWhenEndpointEnabledPropertyIsFalseShouldNotMatch() {
this.contextRunner.withPropertyValues("management.endpoint.foo.enabled=false")
.withUserConfiguration(FooEndpointEnabledByDefaultTrue.class,
ComponentEnabledIfEndpointIsEnabledConfiguration.class)
.run((context) -> assertThat(context).doesNotHaveBean("fooComponent"));
}
@Test
public void outcomeWithNoReferenceShouldFail() {
this.contextRunner
.withUserConfiguration(
ComponentWithNoEndpointReferenceConfiguration.class)
.run((context) -> {
assertThat(context).hasFailed();
assertThat(context.getStartupFailure().getCause().getMessage())
.contains(
"No endpoint is specified and the return type of the @Bean method "
+ "is neither an @Endpoint, nor an @EndpointExtension");
});
}
@Endpoint(id = "foo", enableByDefault = true) @Endpoint(id = "foo", enableByDefault = true)
static class FooEndpointEnabledByDefaultTrue { static class FooEndpointEnabledByDefaultTrue {
@ -187,4 +225,26 @@ public class ConditionalOnEnabledEndpointTests {
} }
@Configuration
static class ComponentEnabledIfEndpointIsEnabledConfiguration {
@Bean
@ConditionalOnEnabledEndpoint(endpoint = FooEndpointEnabledByDefaultTrue.class)
public String fooComponent() {
return "foo";
}
}
@Configuration
static class ComponentWithNoEndpointReferenceConfiguration {
@Bean
@ConditionalOnEnabledEndpoint
public String fooComponent() {
return "foo";
}
}
} }

Loading…
Cancel
Save