Skip to content

Commit 861587e

Browse files
committed
Allow @ConditionalOnEnabledEndpoint to be used on any component
Closes gh-14787
1 parent 21ebb94 commit 861587e

File tree

3 files changed

+140
-9
lines changed

3 files changed

+140
-9
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnEnabledEndpoint.java

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2017 the original author or authors.
2+
* Copyright 2012-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@
2323
import java.lang.annotation.Target;
2424

2525
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
26+
import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension;
2627
import org.springframework.context.annotation.Conditional;
2728
import org.springframework.core.env.Environment;
2829

@@ -31,6 +32,60 @@
3132
* according to the endpoints specific {@link Environment} property, falling back to
3233
* {@code management.endpoints.enabled-by-default} or failing that
3334
* {@link Endpoint#enableByDefault()}.
35+
* <p>
36+
* When placed on a {@code @Bean} method, the endpoint defaults to the return type of the
37+
* factory method:
38+
*
39+
* <pre class="code">
40+
* &#064;Configuration
41+
* public class MyConfiguration {
42+
*
43+
* &#064;ConditionalOnEnableEndpoint
44+
* &#064;Bean
45+
* public MyEndpoint myEndpoint() {
46+
* ...
47+
* }
48+
*
49+
* }</pre>
50+
* <p>
51+
* It is also possible to use the same mechanism for extensions:
52+
*
53+
* <pre class="code">
54+
* &#064;Configuration
55+
* public class MyConfiguration {
56+
*
57+
* &#064;ConditionalOnEnableEndpoint
58+
* &#064;Bean
59+
* public MyEndpointWebExtension myEndpointWebExtension() {
60+
* ...
61+
* }
62+
*
63+
* }</pre>
64+
* <p>
65+
* In the sample above, {@code MyEndpointWebExtension} will be created if the endpoint is
66+
* enabled as defined by the rules above. {@code MyEndpointWebExtension} must be a regular
67+
* extension that refers to an endpoint, something like:
68+
*
69+
* <pre class="code">
70+
* &#064;EndpointWebExtension(endpoint = MyEndpoint.class)
71+
* public class MyEndpointWebExtension {
72+
*
73+
* }</pre>
74+
* <p>
75+
* Alternatively, the target endpoint can be manually specified for components that should
76+
* only be created when a given endpoint is enabled:
77+
*
78+
* <pre class="code">
79+
* &#064;Configuration
80+
* public class MyConfiguration {
81+
*
82+
* &#064;ConditionalOnEnableEndpoint(endpoint = MyEndpoint.class)
83+
* &#064;Bean
84+
* public MyComponent myComponent() {
85+
* ...
86+
* }
87+
*
88+
* }</pre>
3489
*
3590
* @author Stephane Nicoll
3691
* @since 2.0.0
@@ -42,4 +97,12 @@
4297
@Conditional(OnEnabledEndpointCondition.class)
4398
public @interface ConditionalOnEnabledEndpoint {
4499

100+
/**
101+
* The endpoint type that should be checked. Inferred when the return type of the
102+
* {@code @Bean} method is either an {@link Endpoint} or an {@link EndpointExtension}.
103+
* @return the endpoint type to check
104+
* @since 2.0.6
105+
*/
106+
Class<?> endpoint() default Void.class;
107+
45108
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/OnEnabledEndpointCondition.java

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2017 the original author or authors.
2+
* Copyright 2012-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure.endpoint.condition;
1818

19+
import java.util.Map;
1920
import java.util.Optional;
2021

2122
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
@@ -92,16 +93,23 @@ private AnnotationAttributes getEndpointAttributes(ConditionContext context,
9293
metadata instanceof MethodMetadata
9394
&& metadata.isAnnotated(Bean.class.getName()),
9495
"OnEnabledEndpointCondition may only be used on @Bean methods");
95-
return getEndpointAttributes(context, (MethodMetadata) metadata);
96+
Class<?> endpointType = getEndpointType(context, (MethodMetadata) metadata);
97+
return getEndpointAttributes(endpointType);
9698
}
9799

98-
private AnnotationAttributes getEndpointAttributes(ConditionContext context,
99-
MethodMetadata metadata) {
100+
private Class<?> getEndpointType(ConditionContext context, MethodMetadata metadata) {
101+
Map<String, Object> attributes = metadata
102+
.getAnnotationAttributes(ConditionalOnEnabledEndpoint.class.getName());
103+
if (attributes != null && attributes.containsKey("endpoint")) {
104+
Class<?> target = (Class<?>) attributes.get("endpoint");
105+
if (target != Void.class) {
106+
return target;
107+
}
108+
}
100109
// We should be safe to load at this point since we are in the REGISTER_BEAN phase
101110
try {
102-
Class<?> returnType = ClassUtils.forName(metadata.getReturnTypeName(),
111+
return ClassUtils.forName(metadata.getReturnTypeName(),
103112
context.getClassLoader());
104-
return getEndpointAttributes(returnType);
105113
}
106114
catch (Throwable ex) {
107115
throw new IllegalStateException("Failed to extract endpoint id for "
@@ -119,8 +127,8 @@ protected AnnotationAttributes getEndpointAttributes(Class<?> type) {
119127
attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(type,
120128
EndpointExtension.class, false, true);
121129
Assert.state(attributes != null,
122-
"OnEnabledEndpointCondition may only be used on @Bean methods that "
123-
+ "return an @Endpoint or @EndpointExtension");
130+
"No endpoint is specified and the return type of the @Bean method is "
131+
+ "neither an @Endpoint, nor an @EndpointExtension");
124132
return getEndpointAttributes(attributes.getClass("endpoint"));
125133
}
126134

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnEnabledEndpointTests.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,44 @@ public void outcomeWhenNoPropertiesAndExtensionAnnotationIsNotEnabledByDefaultSh
102102
.doesNotHaveBean("fooExt"));
103103
}
104104

105+
@Test
106+
public void outcomeWithReferenceWhenNoPropertiesShouldMatch() {
107+
this.contextRunner
108+
.withUserConfiguration(FooEndpointEnabledByDefaultTrue.class,
109+
ComponentEnabledIfEndpointIsEnabledConfiguration.class)
110+
.run((context) -> assertThat(context).hasBean("fooComponent"));
111+
}
112+
113+
@Test
114+
public void outcomeWithReferenceWhenEndpointEnabledPropertyIsTrueShouldMatch() {
115+
this.contextRunner.withPropertyValues("management.endpoint.foo.enabled=true")
116+
.withUserConfiguration(FooEndpointEnabledByDefaultTrue.class,
117+
ComponentEnabledIfEndpointIsEnabledConfiguration.class)
118+
.run((context) -> assertThat(context).hasBean("fooComponent"));
119+
}
120+
121+
@Test
122+
public void outcomeWithReferenceWhenEndpointEnabledPropertyIsFalseShouldNotMatch() {
123+
this.contextRunner.withPropertyValues("management.endpoint.foo.enabled=false")
124+
.withUserConfiguration(FooEndpointEnabledByDefaultTrue.class,
125+
ComponentEnabledIfEndpointIsEnabledConfiguration.class)
126+
.run((context) -> assertThat(context).doesNotHaveBean("fooComponent"));
127+
}
128+
129+
@Test
130+
public void outcomeWithNoReferenceShouldFail() {
131+
this.contextRunner
132+
.withUserConfiguration(
133+
ComponentWithNoEndpointReferenceConfiguration.class)
134+
.run((context) -> {
135+
assertThat(context).hasFailed();
136+
assertThat(context.getStartupFailure().getCause().getMessage())
137+
.contains(
138+
"No endpoint is specified and the return type of the @Bean method "
139+
+ "is neither an @Endpoint, nor an @EndpointExtension");
140+
});
141+
}
142+
105143
@Endpoint(id = "foo", enableByDefault = true)
106144
static class FooEndpointEnabledByDefaultTrue {
107145

@@ -187,4 +225,26 @@ public FooEndpointExtensionEnabledByDefaultFalse fooExt() {
187225

188226
}
189227

228+
@Configuration
229+
static class ComponentEnabledIfEndpointIsEnabledConfiguration {
230+
231+
@Bean
232+
@ConditionalOnEnabledEndpoint(endpoint = FooEndpointEnabledByDefaultTrue.class)
233+
public String fooComponent() {
234+
return "foo";
235+
}
236+
237+
}
238+
239+
@Configuration
240+
static class ComponentWithNoEndpointReferenceConfiguration {
241+
242+
@Bean
243+
@ConditionalOnEnabledEndpoint
244+
public String fooComponent() {
245+
return "foo";
246+
}
247+
248+
}
249+
190250
}

0 commit comments

Comments
 (0)