diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnEnabledEndpoint.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnEnabledEndpoint.java index 52664b9d58..f55fb15443 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnEnabledEndpoint.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnEnabledEndpoint.java @@ -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"); * 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 org.springframework.boot.actuate.endpoint.annotation.Endpoint; +import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension; import org.springframework.context.annotation.Conditional; 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 * {@code management.endpoints.enabled-by-default} or failing that * {@link Endpoint#enableByDefault()}. + *

+ * When placed on a {@code @Bean} method, the endpoint defaults to the return type of the + * factory method: + * + *

+ * @Configuration
+ * public class MyConfiguration {
+ *
+ *     @ConditionalOnEnableEndpoint
+ *     @Bean
+ *     public MyEndpoint myEndpoint() {
+ *         ...
+ *     }
+ *
+ * }
+ *

+ * It is also possible to use the same mechanism for extensions: + * + *

+ * @Configuration
+ * public class MyConfiguration {
+ *
+ *     @ConditionalOnEnableEndpoint
+ *     @Bean
+ *     public MyEndpointWebExtension myEndpointWebExtension() {
+ *         ...
+ *     }
+ *
+ * }
+ *

+ * 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: + * + *

+ * @EndpointWebExtension(endpoint = MyEndpoint.class)
+ * public class MyEndpointWebExtension {
+ *
+ * }
+ *

+ * Alternatively, the target endpoint can be manually specified for components that should + * only be created when a given endpoint is enabled: + * + *

+ * @Configuration
+ * public class MyConfiguration {
+ *
+ *     @ConditionalOnEnableEndpoint(endpoint = MyEndpoint.class)
+ *     @Bean
+ *     public MyComponent myComponent() {
+ *         ...
+ *     }
+ *
+ * }
* * @author Stephane Nicoll * @since 2.0.0 @@ -42,4 +97,12 @@ import org.springframework.core.env.Environment; @Conditional(OnEnabledEndpointCondition.class) 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; + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/OnEnabledEndpointCondition.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/OnEnabledEndpointCondition.java index 6e2f93a6ed..ef25e29311 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/OnEnabledEndpointCondition.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/OnEnabledEndpointCondition.java @@ -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"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.boot.actuate.autoconfigure.endpoint.condition; +import java.util.Map; import java.util.Optional; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; @@ -92,16 +93,23 @@ class OnEnabledEndpointCondition extends SpringBootCondition { metadata instanceof MethodMetadata && metadata.isAnnotated(Bean.class.getName()), "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, - MethodMetadata metadata) { + private Class getEndpointType(ConditionContext context, MethodMetadata metadata) { + Map 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 try { - Class returnType = ClassUtils.forName(metadata.getReturnTypeName(), + return ClassUtils.forName(metadata.getReturnTypeName(), context.getClassLoader()); - return getEndpointAttributes(returnType); } catch (Throwable ex) { throw new IllegalStateException("Failed to extract endpoint id for " @@ -119,8 +127,8 @@ class OnEnabledEndpointCondition extends SpringBootCondition { attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(type, EndpointExtension.class, false, true); Assert.state(attributes != null, - "OnEnabledEndpointCondition may only be used on @Bean methods that " - + "return an @Endpoint or @EndpointExtension"); + "No endpoint is specified and the return type of the @Bean method is " + + "neither an @Endpoint, nor an @EndpointExtension"); return getEndpointAttributes(attributes.getClass("endpoint")); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnEnabledEndpointTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnEnabledEndpointTests.java index def3920af7..a83516afc2 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnEnabledEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnEnabledEndpointTests.java @@ -102,6 +102,44 @@ public class ConditionalOnEnabledEndpointTests { .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) 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"; + } + + } + }