Skip to content

Commit be684a0

Browse files
committed
Add @ConditionalOnExposedEndpoint condition
Prior to this commit, Actuator `Endpoint` instantiations would be guarded by `@ConditionalOnEnabledEnpoint` condition annotations. This feature saves resources as disabled endpoints aren't unnecessarily instantiated. By default, only `"health"` and `"info"` endpoints are exposed over the web and all endpoints are exposed over JMX. As of spring-projectsgh-16090, JMX is now disabled by default. This is an opportunity to avoid instantiating endpoints if they won't be exposed at all, which is more likely due to the exposure defaults. This commit adds a new `@ConditionalOnExposedEndpoint` conditional annotation that checks the `Environment` for configuration properties under `"management.endpoints.web.exposure.*"` and `"management.endpoints.jmx.exposure.*"`. In the case of JMX, an additional check is perfomed, checking that JMX is enabled first. The rules implemented in the condition itself are following the ones described in `ExposeExcludePropertyEndpointFilter`. See spring-projectsgh-16093
1 parent 7c58d72 commit be684a0

File tree

5 files changed

+578
-56
lines changed

5 files changed

+578
-56
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright 2012-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.autoconfigure.endpoint.condition;
18+
19+
import java.util.Map;
20+
21+
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
22+
import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension;
23+
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
24+
import org.springframework.context.annotation.Bean;
25+
import org.springframework.context.annotation.ConditionContext;
26+
import org.springframework.core.annotation.AnnotatedElementUtils;
27+
import org.springframework.core.annotation.AnnotationAttributes;
28+
import org.springframework.core.type.AnnotatedTypeMetadata;
29+
import org.springframework.core.type.MethodMetadata;
30+
import org.springframework.util.Assert;
31+
import org.springframework.util.ClassUtils;
32+
33+
/**
34+
* Base class for {@link Endpoint} related {@link SpringBootCondition} implementations.
35+
*
36+
* @author Stephane Nicoll
37+
* @author Andy Wilkinson
38+
* @author Phillip Webb
39+
*/
40+
abstract class AbstractEndpointCondition extends SpringBootCondition {
41+
42+
AnnotationAttributes getEndpointAttributes(Class<?> annotationClass,
43+
ConditionContext context, AnnotatedTypeMetadata metadata) {
44+
return getEndpointAttributes(getEndpointType(annotationClass, context, metadata));
45+
}
46+
47+
Class<?> getEndpointType(Class<?> annotationClass, ConditionContext context,
48+
AnnotatedTypeMetadata metadata) {
49+
Map<String, Object> attributes = metadata
50+
.getAnnotationAttributes(annotationClass.getName());
51+
if (attributes != null && attributes.containsKey("endpoint")) {
52+
Class<?> target = (Class<?>) attributes.get("endpoint");
53+
if (target != Void.class) {
54+
return target;
55+
}
56+
}
57+
Assert.state(
58+
metadata instanceof MethodMetadata
59+
&& metadata.isAnnotated(Bean.class.getName()),
60+
"EndpointCondition must be used on @Bean methods when the endpoint is not specified");
61+
MethodMetadata methodMetadata = (MethodMetadata) metadata;
62+
try {
63+
return ClassUtils.forName(methodMetadata.getReturnTypeName(),
64+
context.getClassLoader());
65+
}
66+
catch (Throwable ex) {
67+
throw new IllegalStateException("Failed to extract endpoint id for "
68+
+ methodMetadata.getDeclaringClassName() + "."
69+
+ methodMetadata.getMethodName(), ex);
70+
}
71+
}
72+
73+
AnnotationAttributes getEndpointAttributes(Class<?> type) {
74+
AnnotationAttributes attributes = AnnotatedElementUtils
75+
.findMergedAnnotationAttributes(type, Endpoint.class, true, true);
76+
if (attributes != null) {
77+
return attributes;
78+
}
79+
attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(type,
80+
EndpointExtension.class, false, true);
81+
Assert.state(attributes != null,
82+
"No endpoint is specified and the return type of the @Bean method is "
83+
+ "neither an @Endpoint, nor an @EndpointExtension");
84+
return getEndpointAttributes(attributes.getClass("endpoint"));
85+
}
86+
87+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
* Copyright 2012-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.autoconfigure.endpoint.condition;
18+
19+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
26+
import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension;
27+
import org.springframework.context.annotation.Conditional;
28+
import org.springframework.core.env.Environment;
29+
30+
/**
31+
* {@link Conditional} that checks whether an endpoint is exposed or not.
32+
* Matches according to the endpoint exposure configuration {@link Environment}
33+
* properties. This is designed as a companion annotation to {@link ConditionalOnEnabledEndpoint}.
34+
* <p>
35+
* For a given {@link Endpoint}, the condition will match if:
36+
* <ul>
37+
* <li>{@code "management.endpoints.web.exposure.*"} expose this endpoint</li>
38+
* <li>or if JMX is enabled and {@code "management.endpoints.jmx.exposure.*"} expose this endpoint</li>
39+
* </ul>
40+
*
41+
* When placed on a {@code @Bean} method, the endpoint defaults to the return type of the
42+
* factory method:
43+
*
44+
* <pre class="code">
45+
* &#064;Configuration
46+
* public class MyConfiguration {
47+
*
48+
* &#064;ConditionalOnExposedEndpoint
49+
* &#064;Bean
50+
* public MyEndpoint myEndpoint() {
51+
* ...
52+
* }
53+
*
54+
* }</pre>
55+
* <p>
56+
* It is also possible to use the same mechanism for extensions:
57+
*
58+
* <pre class="code">
59+
* &#064;Configuration
60+
* public class MyConfiguration {
61+
*
62+
* &#064;ConditionalOnExposedEndpoint
63+
* &#064;Bean
64+
* public MyEndpointWebExtension myEndpointWebExtension() {
65+
* ...
66+
* }
67+
*
68+
* }</pre>
69+
* <p>
70+
* In the sample above, {@code MyEndpointWebExtension} will be created if the endpoint is
71+
* enabled as defined by the rules above. {@code MyEndpointWebExtension} must be a regular
72+
* extension that refers to an endpoint, something like:
73+
*
74+
* <pre class="code">
75+
* &#064;EndpointWebExtension(endpoint = MyEndpoint.class)
76+
* public class MyEndpointWebExtension {
77+
*
78+
* }</pre>
79+
* <p>
80+
* Alternatively, the target endpoint can be manually specified for components that should
81+
* only be created when a given endpoint is enabled:
82+
*
83+
* <pre class="code">
84+
* &#064;Configuration
85+
* public class MyConfiguration {
86+
*
87+
* &#064;ConditionalOnExposedEndpoint(endpoint = MyEndpoint.class)
88+
* &#064;Bean
89+
* public MyComponent myComponent() {
90+
* ...
91+
* }
92+
*
93+
* }</pre>
94+
*
95+
* @author Brian Clozel
96+
* @since 2.2.0
97+
* @see Endpoint
98+
* @see ConditionalOnEnabledEndpoint
99+
*/
100+
@Retention(RetentionPolicy.RUNTIME)
101+
@Target({ ElementType.METHOD, ElementType.TYPE })
102+
@Documented
103+
@Conditional(OnExposedEndpointCondition.class)
104+
public @interface ConditionalOnExposedEndpoint {
105+
106+
/**
107+
* The endpoint type that should be checked. Inferred when the return type of the
108+
* {@code @Bean} method is either an {@link Endpoint} or an {@link EndpointExtension}.
109+
* @return the endpoint type to check
110+
*/
111+
Class<?> endpoint() default Void.class;
112+
113+
}

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

+3-56
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,15 @@
1616

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

19-
import java.util.Map;
2019
import java.util.Optional;
2120

2221
import org.springframework.boot.actuate.endpoint.EndpointId;
23-
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
24-
import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension;
2522
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
2623
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
27-
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
28-
import org.springframework.context.annotation.Bean;
2924
import org.springframework.context.annotation.ConditionContext;
30-
import org.springframework.core.annotation.AnnotatedElementUtils;
3125
import org.springframework.core.annotation.AnnotationAttributes;
3226
import org.springframework.core.env.Environment;
3327
import org.springframework.core.type.AnnotatedTypeMetadata;
34-
import org.springframework.core.type.MethodMetadata;
35-
import org.springframework.util.Assert;
36-
import org.springframework.util.ClassUtils;
3728
import org.springframework.util.ConcurrentReferenceHashMap;
3829

3930
/**
@@ -44,7 +35,7 @@
4435
* @author Phillip Webb
4536
* @see ConditionalOnEnabledEndpoint
4637
*/
47-
class OnEnabledEndpointCondition extends SpringBootCondition {
38+
class OnEnabledEndpointCondition extends AbstractEndpointCondition {
4839

4940
private static final String ENABLED_BY_DEFAULT_KEY = "management.endpoints.enabled-by-default";
5041

@@ -54,7 +45,8 @@ class OnEnabledEndpointCondition extends SpringBootCondition {
5445
public ConditionOutcome getMatchOutcome(ConditionContext context,
5546
AnnotatedTypeMetadata metadata) {
5647
Environment environment = context.getEnvironment();
57-
AnnotationAttributes attributes = getEndpointAttributes(context, metadata);
48+
AnnotationAttributes attributes = getEndpointAttributes(
49+
ConditionalOnEnabledEndpoint.class, context, metadata);
5850
EndpointId id = EndpointId.of(attributes.getString("id"));
5951
String key = "management.endpoint." + id.toLowerCaseString() + ".enabled";
6052
Boolean userDefinedEnabled = environment.getProperty(key, Boolean.class);
@@ -88,49 +80,4 @@ private Boolean isEnabledByDefault(Environment environment) {
8880
return enabledByDefault.orElse(null);
8981
}
9082

91-
private AnnotationAttributes getEndpointAttributes(ConditionContext context,
92-
AnnotatedTypeMetadata metadata) {
93-
return getEndpointAttributes(getEndpointType(context, metadata));
94-
}
95-
96-
private Class<?> getEndpointType(ConditionContext context,
97-
AnnotatedTypeMetadata metadata) {
98-
Map<String, Object> attributes = metadata
99-
.getAnnotationAttributes(ConditionalOnEnabledEndpoint.class.getName());
100-
if (attributes != null && attributes.containsKey("endpoint")) {
101-
Class<?> target = (Class<?>) attributes.get("endpoint");
102-
if (target != Void.class) {
103-
return target;
104-
}
105-
}
106-
Assert.state(
107-
metadata instanceof MethodMetadata
108-
&& metadata.isAnnotated(Bean.class.getName()),
109-
"OnEnabledEndpointCondition must be used on @Bean methods when the endpoint is not specified");
110-
MethodMetadata methodMetadata = (MethodMetadata) metadata;
111-
try {
112-
return ClassUtils.forName(methodMetadata.getReturnTypeName(),
113-
context.getClassLoader());
114-
}
115-
catch (Throwable ex) {
116-
throw new IllegalStateException("Failed to extract endpoint id for "
117-
+ methodMetadata.getDeclaringClassName() + "."
118-
+ methodMetadata.getMethodName(), ex);
119-
}
120-
}
121-
122-
protected AnnotationAttributes getEndpointAttributes(Class<?> type) {
123-
AnnotationAttributes attributes = AnnotatedElementUtils
124-
.findMergedAnnotationAttributes(type, Endpoint.class, true, true);
125-
if (attributes != null) {
126-
return attributes;
127-
}
128-
attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(type,
129-
EndpointExtension.class, false, true);
130-
Assert.state(attributes != null,
131-
"No endpoint is specified and the return type of the @Bean method is "
132-
+ "neither an @Endpoint, nor an @EndpointExtension");
133-
return getEndpointAttributes(attributes.getClass("endpoint"));
134-
}
135-
13683
}

0 commit comments

Comments
 (0)